diff --git a/hub/.gitignore b/hub/.gitignore index 12419b1..ed814f3 100644 --- a/hub/.gitignore +++ b/hub/.gitignore @@ -4,3 +4,6 @@ dist/ .env .env.* !.env.example +admin-web/node_modules/ +admin-web/build/ +admin-web/.svelte-kit/ diff --git a/hub/admin-web/.gitignore b/hub/admin-web/.gitignore new file mode 100644 index 0000000..3b462cb --- /dev/null +++ b/hub/admin-web/.gitignore @@ -0,0 +1,23 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/hub/admin-web/.npmrc b/hub/admin-web/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/hub/admin-web/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/hub/admin-web/.vscode/extensions.json b/hub/admin-web/.vscode/extensions.json new file mode 100644 index 0000000..28d1e67 --- /dev/null +++ b/hub/admin-web/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["svelte.svelte-vscode"] +} diff --git a/hub/admin-web/README.md b/hub/admin-web/README.md new file mode 100644 index 0000000..7957afe --- /dev/null +++ b/hub/admin-web/README.md @@ -0,0 +1,42 @@ +# sv + +Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). + +## Creating a project + +If you're seeing this, you've probably already done this step. Congrats! + +```sh +# create a new project +npx sv create my-app +``` + +To recreate this project with the same configuration: + +```sh +# recreate this project +npx sv@0.16.2 create --template minimal --types ts --install npm D:/Projects/curriculum-project-hub/hub/admin-web +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: + +```sh +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +To create a production version of your app: + +```sh +npm run build +``` + +You can preview the production build with `npm run preview`. + +> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. diff --git a/hub/admin-web/package-lock.json b/hub/admin-web/package-lock.json new file mode 100644 index 0000000..56979db --- /dev/null +++ b/hub/admin-web/package-lock.json @@ -0,0 +1,2594 @@ +{ + "name": "admin-web", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "admin-web", + "version": "0.0.1", + "devDependencies": { + "@skeletonlabs/skeleton": "^4.15.2", + "@skeletonlabs/skeleton-svelte": "^4.15.2", + "@sveltejs/adapter-auto": "^7.0.1", + "@sveltejs/adapter-static": "^3.0.10", + "@sveltejs/kit": "^2.63.0", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tailwindcss/vite": "^4.3.2", + "bits-ui": "^2.18.1", + "svelte": "^5.56.1", + "svelte-check": "^4.6.0", + "tailwindcss": "^4.3.2", + "typescript": "^6.0.3", + "vite": "^8.0.16" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@internationalized/date": { + "version": "3.12.2", + "resolved": "https://registry.npmmirror.com/@internationalized/date/-/date-3.12.2.tgz", + "integrity": "sha512-FY1Y+H64NDs+HAF6omlnWxm3mEpfgaCSWtL5l551ZZfImA+kGjPFgrnJrGjH6lfmLL0g8Z/mBu1R3kufeCp6Jw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@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.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.139.0", + "resolved": "https://registry.npmmirror.com/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmmirror.com/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmmirror.com/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@skeletonlabs/skeleton": { + "version": "4.15.2", + "resolved": "https://registry.npmmirror.com/@skeletonlabs/skeleton/-/skeleton-4.15.2.tgz", + "integrity": "sha512-5O23Py76nw56aoieV2b2T7MJ6xS0DwDjUULpwLnCXxXOnmzADEoWoQxb/ABbqg04IMOygtRzK3HO22I1+kFsog==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "tailwindcss": "^4.0.0" + } + }, + "node_modules/@skeletonlabs/skeleton-common": { + "version": "4.15.2", + "resolved": "https://registry.npmmirror.com/@skeletonlabs/skeleton-common/-/skeleton-common-4.15.2.tgz", + "integrity": "sha512-y7KZn++Av8UHdoeaaguQ7zIS1HlK7Y5jyPDnHy65wJ60iS97fMtQV0kGhqVoYCWhor+xrjbAFjWtaPOPPDEMYA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@skeletonlabs/skeleton-svelte": { + "version": "4.15.2", + "resolved": "https://registry.npmmirror.com/@skeletonlabs/skeleton-svelte/-/skeleton-svelte-4.15.2.tgz", + "integrity": "sha512-vZkRhR701EOHVXVKh/NFRSY8D9LPBvmdNFdtfgqO7TEg77sypJUvERl2dxErTLY1QjVklVpjsHRCTaSLn+1Ntg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@internationalized/date": "3.12.0", + "@skeletonlabs/skeleton-common": "4.15.2", + "@zag-js/accordion": "1.39.1", + "@zag-js/avatar": "1.39.1", + "@zag-js/carousel": "1.39.1", + "@zag-js/collapsible": "1.39.1", + "@zag-js/collection": "1.39.1", + "@zag-js/combobox": "1.39.1", + "@zag-js/date-picker": "1.39.1", + "@zag-js/dialog": "1.39.1", + "@zag-js/file-upload": "1.39.1", + "@zag-js/floating-panel": "1.39.1", + "@zag-js/listbox": "1.39.1", + "@zag-js/menu": "1.39.1", + "@zag-js/pagination": "1.39.1", + "@zag-js/popover": "1.39.1", + "@zag-js/progress": "1.39.1", + "@zag-js/radio-group": "1.39.1", + "@zag-js/rating-group": "1.39.1", + "@zag-js/slider": "1.39.1", + "@zag-js/steps": "1.39.1", + "@zag-js/svelte": "1.39.1", + "@zag-js/switch": "1.39.1", + "@zag-js/tabs": "1.39.1", + "@zag-js/tags-input": "1.39.1", + "@zag-js/toast": "1.39.1", + "@zag-js/toggle-group": "1.39.1", + "@zag-js/tooltip": "1.39.1", + "@zag-js/tree-view": "1.39.1" + }, + "peerDependencies": { + "svelte": "^5.29.0" + } + }, + "node_modules/@skeletonlabs/skeleton-svelte/node_modules/@internationalized/date": { + "version": "3.12.0", + "resolved": "https://registry.npmmirror.com/@internationalized/date/-/date-3.12.0.tgz", + "integrity": "sha512-/PyIMzK29jtXaGU23qTvNZxvBXRtKbNnGDFD+PY6CZw/Y8Ex8pFUzkuCJCG9aOqmShjqhS9mPqP6Dk5onQY8rQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.11.tgz", + "integrity": "sha512-LFuZUkjJ9iF7JZye/aG5XM0SFcQ5VyL0oVX4WJ9dc0Va3R3s0OauX1BESVCb+YN/ol8TAfqGDDAQsTG627Y5kw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/adapter-auto": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/@sveltejs/adapter-auto/-/adapter-auto-7.0.1.tgz", + "integrity": "sha512-dvuPm1E7M9NI/+canIQ6KKQDU2AkEefEZ2Dp7cY6uKoPq9Z/PhOXABe526UdW2mN986gjVkuSLkOYIBnS/M2LQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@sveltejs/kit": "^2.0.0" + } + }, + "node_modules/@sveltejs/adapter-static": { + "version": "3.0.10", + "resolved": "https://registry.npmmirror.com/@sveltejs/adapter-static/-/adapter-static-3.0.10.tgz", + "integrity": "sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@sveltejs/kit": "^2.0.0" + } + }, + "node_modules/@sveltejs/kit": { + "version": "2.69.2", + "resolved": "https://registry.npmmirror.com/@sveltejs/kit/-/kit-2.69.2.tgz", + "integrity": "sha512-CMdPDbYjRwRu4KXTxBVMuOpFPCt1i/v0ANennotec+K9Cmb2e3w2yYzJiC6Vh/WSvm9Khi5sJMZa0rJPqfHlDw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@sveltejs/acorn-typescript": "^1.0.9", + "@types/cookie": "^0.6.0", + "acorn": "^8.16.0", + "cookie": "^0.6.0", + "devalue": "^5.8.1", + "esm-env": "^1.2.2", + "kleur": "^4.1.5", + "magic-string": "^0.30.5", + "mrmime": "^2.0.0", + "set-cookie-parser": "^3.0.0", + "sirv": "^3.0.0" + }, + "bin": { + "svelte-kit": "svelte-kit.js" + }, + "engines": { + "node": ">=18.13" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": "^5.3.3 || ^6.0.0", + "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@sveltejs/load-config": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/@sveltejs/load-config/-/load-config-0.2.0.tgz", + "integrity": "sha512-1LgZ/qUqSoq+QorD83lk2hka79Px0wXNW2q5V1nZlxGhQgw1jrsIbVz5YiCeucVLo4XvFLjXukUaQjIiqowkcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-7.2.0.tgz", + "integrity": "sha512-1SpkuMSRLfugrVX+IrKfE1RUegzo8AQzKQ6qQPfVzbcWi5IhuTPaKb5ZrLpucleFznkc4/RTeSPoRnGWFxX+EQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "deepmerge": "^4.3.1", + "magic-string": "^0.30.21", + "obug": "^2.1.0", + "vitefu": "^1.1.2" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24" + }, + "peerDependencies": { + "svelte": "^5.46.4", + "vite": "^8.0.0-beta.7 || ^8.0.0" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmmirror.com/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/@tailwindcss/node/-/node-4.3.2.tgz", + "integrity": "sha512-yWP/sqEcBLaD8JuA6zNwxoYKr75qxTioYwlRwekj5Jr/I5GXnoJfjetH/psLUIv74cYTH2lBUEzBkinthoYcBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "5.21.6", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.2" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/@tailwindcss/oxide/-/oxide-4.3.2.tgz", + "integrity": "sha512-z8ZgnzX8gdNoWLBLqBPoh/sjnxkwvf9ZuWjnO0l0yIzbLa5/9S+eC5QxGZKRobVHIC3/1BoMWjHblqWjcgFgag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.2", + "@tailwindcss/oxide-darwin-arm64": "4.3.2", + "@tailwindcss/oxide-darwin-x64": "4.3.2", + "@tailwindcss/oxide-freebsd-x64": "4.3.2", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.2", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.2", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.2", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.2", + "@tailwindcss/oxide-linux-x64-musl": "4.3.2", + "@tailwindcss/oxide-wasm32-wasi": "4.3.2", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.2", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.2" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.2.tgz", + "integrity": "sha512-WHxqIuHpvZ5VtdX6GTl1Ik/Vp2YuN42Et+0CdeaVd/frQ9jAvGmvR8vLT+jk3e8/Q3x8kECB9+R17pgpp2BulA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.2.tgz", + "integrity": "sha512-GZypeUY/IDJW3877KeM+O67vbXr3MBnbtEL4aYhNErv/JWZhye2vGSWWG9tB6iiqR2MqRNkY8IOUy4NdSZV26w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.2.tgz", + "integrity": "sha512-UIIzmefR6KO1sDU7MzRqAxC8iBpft/VhkGjTjnhoS6k7Z3rQ9wEgA1ODSiyH/tcSYssulNm4Ci3hOeK1jH7ccQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.2.tgz", + "integrity": "sha512-GN+uAmcI6DNspnCDwtOAZrTz6oukJnp337qZvxqCGLd3BHBzJpO0ZbTLRvJNdztOeAmTzewewGIMPb0tk2R4WA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.2.tgz", + "integrity": "sha512-4ABn7qSbdHRwTiDiuWNegCyb5+2FJ4vKIKc3DmKrvAFw7MU1Lm11dIkTPwUaFdTzc7IsOpDbqBrlh0x6y36U/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.2.tgz", + "integrity": "sha512-wDgEIGwoM8w8pufh9LVt1PahDgNdKXrLC2qfAnV3vAmococ9RWbxeAw4pxPttd/TsJfwjyLf90Dg1y9y8I6Emw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.2.tgz", + "integrity": "sha512-J5Nuk0uZQIiMTJj3LEx4sAA9tMFUoXQZFv1J6An+QGYe53HKRJuFDi0rpq/tuouCZeAbOBY3kQ6g8qeD4TUjtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.2.tgz", + "integrity": "sha512-kqCZpSKOBEJO4mz7OqWoofBZeXTAwaVGPj0ErAj7CojmhKpWVWVOnrt9dE8odoIraZq4oj3ausM37kXi+Tow8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.2.tgz", + "integrity": "sha512-cixpqbh2toJDmkuCRI68nXA8ZxNmdK9Y+9v5h3MC3ZQKy/0BO8AWzlkWyRM7JAFSGBlfig4YVTPsK6MVgqz1uw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.2.tgz", + "integrity": "sha512-4ec2Z/LOmRsAgU23CS4xeJfcJlmRg94A/XrbGRCF1gyU/zdDfRLYDVsS+ynSZCmGNxQ1jQriQOKMQeQxBA3Isw==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.2.tgz", + "integrity": "sha512-Zyr/M0+XcYZu3bZrUytc7TXvrk0ftWfl8gN2MwekNDzhqhKRUucMPSeOzM0o0wH5AWOU49BsKRrfKxI2atCPMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.2.tgz", + "integrity": "sha512-QI9BO7KlNZsp2GuO0jwAAj5jCDABOKXRkCk2XuKTSaNEFSdfzqswYVTtCHBNKHLsqyjFyFkqlDiwkNbTYSssMQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/@tailwindcss/vite/-/vite-4.3.2.tgz", + "integrity": "sha512-eHpMeX4JXfVNJDEcsouTeCBubJBTcTLigeaw/NTUW6PB5ATKKXdyonnXgTBX2VuRbjz1hjfz6C5XAhr52ImQXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.2", + "@tailwindcss/oxide": "4.3.2", + "tailwindcss": "4.3.2" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@zag-js/accordion": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/accordion/-/accordion-1.39.1.tgz", + "integrity": "sha512-GA3m7gRTm3weSe1eMlHIsTNztcjZ6joIaRgxxKil7q/UX0xIVVGDy0aCr6oo7FAuoMiOOBVurYXILpFZ30nOXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.39.1", + "@zag-js/core": "1.39.1", + "@zag-js/dom-query": "1.39.1", + "@zag-js/types": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/anatomy": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/anatomy/-/anatomy-1.39.1.tgz", + "integrity": "sha512-p2iFAs2pVQgv5iCDAftA7g9Z/fUYXW94dRIGk415TSbkp/YDENydm/JtRoNctp302UIx4Eeuc5QBR+7h5kuISA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@zag-js/aria-hidden": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/aria-hidden/-/aria-hidden-1.39.1.tgz", + "integrity": "sha512-wiwcz3N086qBMEU3VKfHhcvGm6Jm1PIcDXys/jEqiKPtHoYZhDip0n0cPOoasss/A1oS39QFVdk3WpLXGu3Izw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.39.1" + } + }, + "node_modules/@zag-js/auto-resize": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/auto-resize/-/auto-resize-1.39.1.tgz", + "integrity": "sha512-ditIo9mW7fapq+4yx3/8hMpMZlWaoOy66EOzUz8dSVqnxnTWAjnTICu/9zFh8pkWerlzGTtDOJPP1oZ8S/rgVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.39.1" + } + }, + "node_modules/@zag-js/avatar": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/avatar/-/avatar-1.39.1.tgz", + "integrity": "sha512-LWrgJ0bebnXPSL+uehA9z6BlCD/MZEOQBJqH/F2QQFSAAZXUUDKtzVDmc+UtwjDsHXqqTghi+v2atQJHNMcJ2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.39.1", + "@zag-js/core": "1.39.1", + "@zag-js/dom-query": "1.39.1", + "@zag-js/types": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/carousel": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/carousel/-/carousel-1.39.1.tgz", + "integrity": "sha512-5z5z3IldUgZ/R+KZLNQDoJFNTXzYd28YOmgfWH61Vvyv+RarX8kwZW8ajW/fNiqcWXyhW3/VMU0lArrfjbQVtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.39.1", + "@zag-js/core": "1.39.1", + "@zag-js/dom-query": "1.39.1", + "@zag-js/scroll-snap": "1.39.1", + "@zag-js/types": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/collapsible": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/collapsible/-/collapsible-1.39.1.tgz", + "integrity": "sha512-Zgccg/t7M8i0JVwZPPgW7XB7kGhTO475hsmwkF/8CYLqBBckVDHUARp2we24hENCm/98eez6R0eDEmE+tldFWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.39.1", + "@zag-js/core": "1.39.1", + "@zag-js/dom-query": "1.39.1", + "@zag-js/types": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/collection": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/collection/-/collection-1.39.1.tgz", + "integrity": "sha512-fyOyKmP7MRo0/U8mBmB7KgHRXHhXP27LCcasy3x+qTAQtuEfYG1EPhKuj07oBWlX/2qfcKYn2R3YopHcqFcCiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/combobox": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/combobox/-/combobox-1.39.1.tgz", + "integrity": "sha512-fmStpG+k4xrxCzqUX0ssnOMeoSietWm5ir3qmEZcagzNqNycAXMvOELAIeyXi87Kut6aDGhxLOV7o395HVXl/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.39.1", + "@zag-js/collection": "1.39.1", + "@zag-js/core": "1.39.1", + "@zag-js/dismissable": "1.39.1", + "@zag-js/dom-query": "1.39.1", + "@zag-js/focus-visible": "1.39.1", + "@zag-js/live-region": "1.39.1", + "@zag-js/popper": "1.39.1", + "@zag-js/types": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/core": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/core/-/core-1.39.1.tgz", + "integrity": "sha512-Yp0r49QLYXe2j7fgyAiilH4umXFydCnr5hcRDwJU+sxvUAlq00JQIJIEK2pT6k8cJiNNsFEV5WkOX7jsqpAX2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/date-picker": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/date-picker/-/date-picker-1.39.1.tgz", + "integrity": "sha512-t9q1H0aZQJkbzKTR2Bn5vMwaoFoirxekiSxw8ju0F0vr4Kg4BJ9yueOQm5I2wALKnJbZu4Ua5MgzlrDF3CQt3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.39.1", + "@zag-js/core": "1.39.1", + "@zag-js/date-utils": "1.39.1", + "@zag-js/dismissable": "1.39.1", + "@zag-js/dom-query": "1.39.1", + "@zag-js/live-region": "1.39.1", + "@zag-js/popper": "1.39.1", + "@zag-js/types": "1.39.1", + "@zag-js/utils": "1.39.1" + }, + "peerDependencies": { + "@internationalized/date": ">=3.0.0" + } + }, + "node_modules/@zag-js/date-utils": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/date-utils/-/date-utils-1.39.1.tgz", + "integrity": "sha512-i4SvBhru2Yz/zsHT0XvyFhf4a+pAKYkWXeVfU0RvF2S6mPTfgaMFF9ZNPq5Sy8K31EtAa6AVXcybYaYnibn1FA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@internationalized/date": ">=3.0.0" + } + }, + "node_modules/@zag-js/dialog": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/dialog/-/dialog-1.39.1.tgz", + "integrity": "sha512-q+HTmfuRDRZthln9mb7i52wdltQOZlw3+nw3a2uygEe9xuEtHBwUz31XJzkn2UWQqhAt7cC39OwykhNLKrfkqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.39.1", + "@zag-js/aria-hidden": "1.39.1", + "@zag-js/core": "1.39.1", + "@zag-js/dismissable": "1.39.1", + "@zag-js/dom-query": "1.39.1", + "@zag-js/focus-trap": "1.39.1", + "@zag-js/remove-scroll": "1.39.1", + "@zag-js/types": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/dismissable": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/dismissable/-/dismissable-1.39.1.tgz", + "integrity": "sha512-7/soy93Ersd5qedhSL/+CDcZ9gNTQV0ooDcqKtM8b4IxwD4rgWwGsewJY+tbKmOqaZobwa0YcWV2+YGgI23ESw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.39.1", + "@zag-js/interact-outside": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/dom-query": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/dom-query/-/dom-query-1.39.1.tgz", + "integrity": "sha512-k01aXeUWLyJfB61CODaXj4PLhYmVpnVMFrC+3nk/XCn1MW7my8L/8KVg0m4W8n+X9MhpaLWsZDmK/dwED/3qSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/types": "1.39.1" + } + }, + "node_modules/@zag-js/file-upload": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/file-upload/-/file-upload-1.39.1.tgz", + "integrity": "sha512-cErPOnPwPyneUXpelsfm75DKn0/4SI8aqQnlbrqo522PEqAQyDfDdBsqebGgKWG3F0A++kKFp9LO9A5zCrw5gA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.39.1", + "@zag-js/core": "1.39.1", + "@zag-js/dom-query": "1.39.1", + "@zag-js/file-utils": "1.39.1", + "@zag-js/i18n-utils": "1.39.1", + "@zag-js/types": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/file-utils": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/file-utils/-/file-utils-1.39.1.tgz", + "integrity": "sha512-ll/W5o74SMmoAS+l7PkmmGjPj4PLCSG/cwQh1Y/+LpaSev0YiR3Nk2OzRIIPtm3NivYVxKGawaCOf1RvT/82LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/i18n-utils": "1.39.1" + } + }, + "node_modules/@zag-js/floating-panel": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/floating-panel/-/floating-panel-1.39.1.tgz", + "integrity": "sha512-IfPbf3pwJGqBWHec/rPzpdPjfMCLed59LlEophvRy49FEdksv8eN6nr9DXl2wWZEoQhH99scXfLMbtEZsPsFWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.39.1", + "@zag-js/core": "1.39.1", + "@zag-js/dom-query": "1.39.1", + "@zag-js/popper": "1.39.1", + "@zag-js/rect-utils": "1.39.1", + "@zag-js/store": "1.39.1", + "@zag-js/types": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/focus-trap": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/focus-trap/-/focus-trap-1.39.1.tgz", + "integrity": "sha512-2ZzVefHMotvtxUo/gP4R45Szw/EPaPkTKEHaug6/il62SPDbkFODF+5r1zXyLbLuwCHq0apvQasg/ONLihwlXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.39.1" + } + }, + "node_modules/@zag-js/focus-visible": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/focus-visible/-/focus-visible-1.39.1.tgz", + "integrity": "sha512-iEuTOYHE8HRn/7ULC9c9BTTWo0C0MJRCbYVxbh/d7v8qAuq4CS76pdfceNo3KeWbb968T+yiG6q0AjiHsr8IOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.39.1" + } + }, + "node_modules/@zag-js/i18n-utils": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/i18n-utils/-/i18n-utils-1.39.1.tgz", + "integrity": "sha512-TKRLQQlHgJ4cxsHo3tZPtbFjGu9m1UPtfezRGFKq7A8czhdqRhaCpaWF849cd6dI7x6rWvvTan858gOFpyANnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.39.1" + } + }, + "node_modules/@zag-js/interact-outside": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/interact-outside/-/interact-outside-1.39.1.tgz", + "integrity": "sha512-LnSbA+txMsFmzNPn84QKH01x2yJv4At/eKHn6rT2PyxXkJQIh8PvCTS3zVz4Syw11cmhcXt2eRwhzx8yImV92w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/listbox": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/listbox/-/listbox-1.39.1.tgz", + "integrity": "sha512-Mz0UpdXobdTQTyjM+Avgi7pDVB2dKyaUHqw3TloeleQL3VwTqClclkwHXtLYYE+oXa0zOet37wI9mzfaYx9iZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.39.1", + "@zag-js/collection": "1.39.1", + "@zag-js/core": "1.39.1", + "@zag-js/dom-query": "1.39.1", + "@zag-js/focus-visible": "1.39.1", + "@zag-js/types": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/live-region": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/live-region/-/live-region-1.39.1.tgz", + "integrity": "sha512-E7YNd0QGzJ2n1ZhnI2smv+klwifsNRf9QaDCx7quVJCVYywpupsBK4R25KN75S1z8XaK+jAy6HYKj8DIhYjYeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@zag-js/menu": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/menu/-/menu-1.39.1.tgz", + "integrity": "sha512-bRDGLGkiGhzNtORBXkbBQV/xp2zEkwpYIepfWCaUoFwKUmx7GGnShTBFxJyq0u2D4IkS9GOwcqm20EhMv6V+TA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.39.1", + "@zag-js/core": "1.39.1", + "@zag-js/dismissable": "1.39.1", + "@zag-js/dom-query": "1.39.1", + "@zag-js/focus-visible": "1.39.1", + "@zag-js/popper": "1.39.1", + "@zag-js/rect-utils": "1.39.1", + "@zag-js/types": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/pagination": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/pagination/-/pagination-1.39.1.tgz", + "integrity": "sha512-3Q1B9/g3ajhvXjuGffJ7otyXcXK5+uhdbE5A9CZa4bsW3pf25L9Cp+ZAjdXQMDc8T4jhZJAKFmDJfQgtr1oEIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.39.1", + "@zag-js/core": "1.39.1", + "@zag-js/dom-query": "1.39.1", + "@zag-js/types": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/popover": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/popover/-/popover-1.39.1.tgz", + "integrity": "sha512-aO3ExO/O7Sa3ovdozFI6SujhNOpYdCca4bImnAiovDL8DY8zN3UNQebu35IQvw9/aRsx9VKSJL1AqzJJUImFRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.39.1", + "@zag-js/aria-hidden": "1.39.1", + "@zag-js/core": "1.39.1", + "@zag-js/dismissable": "1.39.1", + "@zag-js/dom-query": "1.39.1", + "@zag-js/focus-trap": "1.39.1", + "@zag-js/popper": "1.39.1", + "@zag-js/remove-scroll": "1.39.1", + "@zag-js/types": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/popper": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/popper/-/popper-1.39.1.tgz", + "integrity": "sha512-h0UMY2dXJNfM3OvMQ9t9LzlmwvpCgjloz2IvU1txY3r32UIy7ve1H70zkKagLtLRxFTuWmhumYUPULPo/6a1DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.6", + "@zag-js/dom-query": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/progress": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/progress/-/progress-1.39.1.tgz", + "integrity": "sha512-1IHyOw8DqPs3YH149Oj7W9a5oEfY5pc9GAVOPGbzYxVK/W8d/NIjVxa565I3J5cDJ0s6z3FrMSXMWUwr1ML4tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.39.1", + "@zag-js/core": "1.39.1", + "@zag-js/dom-query": "1.39.1", + "@zag-js/types": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/radio-group": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/radio-group/-/radio-group-1.39.1.tgz", + "integrity": "sha512-+sC9xcAyY/GbY+8HpKlbPgSyOxBLUSB18s6fe6K1wdmyom4PM0nmhLouuxisbFZYHOyfQwAOMo+ainRENB2hzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.39.1", + "@zag-js/core": "1.39.1", + "@zag-js/dom-query": "1.39.1", + "@zag-js/focus-visible": "1.39.1", + "@zag-js/types": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/rating-group": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/rating-group/-/rating-group-1.39.1.tgz", + "integrity": "sha512-IfdxWmM+3zpztx/HcE3bWob72sZNb1+BzK4tSySLVyjeqs8OzLDzrCbKqt10DmibnNOvpbjbq4eX4P5hV9YN7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.39.1", + "@zag-js/core": "1.39.1", + "@zag-js/dom-query": "1.39.1", + "@zag-js/types": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/rect-utils": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/rect-utils/-/rect-utils-1.39.1.tgz", + "integrity": "sha512-5gJ0PzeUme76xTWG+4XythWgmGgDKV4XAxEUaB3KKDtXgjDHwtu7PwKLIzFtlaaSf/U23PY+RNVBVCYg1GmZog==", + "dev": true, + "license": "MIT" + }, + "node_modules/@zag-js/remove-scroll": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/remove-scroll/-/remove-scroll-1.39.1.tgz", + "integrity": "sha512-uZfPR3Gl9sQFo+tJ7kbuwsBhw+RIZwWFnMDgrz5LIwSNGN6hsyC4HGOxe29clkWQ2X2AjqqmEMETwgX7Jg+wxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.39.1" + } + }, + "node_modules/@zag-js/scroll-snap": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/scroll-snap/-/scroll-snap-1.39.1.tgz", + "integrity": "sha512-AzCc8MAAVqkiK5Y0cJZ24OIBZDQrUmEexACMuR6M5yZmlcEbS0EA/d6Wq+LSR1JMVTD4B+UwcMj1D3vJQ90ZTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/dom-query": "1.39.1" + } + }, + "node_modules/@zag-js/slider": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/slider/-/slider-1.39.1.tgz", + "integrity": "sha512-OEA9R7Ly5cw+6ANofnMpuHH3rAo8gZEnxy7iEwePu11pq2RCnt8DSj2V+uqU+dTq15Uup1LSzRgJfTnAC4Z85A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.39.1", + "@zag-js/core": "1.39.1", + "@zag-js/dom-query": "1.39.1", + "@zag-js/types": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/steps": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/steps/-/steps-1.39.1.tgz", + "integrity": "sha512-DC6swMpwITTB0DyCSxlpWyPNSUN9ul9jz4N6aAyQ0L1IK/noF/YYTZRAcXNSRzN4iutO/2mFGGbwGq/oVf+gPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.39.1", + "@zag-js/core": "1.39.1", + "@zag-js/dom-query": "1.39.1", + "@zag-js/types": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/store": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/store/-/store-1.39.1.tgz", + "integrity": "sha512-zFpwP4lhiBVD9987rwAfZNVa2/f/xx4mhbCE1EEw31zxLAozY2jONeJ3UzPP05VbzKlRHBcvkaXAJQQGegTwFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "proxy-compare": "3.0.1" + } + }, + "node_modules/@zag-js/svelte": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/svelte/-/svelte-1.39.1.tgz", + "integrity": "sha512-ZOyZjyvjePZdrkNTy5fa92ijeeID9e+3LRGziKGIII3JSEvwfkG/Buf8W84N8VHFxi0G0GcKmgVCDgKHyGQYoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/core": "1.39.1", + "@zag-js/types": "1.39.1", + "@zag-js/utils": "1.39.1" + }, + "peerDependencies": { + "svelte": ">=5" + } + }, + "node_modules/@zag-js/switch": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/switch/-/switch-1.39.1.tgz", + "integrity": "sha512-ikeQ42c0vyyPLeyW9U0dvcqTV1Ekpx5jZ050R905HGJ2GeWE0uBGuHbMpTG5U6Pwb0a+TMzqAr+jMsquVTCwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.39.1", + "@zag-js/core": "1.39.1", + "@zag-js/dom-query": "1.39.1", + "@zag-js/focus-visible": "1.39.1", + "@zag-js/types": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/tabs": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/tabs/-/tabs-1.39.1.tgz", + "integrity": "sha512-P2RThO1gX9SFsNqrAGPsXJxrjn5YqP6MFs9mdExU+tzzZyVjJQADkAmh98C0eEaCb6HKLpJZ/17hrnLDhm1Tig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.39.1", + "@zag-js/core": "1.39.1", + "@zag-js/dom-query": "1.39.1", + "@zag-js/types": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/tags-input": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/tags-input/-/tags-input-1.39.1.tgz", + "integrity": "sha512-tc0+bd9FiUJwa+wY2hSVVGHLIBC3C3rOZX/4zjchRMs1xgl92c1/tYbytXny7ABB8ZMHveG7MtgDppVF4VkwBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.39.1", + "@zag-js/auto-resize": "1.39.1", + "@zag-js/core": "1.39.1", + "@zag-js/dom-query": "1.39.1", + "@zag-js/interact-outside": "1.39.1", + "@zag-js/live-region": "1.39.1", + "@zag-js/types": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/toast": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/toast/-/toast-1.39.1.tgz", + "integrity": "sha512-K7ndEfBTKDds10iQKCQUmin74s6V4BEIypAIyQxs18gQB9TCn5+wff886JAzecIKPY97PDQHDKjYR71yzRC7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.39.1", + "@zag-js/core": "1.39.1", + "@zag-js/dismissable": "1.39.1", + "@zag-js/dom-query": "1.39.1", + "@zag-js/types": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/toggle-group": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/toggle-group/-/toggle-group-1.39.1.tgz", + "integrity": "sha512-KS4Bo17foMKXVBhQjocRf4GQxMV4pMXclTo14IWjldaHs2HIrNJ0Ar0Ri+vo47BBKBNsXs4HuNvfbMdQj94wEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.39.1", + "@zag-js/core": "1.39.1", + "@zag-js/dom-query": "1.39.1", + "@zag-js/types": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/tooltip": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/tooltip/-/tooltip-1.39.1.tgz", + "integrity": "sha512-IsxFj7l8kPciwIyYJWlmQ7mhXocbjXxLj3m9z099slYOF7lApA33/ndY32w9ptrI4/nUh2nldzw6eRfSpVnuOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.39.1", + "@zag-js/core": "1.39.1", + "@zag-js/dom-query": "1.39.1", + "@zag-js/focus-visible": "1.39.1", + "@zag-js/popper": "1.39.1", + "@zag-js/types": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/tree-view": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/tree-view/-/tree-view-1.39.1.tgz", + "integrity": "sha512-sm6qUZjO0OaqBqO5s55KU+l5p1wXfUVScoen7BYVoFBuROH7qAZJi8YMclGvnnlyV506i8Hk0qqWnLg0F38jCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@zag-js/anatomy": "1.39.1", + "@zag-js/collection": "1.39.1", + "@zag-js/core": "1.39.1", + "@zag-js/dom-query": "1.39.1", + "@zag-js/types": "1.39.1", + "@zag-js/utils": "1.39.1" + } + }, + "node_modules/@zag-js/types": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/types/-/types-1.39.1.tgz", + "integrity": "sha512-w3vVpgxmdJvMDvv19DXTtFI6kJL6TXw//U0Z1BAc3rnDA9orcB9Ryw4uMNvIzFA607CgssyJcWDaQ/M3yAcbJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "3.2.3" + } + }, + "node_modules/@zag-js/utils": { + "version": "1.39.1", + "resolved": "https://registry.npmmirror.com/@zag-js/utils/-/utils-1.39.1.tgz", + "integrity": "sha512-9k741cH7L655Ua3tedTkuMblcXVXVgCLTB9svp9oTjA7oatpOpYF4z43kgAQVjyThNXMJ7AvtO4C80ajQLTScg==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aria-query": { + "version": "5.3.1", + "resolved": "https://registry.npmmirror.com/aria-query/-/aria-query-5.3.1.tgz", + "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/bits-ui": { + "version": "2.18.1", + "resolved": "https://registry.npmmirror.com/bits-ui/-/bits-ui-2.18.1.tgz", + "integrity": "sha512-KkemzKFH4T3gt3H+P86JcnAWExjByv/6vlwjm/BoCwTPHu03yiCdxbghdJLvFReQTe0acCAiRcKfmixxD6XvlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.1", + "@floating-ui/dom": "^1.7.1", + "esm-env": "^1.1.2", + "runed": "^0.35.1", + "svelte-toolbelt": "^0.10.6", + "tabbable": "^6.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/huntabyte" + }, + "peerDependencies": { + "@internationalized/date": "^3.8.1", + "svelte": "^5.33.0" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmmirror.com/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.8.1", + "resolved": "https://registry.npmmirror.com/devalue/-/devalue-5.8.1.tgz", + "integrity": "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.21.6", + "resolved": "https://registry.npmmirror.com/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/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esrap": { + "version": "2.2.13", + "resolved": "https://registry.npmmirror.com/esrap/-/esrap-2.2.13.tgz", + "integrity": "sha512-m8jH5hZgJE2RRUK/jjkGPcJEDAV+dYnZYFkosQaPTcE+Yw4xynXHOo6FUdwaWBtdR3b1MMa7wEDTSHeR2VWsGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "peerDependencies": { + "@typescript-eslint/types": "^8.2.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/types": { + "optional": true + } + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmmirror.com/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/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmmirror.com/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/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/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmmirror.com/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/obug": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/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/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.16", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/proxy-compare/-/proxy-compare-3.0.1.tgz", + "integrity": "sha512-V9plBAt3qjMlS1+nC8771KNf6oJ12gExvaxnNzN/9yVRLdTv/lc+oJlnSzrdYDAvBfTStPCoiaCOTmTs0adv7Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/rolldown": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.139.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" + } + }, + "node_modules/runed": { + "version": "0.35.1", + "resolved": "https://registry.npmmirror.com/runed/-/runed-0.35.1.tgz", + "integrity": "sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "esm-env": "^1.0.0", + "lz-string": "^1.5.0" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.21.0", + "svelte": "^5.7.0" + }, + "peerDependenciesMeta": { + "@sveltejs/kit": { + "optional": true + } + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/set-cookie-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/set-cookie-parser/-/set-cookie-parser-3.1.2.tgz", + "integrity": "sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==", + "dev": true, + "license": "MIT" + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmmirror.com/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/svelte": { + "version": "5.56.4", + "resolved": "https://registry.npmmirror.com/svelte/-/svelte-5.56.4.tgz", + "integrity": "sha512-/d0QHehmRuJW8gVz395MTkPcPozxzdjBMBE8oEYGz8O3b9KTMzzQ9ZHJQLuFKOHOPQbU6kx/X4iid/EBBzH7iw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.10", + "@types/estree": "^1.0.5", + "@types/trusted-types": "^2.0.7", + "acorn": "^8.12.1", + "aria-query": "5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "devalue": "^5.8.1", + "esm-env": "^1.2.1", + "esrap": "^2.2.12", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/svelte-check": { + "version": "4.7.2", + "resolved": "https://registry.npmmirror.com/svelte-check/-/svelte-check-4.7.2.tgz", + "integrity": "sha512-GoS4XJdGswlq0rIT1vtFLzJY1bvHtY37McY9H9Gkm1Ggw/ICdZYn8J/Z8Yi0BEL0i3R4+jtaWVePjyppMlij/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "@sveltejs/load-config": "^0.2.0", + "chokidar": "^4.0.1", + "fdir": "^6.2.0", + "picocolors": "^1.0.0", + "sade": "^1.7.4" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": ">=5.0.0" + } + }, + "node_modules/svelte-toolbelt": { + "version": "0.10.6", + "resolved": "https://registry.npmmirror.com/svelte-toolbelt/-/svelte-toolbelt-0.10.6.tgz", + "integrity": "sha512-YWuX+RE+CnWYx09yseAe4ZVMM7e7GRFZM6OYWpBKOb++s+SQ8RBIMMe+Bs/CznBMc0QPLjr+vDBxTAkozXsFXQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/huntabyte" + ], + "dependencies": { + "clsx": "^2.1.1", + "runed": "^0.35.1", + "style-to-object": "^1.0.8" + }, + "engines": { + "node": ">=18", + "pnpm": ">=8.7.0" + }, + "peerDependencies": { + "svelte": "^5.30.2" + } + }, + "node_modules/tabbable": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/tabbable/-/tabbable-6.5.0.tgz", + "integrity": "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tailwindcss": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-4.3.2.tgz", + "integrity": "sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/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/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "8.1.4", + "resolved": "https://registry.npmmirror.com/vite/-/vite-8.1.4.tgz", + "integrity": "sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.16", + "rolldown": "~1.1.4", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.3", + "resolved": "https://registry.npmmirror.com/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/zimmerframe": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/zimmerframe/-/zimmerframe-1.1.4.tgz", + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/hub/admin-web/package.json b/hub/admin-web/package.json new file mode 100644 index 0000000..1432cdf --- /dev/null +++ b/hub/admin-web/package.json @@ -0,0 +1,29 @@ +{ + "name": "admin-web", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" + }, + "devDependencies": { + "@skeletonlabs/skeleton": "^4.15.2", + "@skeletonlabs/skeleton-svelte": "^4.15.2", + "@sveltejs/adapter-auto": "^7.0.1", + "@sveltejs/adapter-static": "^3.0.10", + "@sveltejs/kit": "^2.63.0", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tailwindcss/vite": "^4.3.2", + "bits-ui": "^2.18.1", + "svelte": "^5.56.1", + "svelte-check": "^4.6.0", + "tailwindcss": "^4.3.2", + "typescript": "^6.0.3", + "vite": "^8.0.16" + } +} diff --git a/hub/admin-web/src/app.d.ts b/hub/admin-web/src/app.d.ts new file mode 100644 index 0000000..da08e6d --- /dev/null +++ b/hub/admin-web/src/app.d.ts @@ -0,0 +1,13 @@ +// See https://svelte.dev/docs/kit/types#app.d.ts +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/hub/admin-web/src/app.html b/hub/admin-web/src/app.html new file mode 100644 index 0000000..2e18223 --- /dev/null +++ b/hub/admin-web/src/app.html @@ -0,0 +1,21 @@ + + + + + + + + + + + + CPH Admin + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/hub/admin-web/src/lib/api.ts b/hub/admin-web/src/lib/api.ts new file mode 100644 index 0000000..076f7d0 --- /dev/null +++ b/hub/admin-web/src/lib/api.ts @@ -0,0 +1,291 @@ +/** + * Thin API client for the org admin backend. Same-origin cookie auth. + */ + +export class ApiError extends Error { + code: string; + status: number; + constructor(code: string, message: string, status: number) { + super(message); + this.name = 'ApiError'; + this.code = code; + this.status = status; + } +} + +async function request(method: string, url: string, body?: unknown): Promise { + const init: RequestInit = { + method, + credentials: 'same-origin', + headers: body !== undefined ? { 'content-type': 'application/json' } : undefined, + body: body !== undefined ? JSON.stringify(body) : undefined + }; + const res = await fetch(url, init); + const text = await res.text(); + let data: unknown = null; + if (text !== '') { + try { + data = JSON.parse(text); + } catch { + data = text; + } + } + if (!res.ok) { + const err = (data as { error?: { code?: string; message?: string } } | null)?.error; + throw new ApiError(err?.code ?? 'http_error', err?.message ?? `HTTP ${res.status}`, res.status); + } + return data; +} + +const get = (u: string) => request('GET', u); +const post = (u: string, b?: unknown) => request('POST', u, b); +const put = (u: string, b?: unknown) => request('PUT', u, b); +const patch = (u: string, b?: unknown) => request('PATCH', u, b); +const del = (u: string) => request('DELETE', u); + +const orgBase = (slug: string) => `/api/org/${encodeURIComponent(slug)}`; + +// --- Types --- + +export interface OrgMembership { + id: string; + slug: string; + name: string; + status: string; + role: 'OWNER' | 'ADMIN' | 'MEMBER'; +} + +export interface MeResponse { + user: { + id: string; + feishuOpenId: string; + displayName: string; + avatarUrl: string | null; + }; + organizations: OrgMembership[]; +} + +export interface OrgMember { + userId: string; + feishuOpenId: string; + displayName: string; + avatarUrl: string | null; + role: 'OWNER' | 'ADMIN' | 'MEMBER'; + createdAt: string; +} + +export interface TeamRow { + id: string; + slug: string; + name: string; + description: string | null; + memberCount: number; + createdAt: string; +} + +export interface TeamMemberRow { + userId: string; + feishuOpenId: string; + displayName: string; + createdAt: string; +} + +export interface ExplorerFolder { + id: string; + name: string; + parentId: string | null; + sortKey: string; + projectCount: number; + childFolderCount: number; +} + +export interface ExplorerProject { + id: string; + name: string; + folderId: string | null; + createdAt: string; + binding: { chatId: string; createdAt: string } | null; +} + +export interface ExplorerData { + folders: ExplorerFolder[]; + projects: ExplorerProject[]; +} + +export interface ProjectDetail { + id: string; + name: string; + folderId: string | null; + folder: { id: string; name: string } | null; + workspaceDir: string; + createdAt: string; + archivedAt: string | null; + createdBy: { id: string; displayName: string; feishuOpenId: string } | null; + binding: { chatId: string; createdAt: string } | null; +} + +export interface TeamAccessEntry { + grantId: string; + projectId: string; + organizationId: string; + teamId: string; + teamSlug: string; + teamName: string; + role: 'READ' | 'EDIT' | 'MANAGE'; +} + +export interface SessionSummary { + id: string; + provider: string; + roleId: string; + model: string; + title: string | null; + runCount: number; + createdAt: string; + updatedAt: string; +} + +export interface OrgModelRow { + id: string; + modelId: string; + label: string; + toolCapable: boolean; + sortKey: string; + createdAt: string; + updatedAt: string; +} + +export interface OrgRoleRow { + id: string; + roleId: string; + label: string; + defaultModelId: string | null; + systemPrompt: string | null; + tools: string[] | null; + createdAt: string; + updatedAt: string; +} + +export interface ProviderConnection { + organizationId: string; + providerId: string; + mode: 'BYOK' | 'PLATFORM_MANAGED'; + baseUrl: string | null; + hasAuthToken: boolean; + createdAt: string; + updatedAt: string; +} + +export interface UsageTotals { + runCount: number; + runsWithCost: number; + runsWithoutCost: number; + inputTokens: number; + outputTokens: number; + costUsd: number | null; +} + +export interface ProjectUsageRow extends UsageTotals { + projectId: string; + projectName: string; + folderId: string | null; +} + +export interface UsageReport { + from: string | null; + to: string | null; + projects: ProjectUsageRow[]; + totals: UsageTotals; +} + +// --- API --- + +export const api = { + me: () => get('/api/me') as Promise, + logout: () => post('/auth/logout'), + + org: (slug: string) => get(orgBase(slug)) as Promise<{ organization: { id: string; slug: string; name: string; status: string }; actorRole: string }>, + settings: (slug: string) => get(`${orgBase(slug)}/settings`) as Promise<{ membersCanCreateProjects: boolean }>, + setSettings: (slug: string, body: { membersCanCreateProjects: boolean }) => + patch(`${orgBase(slug)}/settings`, body) as Promise<{ membersCanCreateProjects: boolean }>, + + members: (slug: string) => get(`${orgBase(slug)}/members`) as Promise<{ members: OrgMember[] }>, + addMember: (slug: string, body: { feishuOpenId: string; displayName?: string; role: string }) => + post(`${orgBase(slug)}/members`, body) as Promise, + setMemberRole: (slug: string, userId: string, role: string) => + patch(`${orgBase(slug)}/members/${userId}`, { role }), + revokeMember: (slug: string, userId: string) => + post(`${orgBase(slug)}/members/${userId}/revoke`), + + teams: (slug: string) => get(`${orgBase(slug)}/teams`) as Promise<{ teams: TeamRow[] }>, + createTeam: (slug: string, body: { slug: string; name: string; description?: string }) => + post(`${orgBase(slug)}/teams`, body) as Promise, + updateTeam: (slug: string, teamId: string, body: { name?: string; description?: string | null }) => + patch(`${orgBase(slug)}/teams/${teamId}`, body) as Promise, + archiveTeam: (slug: string, teamId: string) => + post(`${orgBase(slug)}/teams/${teamId}/archive`), + teamMembers: (slug: string, teamId: string) => + get(`${orgBase(slug)}/teams/${teamId}/members`) as Promise<{ members: TeamMemberRow[] }>, + addTeamMember: (slug: string, teamId: string, body: { userId?: string; feishuOpenId?: string }) => + post(`${orgBase(slug)}/teams/${teamId}/members`, body) as Promise, + revokeTeamMember: (slug: string, teamId: string, userId: string) => + post(`${orgBase(slug)}/teams/${teamId}/members/${userId}/revoke`), + + explorer: (slug: string) => get(`${orgBase(slug)}/explorer`) as Promise, + createFolder: (slug: string, body: { name: string; parentId?: string; sortKey?: string }) => + post(`${orgBase(slug)}/folders`, body) as Promise<{ id: string; name: string; parentId: string | null; sortKey: string }>, + renameFolder: (slug: string, folderId: string, body: { name?: string; sortKey?: string; parentId?: string | null }) => + patch(`${orgBase(slug)}/folders/${folderId}`, body) as Promise<{ id: string; name: string; parentId: string | null; sortKey: string }>, + archiveFolder: (slug: string, folderId: string) => + post(`${orgBase(slug)}/folders/${folderId}/archive`) as Promise<{ archived: true; folderId: string }>, + createProject: (slug: string, body: { name: string; folderId?: string }) => + post(`${orgBase(slug)}/projects`, body) as Promise<{ id: string; name: string }>, + project: (slug: string, projectId: string) => + get(`${orgBase(slug)}/projects/${projectId}`) as Promise, + renameProject: (slug: string, projectId: string, name: string) => + patch(`${orgBase(slug)}/projects/${projectId}`, { name }), + moveProject: (slug: string, projectId: string, folderId: string | null) => + patch(`${orgBase(slug)}/projects/${projectId}/folder`, { folderId }), + archiveProject: (slug: string, projectId: string) => + post(`${orgBase(slug)}/projects/${projectId}/archive`), + archiveBinding: (slug: string, projectId: string) => + post(`${orgBase(slug)}/projects/${projectId}/binding/archive`), + + teamAccess: (slug: string, projectId: string) => + get(`${orgBase(slug)}/projects/${projectId}/team-access`) as Promise<{ access: TeamAccessEntry[] }>, + grantTeamAccess: (slug: string, projectId: string, body: { teamId?: string; teamSlug?: string; role: string }) => + put(`${orgBase(slug)}/projects/${projectId}/team-access`, body) as Promise, + revokeTeamAccess: (slug: string, projectId: string, teamId: string) => + del(`${orgBase(slug)}/projects/${projectId}/team-access/${teamId}`), + + sessions: (slug: string, projectId: string, limit?: number) => + get(`${orgBase(slug)}/projects/${projectId}/sessions${limit !== undefined ? `?limit=${limit}` : ''}`) as Promise<{ sessions: SessionSummary[] }>, + usage: (slug: string, params?: { from?: string; to?: string; folderId?: string }) => { + const q = new URLSearchParams(); + if (params?.from) q.set('from', params.from); + if (params?.to) q.set('to', params.to); + if (params?.folderId) q.set('folderId', params.folderId); + const qs = q.toString(); + return get(`${orgBase(slug)}/usage${qs ? `?${qs}` : ''}`) as Promise; + }, + + models: (slug: string) => get(`${orgBase(slug)}/models`) as Promise<{ models: OrgModelRow[] }>, + createModel: (slug: string, body: { modelId: string; label: string; toolCapable?: boolean; sortKey?: string }) => + post(`${orgBase(slug)}/models`, body) as Promise, + updateModel: (slug: string, id: string, body: { label?: string; toolCapable?: boolean; sortKey?: string; modelId?: string }) => + patch(`${orgBase(slug)}/models/${id}`, body) as Promise, + deleteModel: (slug: string, id: string) => + del(`${orgBase(slug)}/models/${id}`), + + roles: (slug: string) => get(`${orgBase(slug)}/roles`) as Promise<{ roles: OrgRoleRow[] }>, + createRole: (slug: string, body: { roleId: string; label: string; defaultModelId?: string | null; systemPrompt?: string | null; tools?: string[] | null }) => + post(`${orgBase(slug)}/roles`, body) as Promise, + updateRole: (slug: string, id: string, body: { label?: string; defaultModelId?: string | null; systemPrompt?: string | null; tools?: string[] | null; roleId?: string }) => + patch(`${orgBase(slug)}/roles/${id}`, body) as Promise, + deleteRole: (slug: string, id: string) => + del(`${orgBase(slug)}/roles/${id}`), + + provider: (slug: string) => get(`${orgBase(slug)}/provider-connection`) as Promise, + setProvider: (slug: string, body: { mode: string; providerId?: string; baseUrl?: string | null; authToken?: string | null }) => + put(`${orgBase(slug)}/provider-connection`, body) as Promise +}; diff --git a/hub/admin-web/src/lib/assets/favicon.svg b/hub/admin-web/src/lib/assets/favicon.svg new file mode 100644 index 0000000..cc5dc66 --- /dev/null +++ b/hub/admin-web/src/lib/assets/favicon.svg @@ -0,0 +1 @@ +svelte-logo \ No newline at end of file diff --git a/hub/admin-web/src/lib/components/EmptyState.svelte b/hub/admin-web/src/lib/components/EmptyState.svelte new file mode 100644 index 0000000..d2f062d --- /dev/null +++ b/hub/admin-web/src/lib/components/EmptyState.svelte @@ -0,0 +1,30 @@ + + +
+
+ + + +
+

{title}

+ {#if description} +

{description}

+ {/if} + {#if action} +
+ {@render action()} +
+ {/if} +
diff --git a/hub/admin-web/src/lib/components/ErrorBanner.svelte b/hub/admin-web/src/lib/components/ErrorBanner.svelte new file mode 100644 index 0000000..b33eecd --- /dev/null +++ b/hub/admin-web/src/lib/components/ErrorBanner.svelte @@ -0,0 +1,26 @@ + + +
+ + + +
+

请求失败

+

{message}

+
+ {#if onretry} + + {/if} +
diff --git a/hub/admin-web/src/lib/components/FolderNode.svelte b/hub/admin-web/src/lib/components/FolderNode.svelte new file mode 100644 index 0000000..b3b5fc1 --- /dev/null +++ b/hub/admin-web/src/lib/components/FolderNode.svelte @@ -0,0 +1,48 @@ + + +
+ + {#if open} +
+ +
+ {/if} +
diff --git a/hub/admin-web/src/lib/components/FolderTree.svelte b/hub/admin-web/src/lib/components/FolderTree.svelte new file mode 100644 index 0000000..d56d3ed --- /dev/null +++ b/hub/admin-web/src/lib/components/FolderTree.svelte @@ -0,0 +1,49 @@ + + +
+ {#each childProjects as p (p.id)} + + + + + {p.name} + {#if p.binding} + 已绑定 + {/if} + + + {/each} + + {#each childFolders as f (f.id)} + + {/each} +
diff --git a/hub/admin-web/src/lib/components/Icon.svelte b/hub/admin-web/src/lib/components/Icon.svelte new file mode 100644 index 0000000..9241400 --- /dev/null +++ b/hub/admin-web/src/lib/components/Icon.svelte @@ -0,0 +1,115 @@ + + +{#if name === 'overview'} + + + +{:else if name === 'members'} + + + +{:else if name === 'teams'} + + + +{:else if name === 'projects'} + + + +{:else if name === 'models'} + + + +{:else if name === 'roles'} + + + +{:else if name === 'provider'} + + + + +{:else if name === 'menu'} + + + +{:else if name === 'logout'} + + + +{:else if name === 'org'} + + + +{:else if name === 'chevron'} + + + +{:else if name === 'folder'} + + + +{:else if name === 'file'} + + + +{:else if name === 'check'} + + + +{/if} diff --git a/hub/admin-web/src/lib/components/LoadingState.svelte b/hub/admin-web/src/lib/components/LoadingState.svelte new file mode 100644 index 0000000..7f0d2f7 --- /dev/null +++ b/hub/admin-web/src/lib/components/LoadingState.svelte @@ -0,0 +1,15 @@ + + +
+ + + + +

{label}

+
diff --git a/hub/admin-web/src/lib/components/Modal.svelte b/hub/admin-web/src/lib/components/Modal.svelte new file mode 100644 index 0000000..a8066ac --- /dev/null +++ b/hub/admin-web/src/lib/components/Modal.svelte @@ -0,0 +1,44 @@ + + +{#if open} + +{/if} diff --git a/hub/admin-web/src/lib/components/PageHeader.svelte b/hub/admin-web/src/lib/components/PageHeader.svelte new file mode 100644 index 0000000..b8ea38e --- /dev/null +++ b/hub/admin-web/src/lib/components/PageHeader.svelte @@ -0,0 +1,27 @@ + + +
+
+

{title}

+ {#if description} +

{description}

+ {/if} +
+ {#if actions} +
+ {@render actions()} +
+ {/if} +
diff --git a/hub/admin-web/src/lib/components/RoleCard.svelte b/hub/admin-web/src/lib/components/RoleCard.svelte new file mode 100644 index 0000000..8667bf9 --- /dev/null +++ b/hub/admin-web/src/lib/components/RoleCard.svelte @@ -0,0 +1,148 @@ + + +
+
+ /{roleId || r.roleId} + {label || r.label} +
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ 工具白名单 + +
+ {#each Object.entries(groupedTools) as [group, tools]} +
+

{group}

+
+ {#each tools as t} + + {/each} +
+
+ {/each} +
+
+ +
+ + +
+ +
+ 更新于 {fmtDate(r.updatedAt)} +
+ + +
+
diff --git a/hub/admin-web/src/lib/components/StatCard.svelte b/hub/admin-web/src/lib/components/StatCard.svelte new file mode 100644 index 0000000..7674cfa --- /dev/null +++ b/hub/admin-web/src/lib/components/StatCard.svelte @@ -0,0 +1,19 @@ + + +
+
{label}
+
{value}
+ {#if hint} +
{hint}
+ {/if} +
diff --git a/hub/admin-web/src/lib/components/ToastHost.svelte b/hub/admin-web/src/lib/components/ToastHost.svelte new file mode 100644 index 0000000..a9d0ff8 --- /dev/null +++ b/hub/admin-web/src/lib/components/ToastHost.svelte @@ -0,0 +1,24 @@ + + +
+ {#each $toasts as t (t.id)} +
+

{t.message}

+ +
+ {/each} +
diff --git a/hub/admin-web/src/lib/constants.ts b/hub/admin-web/src/lib/constants.ts new file mode 100644 index 0000000..e17e489 --- /dev/null +++ b/hub/admin-web/src/lib/constants.ts @@ -0,0 +1,22 @@ +export interface ToolOption { + id: string; + label: string; + group: string; +} + +export const TOOL_OPTIONS: ToolOption[] = [ + { id: 'read_file', label: '读取文件 (Read)', group: '文件' }, + { id: 'write_file', label: '写入文件 (Write)', group: '文件' }, + { id: 'list_files', label: '列目录 (Glob)', group: '文件' }, + { id: 'search_files', label: '搜索 (Grep)', group: '文件' }, + { id: 'bash', label: 'Bash 命令', group: 'Shell' }, + { id: 'cph_check', label: 'cph check', group: 'CPH' }, + { id: 'cph_build', label: 'cph build', group: 'CPH' }, + { id: 'send_file', label: '发送文件 (飞书)', group: '飞书 MCP' }, + { id: 'feishu_read_context', label: '读飞书上下文', group: '飞书 MCP' }, + { id: 'feishu_download_resource', label: '下载飞书资源', group: '飞书 MCP' }, + { id: 'request_approval', label: '请求审批', group: '飞书 MCP' } +]; + +export const ORG_ROLES = ['OWNER', 'ADMIN', 'MEMBER'] as const; +export const PERMISSION_ROLES = ['READ', 'EDIT', 'MANAGE'] as const; diff --git a/hub/admin-web/src/lib/format.ts b/hub/admin-web/src/lib/format.ts new file mode 100644 index 0000000..2c6ff9c --- /dev/null +++ b/hub/admin-web/src/lib/format.ts @@ -0,0 +1,28 @@ +export function fmtDate(iso: string): string { + if (!iso) return '—'; + const d = new Date(iso); + if (Number.isNaN(d.getTime())) return iso; + return d.toLocaleString(undefined, { + year: 'numeric', + month: 'short', + day: '2-digit', + hour: '2-digit', + minute: '2-digit' + }); +} + +export function fmtDateOnly(iso: string): string { + if (!iso) return '—'; + const d = new Date(iso); + if (Number.isNaN(d.getTime())) return iso; + return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: '2-digit' }); +} + +export function fmtCost(usd: number | null): string { + if (usd === null) return '—'; + return `$${Number(usd).toFixed(4)}`; +} + +export function fmtNum(n: number): string { + return n.toLocaleString(); +} diff --git a/hub/admin-web/src/lib/index.ts b/hub/admin-web/src/lib/index.ts new file mode 100644 index 0000000..856f2b6 --- /dev/null +++ b/hub/admin-web/src/lib/index.ts @@ -0,0 +1 @@ +// place files you want to import through the `$lib` alias in this folder. diff --git a/hub/admin-web/src/lib/session.ts b/hub/admin-web/src/lib/session.ts new file mode 100644 index 0000000..8110a84 --- /dev/null +++ b/hub/admin-web/src/lib/session.ts @@ -0,0 +1,43 @@ +import { writable } from 'svelte/store'; +import { api, type MeResponse } from './api'; + +interface SessionState { + loading: boolean; + me: MeResponse | null; + error: string | null; +} + +export const session = writable({ + loading: true, + me: null, + error: null +}); + +export async function loadSession(): Promise { + session.update((s) => ({ ...s, loading: true, error: null })); + try { + const me = await api.me(); + session.set({ loading: false, me, error: null }); + } catch (err) { + const status = (err as { status?: number }).status; + if (status === 401) { + redirectToLogin(); + return; + } + session.set({ + loading: false, + me: null, + error: err instanceof Error ? err.message : String(err) + }); + } +} + +export function redirectToLogin(): void { + const ret = encodeURIComponent(window.location.pathname + window.location.hash); + window.location.href = `/auth/feishu?returnTo=${ret}`; +} + +export async function logout(): Promise { + await api.logout(); + redirectToLogin(); +} diff --git a/hub/admin-web/src/lib/toast.ts b/hub/admin-web/src/lib/toast.ts new file mode 100644 index 0000000..ad89770 --- /dev/null +++ b/hub/admin-web/src/lib/toast.ts @@ -0,0 +1,34 @@ +import { writable } from 'svelte/store'; + +export type ToastKind = 'info' | 'success' | 'error'; + +export interface ToastItem { + id: number; + message: string; + kind: ToastKind; +} + +let seq = 0; +export const toasts = writable([]); + +export function pushToast(message: string, kind: ToastKind = 'info', ms = 3200): void { + const id = ++seq; + toasts.update((list) => [...list, { id, message, kind }]); + if (ms > 0) { + setTimeout(() => { + toasts.update((list) => list.filter((t) => t.id !== id)); + }, ms); + } +} + +export function dismissToast(id: number): void { + toasts.update((list) => list.filter((t) => t.id !== id)); +} + +export function toastSuccess(message: string): void { + pushToast(message, 'success'); +} + +export function toastError(message: string): void { + pushToast(message, 'error', 5000); +} diff --git a/hub/admin-web/src/routes/+layout.svelte b/hub/admin-web/src/routes/+layout.svelte new file mode 100644 index 0000000..128fa40 --- /dev/null +++ b/hub/admin-web/src/routes/+layout.svelte @@ -0,0 +1,321 @@ + + + + +{#if $session.loading || redirecting} +
+
+ + + + +

{redirecting ? '正在进入组织…' : '正在加载会话…'}

+
+
+{:else if $session.error} +
+
+
!
+

无法连接到后端

+

{$session.error}

+ +
+
+{:else if !$session.me} +
+
+
+ CPH +
+

Curriculum Project Hub

+

登录以管理组织、项目、团队与模型供应。

+ +
+
+{:else if currentOrg() && isAdmin(currentOrg()!)} + {@const org = currentOrg()!} + {@const me = $session.me!} +
+ {#if mobileNavOpen} + + {/if} + + + +
+
+ +
+
+ {org.name} + / + {pageTitle()} +
+
+ +
+
+
+ {@render children()} +
+
+
+
+{:else if currentOrg() && !isAdmin(currentOrg()!)} + {@const denied = currentOrg()!} +
+
+

无权访问管理后台

+

+ 组织 {denied.name}(/{denied.slug})中你的角色是 + {denied.role}, + 需要 OWNERADMIN。 +

+ {#if memberships().length > 1} + + + {/if} + +
+
+{:else if memberships().length > 0} + {@const denied = pickHomeOrg()!} +
+
+ {#if adminOrgs().length === 0} +

无权访问管理后台

+

+ 你加入了 {denied.name},角色是 + {denied.role}。仅 OWNER/ADMIN 可进入后台。 +

+ {:else} +

正在跳转…

+

+ 即将进入 {adminOrgs()[0].name} + (/{adminOrgs()[0].slug})。 +

+ 立即进入 + {/if} + +
+
+{:else} +
+
+

未加入组织

+

+ 飞书账号已登录,但 /api/me 的 + organizations 为空。 +

+

+ open_id: + {$session.me!.user.feishuOpenId} +

+ +
+
+{/if} diff --git a/hub/admin-web/src/routes/+page.svelte b/hub/admin-web/src/routes/+page.svelte new file mode 100644 index 0000000..c60350a --- /dev/null +++ b/hub/admin-web/src/routes/+page.svelte @@ -0,0 +1,43 @@ + + +
+
+ + + + +

正在进入工作台…

+
+
diff --git a/hub/admin-web/src/routes/admin/org/[slug]/+page.svelte b/hub/admin-web/src/routes/admin/org/[slug]/+page.svelte new file mode 100644 index 0000000..907276e --- /dev/null +++ b/hub/admin-web/src/routes/admin/org/[slug]/+page.svelte @@ -0,0 +1,158 @@ + + +{#if loading} + +{:else if error} + +{:else if org && settings && usage} + + +
+
+

组织信息

+
+
+
Slug
+
/{org.slug}
+
+
+
状态
+
{org.status}
+
+
+
你的角色
+
{org.role}
+
+
+
+ +
+

项目自助创建策略

+

+ 开启后,普通老师可在飞书群自助创建项目;关闭后仅 OWNER / ADMIN 可建。 +

+ +
+
+ +
+
+

用量概览

+

全组织 Agent 运行汇总

+
+
+ +
+ + + + + + +
+ +
+
+

按项目用量

+
+ {#if usage.projects.length === 0} +
+

暂无项目用量数据

+
+ {:else} +
+ + + + + + + + + + + {#each usage.projects as p} + + + + + + + {/each} + +
项目运行in / out tokens成本
{p.projectName}{fmtNum(p.runCount)}{fmtNum(p.inputTokens)} / {fmtNum(p.outputTokens)}{fmtCost(p.costUsd)}
+
+ {/if} +
+{:else} +
+

组织数据不可用

+
+{/if} diff --git a/hub/admin-web/src/routes/admin/org/[slug]/members/+page.svelte b/hub/admin-web/src/routes/admin/org/[slug]/members/+page.svelte new file mode 100644 index 0000000..c79861d --- /dev/null +++ b/hub/admin-web/src/routes/admin/org/[slug]/members/+page.svelte @@ -0,0 +1,168 @@ + + + + +{#if loading} + +{:else if error} + +{:else} +
+

添加成员

+
+ + + + +
+
+ +
+
+

成员列表

+ {members.length} +
+ {#if members.length === 0} + + {:else} +
+ + + + + + + + + + + + {#each members as m} + + + + + + + + {/each} + +
用户open_id组织角色加入时间
+
+ {#if m.avatarUrl} + + {:else} +
+ {m.displayName.slice(0, 1)} +
+ {/if} + {m.displayName} +
+
{m.feishuOpenId} + + {fmtDate(m.createdAt)} + +
+
+ {/if} +

+ 组织角色控制后台访问;项目级权限由「项目」页团队授权(READ / EDIT / MANAGE)决定。 +

+
+{/if} diff --git a/hub/admin-web/src/routes/admin/org/[slug]/models/+page.svelte b/hub/admin-web/src/routes/admin/org/[slug]/models/+page.svelte new file mode 100644 index 0000000..f14fb1d --- /dev/null +++ b/hub/admin-web/src/routes/admin/org/[slug]/models/+page.svelte @@ -0,0 +1,170 @@ + + + + +{#if loading} + +{:else if error} + +{:else} +
+

添加模型

+
+ + + + +
+
+ +
+
+

已启用模型

+ {models.length} +
+ {#if models.length === 0} + + {:else} +
+ + + + + + + + + + + + + {#each models as m (m.id)} + + + + + + + + + {/each} + +
labelmodelId工具能力排序创建
+ save(m, { label: (e.target as HTMLInputElement).value })} + /> + + save(m, { modelId: (e.target as HTMLInputElement).value })} + /> + + save(m, { toolCapable: (e.target as HTMLInputElement).checked })} + /> + + save(m, { sortKey: (e.target as HTMLInputElement).value })} + /> + {fmtDateOnly(m.createdAt)} + +
+
+ {/if} +
+{/if} diff --git a/hub/admin-web/src/routes/admin/org/[slug]/projects/+page.svelte b/hub/admin-web/src/routes/admin/org/[slug]/projects/+page.svelte new file mode 100644 index 0000000..0e22592 --- /dev/null +++ b/hub/admin-web/src/routes/admin/org/[slug]/projects/+page.svelte @@ -0,0 +1,158 @@ + + + + {#snippet actions()} + + + {/snippet} + + +{#if loading} + +{:else if error} + +{:else if data} +
+ {#if data.projects.filter((p) => !p.folderId).length === 0 && data.folders.filter((f) => !f.parentId).length === 0} + + {:else} + + {/if} +
+{/if} + + + + { + if (e.key === 'Enter') createFolder(); + }} + /> + {#if data && data.folders.length > 0} + + + {/if} +
+ + +
+
+ + + + { + if (e.key === 'Enter') createProject(); + }} + /> + {#if data && data.folders.length > 0} + + + {/if} +
+ + +
+
diff --git a/hub/admin-web/src/routes/admin/org/[slug]/projects/[projectId]/+page.svelte b/hub/admin-web/src/routes/admin/org/[slug]/projects/[projectId]/+page.svelte new file mode 100644 index 0000000..2f3e2a4 --- /dev/null +++ b/hub/admin-web/src/routes/admin/org/[slug]/projects/[projectId]/+page.svelte @@ -0,0 +1,274 @@ + + +{#if loading} + +{:else if error} + +{:else if proj} + + + {@const detail = proj} + + {#snippet actions()} + + {#if detail.binding} + + {/if} + + {/snippet} + + +
+
+
+
Workspace
+
{proj.workspaceDir}
+
+
+
创建者
+
+ {proj.createdBy ? `${proj.createdBy.displayName} (${proj.createdBy.feishuOpenId})` : '—'} +
+
+
+
文件夹
+
{proj.folder ? proj.folder.name : '(根)'}
+
+
+
飞书群
+
+ {#if proj.binding} + 已绑定 + chat {proj.binding.chatId} + · {fmtDate(proj.binding.createdAt)} + {:else} + 未绑定 + {/if} +
+
+
+
创建时间
+
{fmtDate(proj.createdAt)}
+
+
+ + {#if explorer} +
+ 移动到文件夹 + + +
+ {/if} +
+ +
+

团队授权

+

通过团队授权项目访问。一项目可授多团队,一团队可访问多项目。

+ +
+ + + +
+ + {#if access.length === 0} + + {:else} + + + + + + + + + + + {#each access as g} + + + + + + + {/each} + +
团队slug角色
{g.teamName}/{g.teamSlug}{g.role} + +
+ {/if} +
+ +
+
+

Agent 会话

+
+ {#if sessions.length === 0} + + {:else} + + + + + + + + + + + {#each sessions as s} + + + + + + + {/each} + +
provider / rolemodel运行更新
{s.provider} / {s.roleId}{s.model}{s.runCount}{fmtDate(s.updatedAt)}
+ {/if} +
+{/if} diff --git a/hub/admin-web/src/routes/admin/org/[slug]/provider/+page.svelte b/hub/admin-web/src/routes/admin/org/[slug]/provider/+page.svelte new file mode 100644 index 0000000..15830ec --- /dev/null +++ b/hub/admin-web/src/routes/admin/org/[slug]/provider/+page.svelte @@ -0,0 +1,145 @@ + + + + +{#if loading} + +{:else if error} + +{:else if conn} +
+
+
+ + +
+ +
+ + +
+ + {#if mode === 'BYOK'} +
+ + +
+
+ + + {#if conn.hasAuthToken} +

已存储密钥;输入新值替换,留空不变。

+ {/if} +
+ {/if} +
+ +
+ {conn.updatedAt ? '更新于 ' + fmtDate(conn.updatedAt) : '尚未配置'} +
+ +
+
+ +
+

运行时解析

+
    +
  • BYOK:agent 使用本连接 baseUrl + authToken。
  • +
  • + + + PLATFORM_MANAGED / 未配置:回退进程环境变量(ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN)。真正 per-org + 平台托管凭据是后续 secret-control-plane(OPEN)。 + +
  • +
  • + + 模型与角色在对应页面管理;空时回退环境默认。 +
  • +
+
+{/if} diff --git a/hub/admin-web/src/routes/admin/org/[slug]/roles/+page.svelte b/hub/admin-web/src/routes/admin/org/[slug]/roles/+page.svelte new file mode 100644 index 0000000..2c07a4e --- /dev/null +++ b/hub/admin-web/src/routes/admin/org/[slug]/roles/+page.svelte @@ -0,0 +1,106 @@ + + + + +{#if loading} + +{:else if error} + +{:else} +
+

新建角色

+
+ + + +
+
+ + {#if roles.length === 0} +
+ +
+ {:else} +
+ {#each roles as r (r.id)} + remove(r)} + onupdated={(updated: OrgRoleRow) => { + roles = roles.map((x) => (x.id === r.id ? updated : x)); + }} + /> + {/each} +
+ {/if} +{/if} diff --git a/hub/admin-web/src/routes/admin/org/[slug]/teams/+page.svelte b/hub/admin-web/src/routes/admin/org/[slug]/teams/+page.svelte new file mode 100644 index 0000000..13dedd6 --- /dev/null +++ b/hub/admin-web/src/routes/admin/org/[slug]/teams/+page.svelte @@ -0,0 +1,215 @@ + + + + +{#if loading} + +{:else if error} + +{:else} +
+

新建团队

+
+ + + + +
+
+ + {#if teams.length === 0} +
+ +
+ {:else} +
+ {#each teams as t (t.id)} +
+
+ {t.name} + /{t.slug} + {t.memberCount} 成员 +
+ + +
+
+ {#if t.description} +

{t.description}

+ {/if} +

创建于 {fmtDate(t.createdAt)}

+ + {#if expandedId === t.id} +
+ {#if loadingMembers} +

加载中…

+ {:else} +
+ { + if (e.key === 'Enter') addMember(t); + }} + /> + +
+ {#if teamMembers.length === 0} +

团队暂无成员

+ {:else} + + + + + + + + + + + {#each teamMembers as m} + + + + + + + {/each} + +
成员open_id加入时间
{m.displayName}{m.feishuOpenId}{fmtDate(m.createdAt)} + +
+ {/if} + {/if} +
+ {/if} +
+ {/each} +
+ {/if} +

归档团队会同步撤销其活跃的项目授权。

+{/if} diff --git a/hub/admin-web/src/routes/app.css b/hub/admin-web/src/routes/app.css new file mode 100644 index 0000000..2a31663 --- /dev/null +++ b/hub/admin-web/src/routes/app.css @@ -0,0 +1,431 @@ +@import 'tailwindcss'; + +@import '@skeletonlabs/skeleton'; +@import '@skeletonlabs/skeleton/themes/hamlindigo'; + +@source './**/*.{html,js,svelte,ts}'; +@source '../lib/**/*.{html,js,svelte,ts}'; + +@theme { + --font-sans: 'Inter', 'Noto Sans SC', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif; + --font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace; +} + +@layer base { + html { + height: 100%; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; + } + + body { + min-height: 100%; + font-family: var(--font-sans); + background: var(--color-surface-50); + color: var(--color-surface-900); + } + + ::selection { + background: color-mix(in oklab, var(--color-primary-500) 28%, transparent); + } + + :focus-visible { + outline: 2px solid var(--color-primary-500); + outline-offset: 2px; + } + + table.data-table { + width: 100%; + border-collapse: collapse; + font-size: 0.875rem; + } + + table.data-table thead th { + padding: 0.625rem 0.75rem; + text-align: left; + font-weight: 500; + color: var(--color-surface-500); + border-bottom: 1px solid var(--color-surface-200); + white-space: nowrap; + } + + table.data-table tbody td { + padding: 0.75rem; + border-bottom: 1px solid var(--color-surface-100); + vertical-align: middle; + } + + table.data-table tbody tr:hover td { + background: color-mix(in oklab, var(--color-surface-100) 70%, transparent); + } + + table.data-table tbody tr:last-child td { + border-bottom: none; + } +} + +@layer components { + .saas-card { + border-radius: 0.75rem; + border: 1px solid var(--color-surface-200); + background: var(--color-surface-50); + box-shadow: 0 1px 2px rgb(15 23 42 / 0.04); + } + + .saas-card-pad { + border-radius: 0.75rem; + border: 1px solid var(--color-surface-200); + background: var(--color-surface-50); + box-shadow: 0 1px 2px rgb(15 23 42 / 0.04); + padding: 1.25rem; + } + + .saas-page-title { + font-size: 1.5rem; + line-height: 2rem; + font-weight: 600; + letter-spacing: -0.02em; + color: var(--color-surface-900); + } + + .saas-section-title { + font-size: 1rem; + font-weight: 600; + color: var(--color-surface-900); + } + + .saas-muted { + font-size: 0.875rem; + color: var(--color-surface-500); + } + + .saas-label { + display: block; + margin-bottom: 0.375rem; + font-size: 0.875rem; + font-weight: 500; + color: var(--color-surface-700); + } + + .saas-help { + margin-top: 0.375rem; + font-size: 0.75rem; + color: var(--color-surface-400); + } + + .saas-toolbar { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.75rem; + margin-bottom: 1.5rem; + } + + .saas-stat { + border-radius: 0.75rem; + border: 1px solid var(--color-surface-200); + background: var(--color-surface-50); + padding: 1rem; + } + + .saas-stat-label { + font-size: 0.75rem; + font-weight: 500; + letter-spacing: 0.04em; + text-transform: uppercase; + color: var(--color-surface-500); + } + + .saas-stat-value { + margin-top: 0.25rem; + font-size: 1.5rem; + line-height: 2rem; + font-weight: 600; + font-variant-numeric: tabular-nums; + letter-spacing: -0.02em; + color: var(--color-surface-900); + } + + .saas-empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.5rem; + padding: 3rem 1rem; + text-align: center; + } + + .saas-shell { + display: flex; + height: 100vh; + overflow: hidden; + background: var(--color-surface-100); + } + + .saas-sidebar { + display: flex; + width: 16rem; + flex-shrink: 0; + flex-direction: column; + border-right: 1px solid var(--color-surface-200); + background: var(--color-surface-50); + } + + .saas-main { + display: flex; + min-width: 0; + flex: 1; + flex-direction: column; + overflow: hidden; + } + + .saas-topbar { + display: flex; + height: 3.5rem; + flex-shrink: 0; + align-items: center; + gap: 0.75rem; + border-bottom: 1px solid var(--color-surface-200); + background: color-mix(in oklab, var(--color-surface-50) 90%, transparent); + padding: 0 1.5rem; + backdrop-filter: blur(8px); + } + + .saas-content { + flex: 1; + overflow-y: auto; + } + + .saas-content-inner { + margin-inline: auto; + width: 100%; + max-width: 72rem; + padding: 1.5rem; + } + + @media (min-width: 768px) { + .saas-content-inner { + padding: 2rem; + } + } + + .saas-nav-item { + display: flex; + align-items: center; + gap: 0.75rem; + border-radius: 0.5rem; + padding: 0.5rem 0.75rem; + font-size: 0.875rem; + font-weight: 500; + color: var(--color-surface-600); + transition: color 0.15s, background-color 0.15s; + } + + .saas-nav-item:hover { + background: var(--color-surface-100); + color: var(--color-surface-900); + } + + .saas-nav-item[data-active='true'] { + background: var(--color-primary-100); + color: var(--color-primary-700); + } + + .saas-badge { + display: inline-flex; + align-items: center; + border-radius: 0.375rem; + padding: 0.125rem 0.5rem; + font-size: 0.75rem; + font-weight: 500; + } + + .saas-badge-neutral { + display: inline-flex; + align-items: center; + border-radius: 0.375rem; + padding: 0.125rem 0.5rem; + font-size: 0.75rem; + font-weight: 500; + background: var(--color-surface-200); + color: var(--color-surface-700); + } + + .saas-badge-primary { + display: inline-flex; + align-items: center; + border-radius: 0.375rem; + padding: 0.125rem 0.5rem; + font-size: 0.75rem; + font-weight: 500; + background: var(--color-primary-100); + color: var(--color-primary-700); + } + + .saas-badge-success { + display: inline-flex; + align-items: center; + border-radius: 0.375rem; + padding: 0.125rem 0.5rem; + font-size: 0.75rem; + font-weight: 500; + background: var(--color-success-100); + color: var(--color-success-700); + } + + .saas-badge-warning { + display: inline-flex; + align-items: center; + border-radius: 0.375rem; + padding: 0.125rem 0.5rem; + font-size: 0.75rem; + font-weight: 500; + background: var(--color-warning-100); + color: var(--color-warning-800); + } + + .saas-badge-error { + display: inline-flex; + align-items: center; + border-radius: 0.375rem; + padding: 0.125rem 0.5rem; + font-size: 0.75rem; + font-weight: 500; + background: var(--color-error-100); + color: var(--color-error-700); + } + + .saas-input, + .saas-select, + .saas-textarea { + width: 100%; + border-radius: 0.5rem; + border: 1px solid var(--color-surface-200); + background: var(--color-surface-50); + color: var(--color-surface-900); + padding: 0.5rem 0.75rem; + font-size: 0.875rem; + line-height: 1.25rem; + outline: none; + transition: border-color 0.15s, box-shadow 0.15s; + } + + .saas-input:focus, + .saas-select:focus, + .saas-textarea:focus { + border-color: var(--color-primary-400); + box-shadow: 0 0 0 3px color-mix(in oklab, var(--color-primary-500) 20%, transparent); + } + + .saas-textarea { + font-family: var(--font-mono); + resize: vertical; + } + + .saas-btn-primary, + .saas-btn-secondary, + .saas-btn-ghost, + .saas-btn-danger { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.375rem; + border-radius: 0.5rem; + padding: 0.5rem 0.875rem; + font-size: 0.875rem; + font-weight: 500; + line-height: 1.25rem; + border: 1px solid transparent; + cursor: pointer; + transition: background-color 0.15s, color 0.15s, border-color 0.15s, opacity 0.15s; + } + + .saas-btn-primary:disabled, + .saas-btn-secondary:disabled, + .saas-btn-ghost:disabled, + .saas-btn-danger:disabled { + opacity: 0.55; + cursor: not-allowed; + } + + .saas-btn-primary { + background: var(--color-primary-500); + color: var(--color-primary-contrast-500, white); + } + + .saas-btn-primary:hover:not(:disabled) { + background: var(--color-primary-600); + } + + .saas-btn-secondary { + background: var(--color-secondary-100); + color: var(--color-secondary-800); + } + + .saas-btn-secondary:hover:not(:disabled) { + background: var(--color-secondary-200); + } + + .saas-btn-ghost { + background: var(--color-surface-100); + color: var(--color-surface-700); + } + + .saas-btn-ghost:hover:not(:disabled) { + background: var(--color-surface-200); + } + + .saas-btn-danger { + background: var(--color-error-100); + color: var(--color-error-700); + } + + .saas-btn-danger:hover:not(:disabled) { + background: var(--color-error-200); + } + + .saas-modal-backdrop { + position: fixed; + inset: 0; + z-index: 50; + display: flex; + align-items: center; + justify-content: center; + background: rgb(2 6 23 / 0.4); + padding: 1rem; + backdrop-filter: blur(2px); + } + + .saas-modal { + width: 100%; + max-width: 28rem; + border-radius: 1rem; + border: 1px solid var(--color-surface-200); + background: var(--color-surface-50); + padding: 1.5rem; + box-shadow: 0 20px 40px rgb(15 23 42 / 0.16); + } + + .saas-status-panel { + display: flex; + min-height: 100vh; + flex-direction: column; + align-items: center; + justify-content: center; + background: var(--color-surface-100); + padding: 1rem; + } + + .saas-status-card { + width: 100%; + max-width: 28rem; + border-radius: 1rem; + border: 1px solid var(--color-surface-200); + background: var(--color-surface-50); + padding: 2rem; + text-align: center; + box-shadow: 0 1px 2px rgb(15 23 42 / 0.04); + } +} diff --git a/hub/admin-web/static/favicon.svg b/hub/admin-web/static/favicon.svg new file mode 100644 index 0000000..43bc66d --- /dev/null +++ b/hub/admin-web/static/favicon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/hub/admin-web/static/robots.txt b/hub/admin-web/static/robots.txt new file mode 100644 index 0000000..b6dd667 --- /dev/null +++ b/hub/admin-web/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/hub/admin-web/svelte.config.js b/hub/admin-web/svelte.config.js new file mode 100644 index 0000000..ce73410 --- /dev/null +++ b/hub/admin-web/svelte.config.js @@ -0,0 +1,18 @@ +import adapter from '@sveltejs/adapter-static'; +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + preprocess: vitePreprocess(), + kit: { + adapter: adapter({ + pages: 'build', + assets: 'build', + fallback: 'index.html', + precompress: false, + strict: false + }) + } +}; + +export default config; diff --git a/hub/admin-web/tsconfig.json b/hub/admin-web/tsconfig.json new file mode 100644 index 0000000..2c2ed3c --- /dev/null +++ b/hub/admin-web/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "rewriteRelativeImportExtensions": true, + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + } + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files + // + // To make changes to top-level options such as include and exclude, we recommend extending + // the generated config; see https://svelte.dev/docs/kit/configuration#typescript +} diff --git a/hub/admin-web/vite.config.ts b/hub/admin-web/vite.config.ts new file mode 100644 index 0000000..ebd5ced --- /dev/null +++ b/hub/admin-web/vite.config.ts @@ -0,0 +1,13 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import tailwindcss from '@tailwindcss/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [tailwindcss(), sveltekit()], + server: { + proxy: { + '/api': 'http://127.0.0.1:8788', + '/auth': 'http://127.0.0.1:8788' + } + } +}); diff --git a/hub/package-lock.json b/hub/package-lock.json index 893906b..ce984cb 100644 --- a/hub/package-lock.json +++ b/hub/package-lock.json @@ -131,9 +131,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "SEE LICENSE IN LICENSE.md", "optional": true, "os": [ @@ -147,9 +144,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "SEE LICENSE IN LICENSE.md", "optional": true, "os": [ @@ -163,9 +157,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "SEE LICENSE IN LICENSE.md", "optional": true, "os": [ @@ -179,9 +170,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "SEE LICENSE IN LICENSE.md", "optional": true, "os": [ @@ -241,34 +229,10 @@ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } }, - "node_modules/@emnapi/core": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", - "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", - "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", @@ -858,7 +822,6 @@ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", "license": "MIT", - "peer": true, "engines": { "node": ">=18.14.1" }, @@ -1199,9 +1162,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1219,9 +1179,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1239,9 +1196,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1259,9 +1213,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1279,9 +1230,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1299,9 +1247,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1392,8 +1337,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@standard-schema/spec": { "version": "1.1.0", @@ -1585,7 +1529,6 @@ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", - "peer": true, "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" @@ -1599,7 +1542,6 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -1609,7 +1551,6 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", - "peer": true, "dependencies": { "mime-db": "^1.54.0" }, @@ -1745,7 +1686,6 @@ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "license": "MIT", - "peer": true, "dependencies": { "bytes": "^3.1.2", "content-type": "^2.0.0", @@ -1770,7 +1710,6 @@ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -1784,7 +1723,6 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -1930,7 +1868,6 @@ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -1944,7 +1881,6 @@ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -1974,7 +1910,6 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.6.0" } @@ -1984,7 +1919,6 @@ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "license": "MIT", - "peer": true, "dependencies": { "object-assign": "^4", "vary": "^1" @@ -2002,7 +1936,6 @@ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "license": "MIT", - "peer": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -2060,7 +1993,6 @@ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -2121,8 +2053,7 @@ "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", - "peer": true + "license": "MIT" }, "node_modules/effect": { "version": "3.21.0", @@ -2150,7 +2081,6 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -2253,8 +2183,7 @@ "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", - "peer": true + "license": "MIT" }, "node_modules/estree-walker": { "version": "3.0.3", @@ -2271,7 +2200,6 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -2281,7 +2209,6 @@ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", "license": "MIT", - "peer": true, "dependencies": { "eventsource-parser": "^3.0.1" }, @@ -2313,7 +2240,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -2357,7 +2283,6 @@ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", "license": "MIT", - "peer": true, "dependencies": { "ip-address": "^10.2.0" }, @@ -2376,7 +2301,6 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -2386,7 +2310,6 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -2396,7 +2319,6 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", - "peer": true, "dependencies": { "mime-db": "^1.54.0" }, @@ -2487,8 +2409,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", - "license": "Unlicense", - "peer": true + "license": "Unlicense" }, "node_modules/fast-uri": { "version": "3.1.3", @@ -2587,7 +2508,6 @@ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", - "peer": true, "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", @@ -2659,7 +2579,6 @@ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -2669,7 +2588,6 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -2819,7 +2737,6 @@ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", - "peer": true, "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", @@ -2853,7 +2770,6 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "license": "MIT", - "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -2869,15 +2785,13 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/ip-address": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", "license": "MIT", - "peer": true, "engines": { "node": ">= 12" } @@ -2895,15 +2809,13 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/jiti": { "version": "2.7.0", @@ -2920,7 +2832,6 @@ "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/panva" } @@ -2955,7 +2866,6 @@ "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" @@ -2974,8 +2884,7 @@ "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", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/light-my-request": { "version": "6.6.0", @@ -3157,9 +3066,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3181,9 +3087,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3205,9 +3108,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3229,9 +3129,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -3335,7 +3232,6 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -3345,7 +3241,6 @@ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -3404,7 +3299,6 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -3446,7 +3340,6 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -3498,7 +3391,6 @@ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "license": "MIT", - "peer": true, "dependencies": { "ee-first": "1.1.1" }, @@ -3511,7 +3403,6 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "license": "ISC", - "peer": true, "dependencies": { "wrappy": "1" } @@ -3521,7 +3412,6 @@ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -3531,7 +3421,6 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -3541,7 +3430,6 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "license": "MIT", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" @@ -3574,6 +3462,7 @@ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -3623,7 +3512,6 @@ "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=16.20.0" } @@ -3676,6 +3564,7 @@ "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@prisma/config": "6.19.3", "@prisma/engines": "6.19.3" @@ -3739,7 +3628,6 @@ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "license": "MIT", - "peer": true, "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -3753,7 +3641,6 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.10" } @@ -3811,7 +3698,6 @@ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" }, @@ -3825,7 +3711,6 @@ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", - "peer": true, "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", @@ -3943,7 +3828,6 @@ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "license": "MIT", - "peer": true, "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", @@ -3990,8 +3874,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/secure-json-parse": { "version": "4.1.0", @@ -4026,7 +3909,6 @@ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", - "peer": true, "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", @@ -4053,7 +3935,6 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -4063,7 +3944,6 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", - "peer": true, "dependencies": { "mime-db": "^1.54.0" }, @@ -4080,7 +3960,6 @@ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "license": "MIT", - "peer": true, "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", @@ -4105,15 +3984,13 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC", - "peer": true + "license": "ISC" }, "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==", "license": "MIT", - "peer": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -4126,7 +4003,6 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -4250,7 +4126,6 @@ "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz", "integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==", "license": "MIT", - "peer": true, "dependencies": { "@stablelib/base64": "^1.0.0", "fast-sha256": "^1.3.0" @@ -4261,7 +4136,6 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -4349,7 +4223,6 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.6" } @@ -4358,8 +4231,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tslib": { "version": "2.8.1", @@ -4375,6 +4247,7 @@ "integrity": "sha512-eUdUIaCr963q2h5u3+QwvYp0+eqPvn+egeqZUm0hwERCqqx1E3kK5ehbGCvqSE5MQAULr67ww0cA3jKc3YkM1w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.28.0" }, @@ -4393,7 +4266,6 @@ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "license": "MIT", - "peer": true, "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", @@ -4412,7 +4284,6 @@ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -4426,7 +4297,6 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -4436,7 +4306,6 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", - "peer": true, "dependencies": { "mime-db": "^1.54.0" }, @@ -4454,6 +4323,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4473,7 +4343,6 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -4483,7 +4352,6 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -4494,6 +4362,7 @@ "integrity": "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -4661,7 +4530,6 @@ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "license": "ISC", - "peer": true, "dependencies": { "isexe": "^2.0.0" }, @@ -4693,8 +4561,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/ws": { "version": "8.21.0", @@ -4722,6 +4589,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -4731,7 +4599,6 @@ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "license": "ISC", - "peer": true, "peerDependencies": { "zod": "^3.25.28 || ^4" } diff --git a/hub/prisma/migrations/20260710120000_org_model_role_management/migration.sql b/hub/prisma/migrations/20260710120000_org_model_role_management/migration.sql new file mode 100644 index 0000000..14522f1 --- /dev/null +++ b/hub/prisma/migrations/20260710120000_org_model_role_management/migration.sql @@ -0,0 +1,62 @@ +-- ADR-0021 / ADR-0017: org-scoped model provider connection, enabled models, +-- and role presets. Aligns to Spec.System.Organization (ProviderCredentialMode, +-- OrganizationProviderConnection) and the "roles are data" seam in agent/models. + +CREATE TYPE "ProviderCredentialMode" AS ENUM ('BYOK', 'PLATFORM_MANAGED'); + +CREATE TABLE "OrganizationProviderConnection" ( + "organizationId" TEXT NOT NULL, + "providerId" TEXT NOT NULL DEFAULT 'openrouter', + "mode" "ProviderCredentialMode" NOT NULL DEFAULT 'PLATFORM_MANAGED', + "baseUrl" TEXT, + "authToken" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "OrganizationProviderConnection_pkey" PRIMARY KEY ("organizationId") +); + +ALTER TABLE "OrganizationProviderConnection" + ADD CONSTRAINT "OrganizationProviderConnection_organizationId_fkey" + FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +CREATE TABLE "OrgModel" ( + "id" TEXT NOT NULL, + "organizationId" TEXT NOT NULL, + "modelId" TEXT NOT NULL, + "label" TEXT NOT NULL, + "toolCapable" BOOLEAN NOT NULL DEFAULT true, + "sortKey" TEXT NOT NULL DEFAULT '', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "OrgModel_pkey" PRIMARY KEY ("id") +); + +CREATE UNIQUE INDEX "OrgModel_organizationId_modelId_key" ON "OrgModel"("organizationId", "modelId"); +CREATE INDEX "OrgModel_organizationId_sortKey_idx" ON "OrgModel"("organizationId", "sortKey"); + +ALTER TABLE "OrgModel" + ADD CONSTRAINT "OrgModel_organizationId_fkey" + FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +CREATE TABLE "OrgRole" ( + "id" TEXT NOT NULL, + "organizationId" TEXT NOT NULL, + "roleId" TEXT NOT NULL, + "label" TEXT NOT NULL, + "defaultModelId" TEXT, + "systemPrompt" TEXT, + "tools" JSONB, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "OrgRole_pkey" PRIMARY KEY ("id") +); + +CREATE UNIQUE INDEX "OrgRole_organizationId_roleId_key" ON "OrgRole"("organizationId", "roleId"); +CREATE INDEX "OrgRole_organizationId_idx" ON "OrgRole"("organizationId"); + +ALTER TABLE "OrgRole" + ADD CONSTRAINT "OrgRole_organizationId_fkey" + FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/hub/src/admin/routes/modelRoutes.ts b/hub/src/admin/routes/modelRoutes.ts new file mode 100644 index 0000000..bd292cf --- /dev/null +++ b/hub/src/admin/routes/modelRoutes.ts @@ -0,0 +1,252 @@ +/** + * Org-admin routes for agent config: provider connection, enabled models, and + * role presets (ADR-0017 / ADR-0021). Mounted under `/api/org/:orgSlug`. + */ +import type { PrismaClient } from "@prisma/client"; +import type { FastifyInstance } from "fastify"; +import { + createOrgModel, + createOrgRole, + deleteOrgModel, + deleteOrgRole, + getProviderConnection, + listOrgModels, + listOrgRoles, + updateOrgModel, + updateOrgRole, + upsertProviderConnection, +} from "../../org/agentConfig.js"; +import { requireOrgRole, type GuardDeps } from "../auth/guards.js"; +import { handleRouteError } from "../errors.js"; + +const PROVIDER_MODES = ["BYOK", "PLATFORM_MANAGED"] as const; +type ProviderMode = (typeof PROVIDER_MODES)[number]; + +export async function registerModelRoutes( + app: FastifyInstance, + config: { readonly prisma: PrismaClient; readonly sessionSecret: string }, +): Promise { + const guardDeps: GuardDeps = { prisma: config.prisma, sessionSecret: config.sessionSecret }; + + // --- Provider connection --------------------------------------------- + + app.get("/api/org/:orgSlug/provider-connection", async (request, reply) => { + try { + const { orgSlug } = request.params as { orgSlug: string }; + const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); + if (auth === null) return; + return await getProviderConnection(config.prisma, auth.organization.id); + } catch (err) { + return handleRouteError(reply, err); + } + }); + + app.put("/api/org/:orgSlug/provider-connection", async (request, reply) => { + try { + const { orgSlug } = request.params as { orgSlug: string }; + const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); + if (auth === null) return; + const body = request.body as { + providerId?: unknown; + mode?: unknown; + baseUrl?: unknown; + authToken?: unknown; + }; + const mode = parseProviderMode(body.mode); + if (mode === null) { + return reply.status(400).send({ + error: { code: "bad_request", message: "mode must be BYOK or PLATFORM_MANAGED" }, + }); + } + const connection = await upsertProviderConnection(config.prisma, { + organizationId: auth.organization.id, + mode, + ...(typeof body.providerId === "string" ? { providerId: body.providerId } : {}), + ...(body.baseUrl === null || typeof body.baseUrl === "string" + ? { baseUrl: body.baseUrl as string | null } + : {}), + ...(body.authToken === null || typeof body.authToken === "string" + ? { authToken: body.authToken as string | null } + : {}), + }); + return connection; + } catch (err) { + return handleRouteError(reply, err); + } + }); + + // --- Models ---------------------------------------------------------- + + app.get("/api/org/:orgSlug/models", async (request, reply) => { + try { + const { orgSlug } = request.params as { orgSlug: string }; + const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); + if (auth === null) return; + return { models: await listOrgModels(config.prisma, auth.organization.id) }; + } catch (err) { + return handleRouteError(reply, err); + } + }); + + app.post("/api/org/:orgSlug/models", async (request, reply) => { + try { + const { orgSlug } = request.params as { orgSlug: string }; + const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); + if (auth === null) return; + const body = request.body as { + modelId?: unknown; + label?: unknown; + toolCapable?: unknown; + sortKey?: unknown; + }; + if (typeof body.modelId !== "string" || typeof body.label !== "string") { + return reply.status(400).send({ + error: { code: "bad_request", message: "modelId and label are required" }, + }); + } + const model = await createOrgModel(config.prisma, { + organizationId: auth.organization.id, + modelId: body.modelId, + label: body.label, + ...(typeof body.toolCapable === "boolean" ? { toolCapable: body.toolCapable } : {}), + ...(typeof body.sortKey === "string" ? { sortKey: body.sortKey } : {}), + }); + return reply.status(201).send(model); + } catch (err) { + return handleRouteError(reply, err); + } + }); + + app.patch("/api/org/:orgSlug/models/:modelId", async (request, reply) => { + try { + const { orgSlug, modelId } = request.params as { orgSlug: string; modelId: string }; + const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); + if (auth === null) return; + const body = request.body as { + label?: unknown; + toolCapable?: unknown; + sortKey?: unknown; + modelId?: unknown; + }; + return await updateOrgModel(config.prisma, { + organizationId: auth.organization.id, + id: modelId, + ...(typeof body.label === "string" ? { label: body.label } : {}), + ...(typeof body.toolCapable === "boolean" ? { toolCapable: body.toolCapable } : {}), + ...(typeof body.sortKey === "string" ? { sortKey: body.sortKey } : {}), + ...(typeof body.modelId === "string" ? { modelId: body.modelId } : {}), + }); + } catch (err) { + return handleRouteError(reply, err); + } + }); + + app.delete("/api/org/:orgSlug/models/:modelId", async (request, reply) => { + try { + const { orgSlug, modelId } = request.params as { orgSlug: string; modelId: string }; + const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); + if (auth === null) return; + return await deleteOrgModel(config.prisma, { + organizationId: auth.organization.id, + id: modelId, + }); + } catch (err) { + return handleRouteError(reply, err); + } + }); + + // --- Roles ----------------------------------------------------------- + + app.get("/api/org/:orgSlug/roles", async (request, reply) => { + try { + const { orgSlug } = request.params as { orgSlug: string }; + const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); + if (auth === null) return; + return { roles: await listOrgRoles(config.prisma, auth.organization.id) }; + } catch (err) { + return handleRouteError(reply, err); + } + }); + + app.post("/api/org/:orgSlug/roles", async (request, reply) => { + try { + const { orgSlug } = request.params as { orgSlug: string }; + const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); + if (auth === null) return; + const body = request.body as { + roleId?: unknown; + label?: unknown; + defaultModelId?: unknown; + systemPrompt?: unknown; + tools?: unknown; + }; + if (typeof body.roleId !== "string" || typeof body.label !== "string") { + return reply.status(400).send({ + error: { code: "bad_request", message: "roleId and label are required" }, + }); + } + const role = await createOrgRole(config.prisma, { + organizationId: auth.organization.id, + roleId: body.roleId, + label: body.label, + ...(typeof body.defaultModelId === "string" ? { defaultModelId: body.defaultModelId } : {}), + ...(body.defaultModelId === null ? { defaultModelId: null } : {}), + ...(typeof body.systemPrompt === "string" ? { systemPrompt: body.systemPrompt } : {}), + ...(body.systemPrompt === null ? { systemPrompt: null } : {}), + ...(Array.isArray(body.tools) ? { tools: body.tools as readonly string[] } : {}), + ...(body.tools === null ? { tools: null } : {}), + }); + return reply.status(201).send(role); + } catch (err) { + return handleRouteError(reply, err); + } + }); + + app.patch("/api/org/:orgSlug/roles/:roleId", async (request, reply) => { + try { + const { orgSlug, roleId } = request.params as { orgSlug: string; roleId: string }; + const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); + if (auth === null) return; + const body = request.body as { + label?: unknown; + defaultModelId?: unknown; + systemPrompt?: unknown; + tools?: unknown; + roleId?: unknown; + }; + return await updateOrgRole(config.prisma, { + organizationId: auth.organization.id, + id: roleId, + ...(typeof body.label === "string" ? { label: body.label } : {}), + ...(typeof body.defaultModelId === "string" ? { defaultModelId: body.defaultModelId } : {}), + ...(body.defaultModelId === null ? { defaultModelId: null } : {}), + ...(typeof body.systemPrompt === "string" ? { systemPrompt: body.systemPrompt } : {}), + ...(body.systemPrompt === null ? { systemPrompt: null } : {}), + ...(Array.isArray(body.tools) ? { tools: body.tools as readonly string[] } : {}), + ...(body.tools === null ? { tools: null } : {}), + ...(typeof body.roleId === "string" ? { roleId: body.roleId } : {}), + }); + } catch (err) { + return handleRouteError(reply, err); + } + }); + + app.delete("/api/org/:orgSlug/roles/:roleId", async (request, reply) => { + try { + const { orgSlug, roleId } = request.params as { orgSlug: string; roleId: string }; + const auth = await requireOrgRole(request, reply, guardDeps, { orgSlug }); + if (auth === null) return; + return await deleteOrgRole(config.prisma, { + organizationId: auth.organization.id, + id: roleId, + }); + } catch (err) { + return handleRouteError(reply, err); + } + }); +} + +function parseProviderMode(value: unknown): ProviderMode | null { + if (typeof value !== "string") return null; + return (PROVIDER_MODES as readonly string[]).includes(value) ? (value as ProviderMode) : null; +} diff --git a/hub/src/admin/routes/orgRoutes.ts b/hub/src/admin/routes/orgRoutes.ts index 8bec133..1440ddc 100644 --- a/hub/src/admin/routes/orgRoutes.ts +++ b/hub/src/admin/routes/orgRoutes.ts @@ -12,6 +12,7 @@ import { import { registerAccessRoutes } from "./accessRoutes.js"; import { registerExplorerRoutes } from "./explorerRoutes.js"; import { registerMembersRoutes } from "./membersRoutes.js"; +import { registerModelRoutes } from "./modelRoutes.js"; import { registerSessionsAndUsageRoutes } from "./sessionsRoutes.js"; import { registerTeamsRoutes } from "./teamsRoutes.js"; import { registerProviderConnectionRoutes } from "./providerConnectionRoutes.js"; @@ -105,6 +106,10 @@ export async function registerOrgRoutes(app: FastifyInstance, config: OrgRouteCo prisma: config.prisma, sessionSecret: config.sessionSecret, }); + await registerModelRoutes(app, { + prisma: config.prisma, + sessionSecret: config.sessionSecret, + }); await registerSessionsAndUsageRoutes(app, { prisma: config.prisma, sessionSecret: config.sessionSecret, diff --git a/hub/src/admin/static.ts b/hub/src/admin/static.ts new file mode 100644 index 0000000..024e2c7 --- /dev/null +++ b/hub/src/admin/static.ts @@ -0,0 +1,82 @@ +/** + * Serves the org-admin SPA (built by SvelteKit via `admin-web/build/`) and + * the SPA index fallback for client-side routes under `/admin/*`. + * + * The SvelteKit project lives in `hub/admin-web/`. Run `npm run build` there + * to produce the static output in `admin-web/build/`. In development, use + * `npm run dev` in `admin-web/` which proxies `/api` and `/auth` to the Hub. + * + * Override the UI directory with `CPH_ADMIN_UI_DIR` env if needed. + */ +import { readFile } from "node:fs/promises"; +import { existsSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, extname, join, resolve as resolvePath } from "node:path"; +import type { FastifyInstance } from "fastify"; + +const MIME: Record = { + ".html": "text/html; charset=utf-8", + ".js": "text/javascript; charset=utf-8", + ".mjs": "text/javascript; charset=utf-8", + ".css": "text/css; charset=utf-8", + ".svg": "image/svg+xml", + ".ico": "image/x-icon", + ".png": "image/png", + ".jpg": "image/jpeg", + ".woff": "font/woff", + ".woff2": "font/woff2", + ".json": "application/json; charset=utf-8", + ".txt": "text/plain; charset=utf-8", +}; + +function resolveUiDir(): string { + const override = process.env["CPH_ADMIN_UI_DIR"]; + if (override && override.trim() !== "") return resolvePath(override); + const here = dirname(fileURLToPath(import.meta.url)); + return resolvePath(join(here, "..", "..", "admin-web", "build")); +} + +export async function registerStaticSpa(app: FastifyInstance): Promise { + const uiDir = resolveUiDir(); + if (!existsSync(join(uiDir, "index.html"))) { + app.log.warn( + { uiDir }, + "admin-web/build not found; SPA shell disabled. Run `npm run build` in admin-web/ to enable. Org admin APIs remain fully functional.", + ); + return; + } + const indexHtml = await readFile(join(uiDir, "index.html"), "utf8"); + + // SvelteKit static assets (_app/*, favicon, etc.) + app.get("/_app/*", async (request, reply) => { + const rel = (request.params as { "*": string })["*"]; + const safe = rel.split("/").filter((p) => p !== ".." && p !== "").join("/"); + try { + const buf = await readFile(join(uiDir, "_app", safe)); + const mime = MIME[extname(safe)] ?? "application/octet-stream"; + return reply.type(mime).send(buf); + } catch { + return reply.status(404).send({ error: { code: "not_found", message: "asset not found" } }); + } + }); + + // Other top-level static assets (favicon.svg, robots.txt, etc.) + app.get("/favicon.svg", async (_request, reply) => { + try { + const buf = await readFile(join(uiDir, "favicon.svg")); + return reply.type("image/svg+xml").send(buf); + } catch { + return reply.status(404).send(); + } + }); + + // SPA client-side route fallback. `/admin/login` is registered earlier as a + // static route and takes precedence; everything else under /admin/* serves + // the index so SvelteKit's client-side router can resolve the view. + app.get("/admin", async (_request, reply) => { + return reply.type("text/html; charset=utf-8").send(indexHtml); + }); + app.get("/admin/*", async (_request, reply) => { + return reply.type("text/html; charset=utf-8").send(indexHtml); + }); +} diff --git a/hub/src/org/agentConfig.ts b/hub/src/org/agentConfig.ts new file mode 100644 index 0000000..227cb0f --- /dev/null +++ b/hub/src/org/agentConfig.ts @@ -0,0 +1,386 @@ +/** + * Org-scoped agent config: provider connection, enabled models, and role + * presets (ADR-0017 / ADR-0021). + * + * Roles and models are **data** (ADR-0017): admin/teacher-defined, persisted + * per organization. The runtime registry (`settings/runtime.ts`) reads these + * rows, falling back to env-based defaults when an org has none configured. + * + * Provider credential storage is a pilot placeholder: the spec leaves key/base + * URL encryption, rotation and resolver mechanism OPEN (secret-control-plane). + * BYOK tokens are stored as opaque strings here; platform-managed credentials + * are resolved from process env, not stored in this table. + */ +import type { PrismaClient } from "@prisma/client"; +import { Prisma } from "@prisma/client"; +import { assertSupportedRoleTools } from "../agent/roleTools.js"; + +export interface ProviderConnectionRow { + readonly organizationId: string; + readonly providerId: string; + readonly mode: "BYOK" | "PLATFORM_MANAGED"; + readonly baseUrl: string | null; + readonly hasAuthToken: boolean; + readonly createdAt: string; + readonly updatedAt: string; +} + +export async function getProviderConnection( + prisma: PrismaClient, + organizationId: string, +): Promise { + const row = await prisma.organizationProviderConnection.findUnique({ + where: { organizationId }, + }); + if (row === null) { + return { + organizationId, + providerId: "openrouter", + mode: "PLATFORM_MANAGED", + baseUrl: null, + hasAuthToken: false, + createdAt: "", + updatedAt: "", + }; + } + return { + organizationId: row.organizationId, + providerId: row.providerId, + mode: row.mode, + baseUrl: row.baseUrl, + hasAuthToken: row.authToken !== null && row.authToken !== "", + createdAt: row.createdAt.toISOString(), + updatedAt: row.updatedAt.toISOString(), + }; +} + +export async function upsertProviderConnection( + prisma: PrismaClient, + input: { + readonly organizationId: string; + readonly providerId?: string | undefined; + readonly mode: "BYOK" | "PLATFORM_MANAGED"; + readonly baseUrl?: string | null | undefined; + readonly authToken?: string | null | undefined; + }, +): Promise { + const providerId = input.providerId ?? "openrouter"; + if (providerId.trim() === "") throw new Error("providerId is required"); + + const baseUrl = + input.baseUrl === undefined || input.baseUrl === null + ? null + : input.baseUrl.trim() === "" + ? null + : input.baseUrl.trim(); + + const authToken = + input.authToken === undefined || input.authToken === null + ? undefined + : input.authToken === "" + ? null + : input.authToken; + + const row = await prisma.organizationProviderConnection.upsert({ + where: { organizationId: input.organizationId }, + create: { + organizationId: input.organizationId, + providerId, + mode: input.mode, + baseUrl, + ...(authToken !== undefined ? { authToken } : {}), + }, + update: { + providerId, + mode: input.mode, + baseUrl, + ...(authToken !== undefined ? { authToken } : {}), + }, + }); + return { + organizationId: row.organizationId, + providerId: row.providerId, + mode: row.mode, + baseUrl: row.baseUrl, + hasAuthToken: row.authToken !== null && row.authToken !== "", + createdAt: row.createdAt.toISOString(), + updatedAt: row.updatedAt.toISOString(), + }; +} + +export interface OrgModelRow { + readonly id: string; + readonly modelId: string; + readonly label: string; + readonly toolCapable: boolean; + readonly sortKey: string; + readonly createdAt: string; + readonly updatedAt: string; +} + +export async function listOrgModels( + prisma: PrismaClient, + organizationId: string, +): Promise { + const rows = await prisma.orgModel.findMany({ + where: { organizationId }, + orderBy: [{ sortKey: "asc" }, { createdAt: "asc" }], + }); + return rows.map(toModelRow); +} + +export async function createOrgModel( + prisma: PrismaClient, + input: { + readonly organizationId: string; + readonly modelId: string; + readonly label: string; + readonly toolCapable?: boolean | undefined; + readonly sortKey?: string | undefined; + }, +): Promise { + const modelId = requireNonEmpty(input.modelId, "modelId"); + const label = requireNonEmpty(input.label, "label"); + const existing = await prisma.orgModel.findUnique({ + where: { organizationId_modelId: { organizationId: input.organizationId, modelId } }, + select: { id: true }, + }); + if (existing !== null) { + throw new Error(`model already exists: ${modelId}`); + } + const row = await prisma.orgModel.create({ + data: { + organizationId: input.organizationId, + modelId, + label, + ...(input.toolCapable !== undefined ? { toolCapable: input.toolCapable } : {}), + ...(input.sortKey !== undefined ? { sortKey: input.sortKey } : {}), + }, + }); + return toModelRow(row); +} + +export async function updateOrgModel( + prisma: PrismaClient, + input: { + readonly organizationId: string; + readonly id: string; + readonly label?: string | undefined; + readonly toolCapable?: boolean | undefined; + readonly sortKey?: string | undefined; + readonly modelId?: string | undefined; + }, +): Promise { + const row = await prisma.orgModel.findUnique({ where: { id: input.id } }); + if (row === null || row.organizationId !== input.organizationId) { + throw new Error(`model not found: ${input.id}`); + } + const updated = await prisma.orgModel.update({ + where: { id: row.id }, + data: { + ...(input.label !== undefined ? { label: requireNonEmpty(input.label, "label") } : {}), + ...(input.toolCapable !== undefined ? { toolCapable: input.toolCapable } : {}), + ...(input.sortKey !== undefined ? { sortKey: input.sortKey } : {}), + ...(input.modelId !== undefined + ? { modelId: requireNonEmpty(input.modelId, "modelId") } + : {}), + }, + }); + return toModelRow(updated); +} + +export async function deleteOrgModel( + prisma: PrismaClient, + input: { readonly organizationId: string; readonly id: string }, +): Promise<{ readonly deleted: true; readonly id: string }> { + const row = await prisma.orgModel.findUnique({ where: { id: input.id } }); + if (row === null || row.organizationId !== input.organizationId) { + throw new Error(`model not found: ${input.id}`); + } + await prisma.orgModel.delete({ where: { id: row.id } }); + return { deleted: true as const, id: row.id }; +} + +export interface OrgRoleRow { + readonly id: string; + readonly roleId: string; + readonly label: string; + readonly defaultModelId: string | null; + readonly systemPrompt: string | null; + readonly tools: readonly string[] | null; + readonly createdAt: string; + readonly updatedAt: string; +} + +export async function listOrgRoles( + prisma: PrismaClient, + organizationId: string, +): Promise { + const rows = await prisma.orgRole.findMany({ + where: { organizationId }, + orderBy: [{ roleId: "asc" }], + }); + return rows.map(toRoleRow); +} + +export async function createOrgRole( + prisma: PrismaClient, + input: { + readonly organizationId: string; + readonly roleId: string; + readonly label: string; + readonly defaultModelId?: string | null | undefined; + readonly systemPrompt?: string | null | undefined; + readonly tools?: readonly string[] | null | undefined; + }, +): Promise { + const roleId = requireNonEmpty(input.roleId, "roleId"); + const label = requireNonEmpty(input.label, "label"); + const existing = await prisma.orgRole.findUnique({ + where: { organizationId_roleId: { organizationId: input.organizationId, roleId } }, + select: { id: true }, + }); + if (existing !== null) { + throw new Error(`role already exists: ${roleId}`); + } + const tools = normalizeTools(input.tools); + const row = await prisma.orgRole.create({ + data: { + organizationId: input.organizationId, + roleId, + label, + ...(input.defaultModelId !== undefined && input.defaultModelId !== null + ? { defaultModelId: input.defaultModelId } + : {}), + ...(input.defaultModelId === null ? { defaultModelId: null } : {}), + ...(input.systemPrompt !== undefined && input.systemPrompt !== null + ? { systemPrompt: input.systemPrompt } + : {}), + ...(input.systemPrompt === null ? { systemPrompt: null } : {}), + ...(tools !== undefined ? { tools: tools ?? Prisma.DbNull } : {}), + }, + }); + return toRoleRow(row); +} + +export async function updateOrgRole( + prisma: PrismaClient, + input: { + readonly organizationId: string; + readonly id: string; + readonly label?: string | undefined; + readonly defaultModelId?: string | null | undefined; + readonly systemPrompt?: string | null | undefined; + readonly tools?: readonly string[] | null | undefined; + readonly roleId?: string | undefined; + }, +): Promise { + const row = await prisma.orgRole.findUnique({ where: { id: input.id } }); + if (row === null || row.organizationId !== input.organizationId) { + throw new Error(`role not found: ${input.id}`); + } + const tools = normalizeTools(input.tools); + const updated = await prisma.orgRole.update({ + where: { id: row.id }, + data: { + ...(input.label !== undefined ? { label: requireNonEmpty(input.label, "label") } : {}), + ...(input.defaultModelId !== undefined && input.defaultModelId !== null + ? { defaultModelId: input.defaultModelId } + : {}), + ...(input.defaultModelId === null ? { defaultModelId: null } : {}), + ...(input.systemPrompt !== undefined && input.systemPrompt !== null + ? { systemPrompt: input.systemPrompt } + : {}), + ...(input.systemPrompt === null ? { systemPrompt: null } : {}), + ...(tools !== undefined ? { tools: tools ?? Prisma.DbNull } : {}), + ...(input.roleId !== undefined ? { roleId: requireNonEmpty(input.roleId, "roleId") } : {}), + }, + }); + return toRoleRow(updated); +} + +export async function deleteOrgRole( + prisma: PrismaClient, + input: { readonly organizationId: string; readonly id: string }, +): Promise<{ readonly deleted: true; readonly id: string }> { + const row = await prisma.orgRole.findUnique({ where: { id: input.id } }); + if (row === null || row.organizationId !== input.organizationId) { + throw new Error(`role not found: ${input.id}`); + } + await prisma.orgRole.delete({ where: { id: row.id } }); + return { deleted: true as const, id: row.id }; +} + +function toModelRow( + row: { + readonly id: string; + readonly modelId: string; + readonly label: string; + readonly toolCapable: boolean; + readonly sortKey: string; + readonly createdAt: Date; + readonly updatedAt: Date; + }, +): OrgModelRow { + return { + id: row.id, + modelId: row.modelId, + label: row.label, + toolCapable: row.toolCapable, + sortKey: row.sortKey, + createdAt: row.createdAt.toISOString(), + updatedAt: row.updatedAt.toISOString(), + }; +} + +function toRoleRow( + row: { + readonly id: string; + readonly roleId: string; + readonly label: string; + readonly defaultModelId: string | null; + readonly systemPrompt: string | null; + readonly tools: unknown; + readonly createdAt: Date; + readonly updatedAt: Date; + }, +): OrgRoleRow { + return { + id: row.id, + roleId: row.roleId, + label: row.label, + defaultModelId: row.defaultModelId, + systemPrompt: row.systemPrompt, + tools: parseTools(row.tools), + createdAt: row.createdAt.toISOString(), + updatedAt: row.updatedAt.toISOString(), + }; +} + +/** + * Normalize a tools input into a value safe for prisma `tools` (Json?) column. + * - `undefined` ⇒ omit (leave unchanged): returns `undefined`. + * - `null` ⇒ unrestricted role (SQL NULL): returns `null`. + * - array ⇒ validated against the supported role tool set (fails fast). + */ +function normalizeTools( + tools: readonly string[] | null | undefined, +): readonly string[] | null | undefined { + if (tools === undefined) return undefined; + if (tools === null) return null; + const list = [...tools]; + assertSupportedRoleTools(list); + return list; +} + +function parseTools(tools: unknown): readonly string[] | null { + if (tools === null) return null; + if (!Array.isArray(tools)) return null; + return tools.filter((t): t is string => typeof t === "string"); +} + +function requireNonEmpty(value: string, label: string): string { + const trimmed = value.trim(); + if (trimmed === "") throw new Error(`${label} is required`); + return trimmed; +} diff --git a/hub/test/integration/admin-agent-config.test.ts b/hub/test/integration/admin-agent-config.test.ts new file mode 100644 index 0000000..b23ca59 --- /dev/null +++ b/hub/test/integration/admin-agent-config.test.ts @@ -0,0 +1,239 @@ +/** + * Org agent-config API tests: provider connection, models, roles (ADR-0017 / + * ADR-0021). Also covers the DB-backed runtime registry wiring. + */ +import Fastify from "fastify"; +import { afterAll, beforeEach, describe, expect, it } from "vitest"; +import { registerAdminPlugin } from "../../src/admin/plugin.js"; +import { + mintSessionToken, + sessionCookieHeader, +} from "../../src/admin/routes/authRoutes.js"; +import { createDbRuntimeSettings } from "../../src/settings/runtime.js"; +import { DEFAULT_ORG_ID, prisma, resetDb } from "./helpers.js"; + +const SESSION_SECRET = "integration-test-session-secret"; + +async function buildApp() { + const app = Fastify({ logger: false }); + await registerAdminPlugin(app, { + prisma, + sessionSecret: SESSION_SECRET, + publicBaseUrl: "http://127.0.0.1:8788", + feishuAppId: "cli_test", + feishuAppSecret: "secret_test", + projectWorkspaceRoot: "/tmp/cph-test-ws", + cookieSecure: false, + }); + await app.ready(); + return app; +} + +async function seedOwner(): Promise { + await prisma.user.create({ + data: { id: "u-owner", feishuOpenId: "ou_owner", displayName: "Owner" }, + }); + await prisma.organizationMembership.create({ + data: { organizationId: DEFAULT_ORG_ID, userId: "u-owner", role: "OWNER" }, + }); + return mintSessionToken({ userId: "u-owner", feishuOpenId: "ou_owner" }, SESSION_SECRET); +} + +describe("admin agent-config API (provider/models/roles)", () => { + beforeEach(async () => { + await resetDb(); + }); + + afterAll(async () => { + await prisma.$disconnect(); + }); + + it("upserts a BYOK provider connection and reads it back without exposing the token", async () => { + const token = await seedOwner(); + const app = await buildApp(); + try { + const cookie = sessionCookieHeader(token); + + const put = await app.inject({ + method: "PUT", + url: "/api/org/test-default/provider-connection", + headers: { cookie, "content-type": "application/json" }, + payload: { mode: "BYOK", baseUrl: "https://gw.test/api", authToken: "secret-token-123" }, + }); + expect(put.statusCode).toBe(200); + const conn = put.json() as { mode: string; baseUrl: string; hasAuthToken: boolean }; + expect(conn.mode).toBe("BYOK"); + expect(conn.baseUrl).toBe("https://gw.test/api"); + expect(conn.hasAuthToken).toBe(true); + + const get = await app.inject({ + method: "GET", + url: "/api/org/test-default/provider-connection", + headers: { cookie }, + }); + const got = get.json() as { mode: string; hasAuthToken: boolean }; + expect(got.mode).toBe("BYOK"); + expect(got.hasAuthToken).toBe(true); + // Token value must never be returned by GET. + expect(get.body).not.toContain("secret-token-123"); + } finally { + await app.close(); + } + }); + + it("creates, updates and deletes an enabled model", async () => { + const token = await seedOwner(); + const app = await buildApp(); + try { + const cookie = sessionCookieHeader(token); + + const create = await app.inject({ + method: "POST", + url: "/api/org/test-default/models", + headers: { cookie, "content-type": "application/json" }, + payload: { modelId: "anthropic/claude-sonnet-5", label: "Sonnet 5" }, + }); + expect(create.statusCode).toBe(201); + const model = create.json() as { id: string; modelId: string }; + expect(model.modelId).toBe("anthropic/claude-sonnet-5"); + + // Duplicate modelId is rejected. + const dup = await app.inject({ + method: "POST", + url: "/api/org/test-default/models", + headers: { cookie, "content-type": "application/json" }, + payload: { modelId: "anthropic/claude-sonnet-5", label: "dup" }, + }); + expect(dup.statusCode).toBe(400); + + const upd = await app.inject({ + method: "PATCH", + url: `/api/org/test-default/models/${model.id}`, + headers: { cookie, "content-type": "application/json" }, + payload: { label: "Sonnet 5 (tool)", toolCapable: true, sortKey: "10" }, + }); + expect(upd.statusCode).toBe(200); + expect((upd.json() as { label: string }).label).toBe("Sonnet 5 (tool)"); + + const list = await app.inject({ + method: "GET", + url: "/api/org/test-default/models", + headers: { cookie }, + }); + expect((list.json() as { models: unknown[] }).models).toHaveLength(1); + + const del = await app.inject({ + method: "DELETE", + url: `/api/org/test-default/models/${model.id}`, + headers: { cookie }, + }); + expect(del.statusCode).toBe(200); + const after = await app.inject({ + method: "GET", + url: "/api/org/test-default/models", + headers: { cookie }, + }); + expect((after.json() as { models: unknown[] }).models).toHaveLength(0); + } finally { + await app.close(); + } + }); + + it("creates a role with tools whitelist and rejects unknown tool ids", async () => { + const token = await seedOwner(); + const app = await buildApp(); + try { + const cookie = sessionCookieHeader(token); + + const create = await app.inject({ + method: "POST", + url: "/api/org/test-default/roles", + headers: { cookie, "content-type": "application/json" }, + payload: { + roleId: "draft", + label: "草稿", + systemPrompt: "You are a teaching drafter.", + tools: ["read_file", "write_file", "cph_check"], + }, + }); + expect(create.statusCode).toBe(201); + const role = create.json() as { id: string; roleId: string; tools: string[] }; + expect(role.roleId).toBe("draft"); + expect(role.tools).toEqual(["read_file", "write_file", "cph_check"]); + + // Unknown tool id is rejected. + const bad = await app.inject({ + method: "POST", + url: "/api/org/test-default/roles", + headers: { cookie, "content-type": "application/json" }, + payload: { roleId: "bad", label: "bad", tools: ["not_a_real_tool"] }, + }); + expect(bad.statusCode).toBe(500); + + // null tools ⇒ unrestricted. + const unrestricted = await app.inject({ + method: "PATCH", + url: `/api/org/test-default/roles/${role.id}`, + headers: { cookie, "content-type": "application/json" }, + payload: { tools: null }, + }); + expect(unrestricted.statusCode).toBe(200); + expect((unrestricted.json() as { tools: string[] | null }).tools).toBeNull(); + } finally { + await app.close(); + } + }); + + it("DbRuntimeSettings reads org-scoped models/roles and falls back to env", async () => { + const token = await seedOwner(); + const app = await buildApp(); + try { + const cookie = sessionCookieHeader(token); + + await app.inject({ + method: "POST", + url: "/api/org/test-default/models", + headers: { cookie, "content-type": "application/json" }, + payload: { modelId: "z-ai/glm-4.7", label: "GLM", toolCapable: true }, + }); + await app.inject({ + method: "POST", + url: "/api/org/test-default/roles", + headers: { cookie, "content-type": "application/json" }, + payload: { roleId: "draft", label: "草稿", defaultModelId: "z-ai/glm-4.7" }, + }); + + await prisma.project.create({ + data: { id: "p-cfg", organizationId: DEFAULT_ORG_ID, name: "CfgProj", workspaceDir: "/tmp/p-cfg" }, + }); + + const settings = createDbRuntimeSettings(prisma, { + ANTHROPIC_AUTH_TOKEN: "env-token", + ANTHROPIC_DEFAULT_SONNET_MODEL: "anthropic/claude-sonnet-5", + }); + const registry = await settings.modelRegistry({ projectId: "p-cfg" }); + const models = registry.listModels(); + expect(models.map((m) => m.id)).toContain("z-ai/glm-4.7"); + const draft = registry.role("draft"); + expect(draft?.label).toBe("草稿"); + expect(registry.resolve(undefined, "draft")).toBe("z-ai/glm-4.7"); + + // Unknown project ⇒ env fallback registry. + const fallback = await settings.modelRegistry({ projectId: "no-such-project" }); + expect(fallback.listModels().map((m) => m.id)).toContain("anthropic/claude-sonnet-5"); + + // BYOK provider connection resolves org credentials. + await app.inject({ + method: "PUT", + url: "/api/org/test-default/provider-connection", + headers: { cookie, "content-type": "application/json" }, + payload: { mode: "BYOK", baseUrl: "https://gw.test/api", authToken: "byok-token" }, + }); + const provider = await settings.provider("openrouter", { projectId: "p-cfg" }); + expect(provider.baseUrl).toBe("https://gw.test/api"); + expect(provider.authToken).toBe("byok-token"); + } finally { + await app.close(); + } + }); +});