diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 409c4c6..e876465 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -31,7 +31,7 @@ jobs: run: bun lint - name: Type checks - run: bun check + run: bun typecheck - name: Tests unit run: bun test:unit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5eb5caa..2a078d1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ on: type: string permissions: - contents: write + contents: read concurrency: group: release @@ -27,6 +27,8 @@ concurrency: jobs: prepare: name: Prepare + permissions: + contents: write runs-on: ubuntu-latest outputs: tag: ${{ steps.version.outputs.tag }} @@ -57,7 +59,7 @@ jobs: run: bun install --frozen-lockfile - name: Run release checks - run: bun check && bun lint && bun test:unit + run: bun typecheck && bun lint && bun test:unit - name: Bump package version id: version @@ -151,6 +153,8 @@ jobs: build: name: Build + permissions: + contents: read needs: prepare strategy: fail-fast: false @@ -184,6 +188,7 @@ jobs: uses: actions/checkout@v6 with: ref: ${{ needs.prepare.outputs.tag }} + persist-credentials: false - name: Set up Bun uses: oven-sh/setup-bun@v2 @@ -224,6 +229,8 @@ jobs: release: name: Release + permissions: + contents: write needs: - prepare - build @@ -308,6 +315,8 @@ jobs: release-homebrew: name: Release to Homebrew + permissions: + contents: read needs: - prepare - release @@ -333,6 +342,8 @@ jobs: release-npm: name: Release to NPM + permissions: + contents: read needs: - prepare - release @@ -342,6 +353,7 @@ jobs: uses: actions/checkout@v6 with: ref: ${{ needs.prepare.outputs.tag }} + persist-credentials: false - name: Set up Node.js for npm publish uses: actions/setup-node@v6 diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml index 2d121d0..2f2d852 100644 --- a/.github/workflows/web.yml +++ b/.github/workflows/web.yml @@ -8,6 +8,13 @@ on: - ".github/workflows/web.yml" - "packages/web/**" +permissions: + contents: read + +concurrency: + group: web-deploy + cancel-in-progress: true + jobs: deploy: runs-on: ubuntu-latest @@ -15,6 +22,8 @@ jobs: steps: - name: Check out uses: actions/checkout@v6 + with: + persist-credentials: false - name: Set up Bun uses: oven-sh/setup-bun@v2 diff --git a/.github/workflows/youtube-token.yml b/.github/workflows/youtube-token.yml new file mode 100644 index 0000000..806d75b --- /dev/null +++ b/.github/workflows/youtube-token.yml @@ -0,0 +1,47 @@ +name: YouTube Token Worker + +on: + push: + branches: + - main + paths: + - ".github/workflows/youtube-token.yml" + - "packages/youtube-token/**" + +permissions: + contents: read + +concurrency: + group: youtube-token-deploy + cancel-in-progress: true + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Check out + uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Set up Bun + uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install --frozen-lockfile --filter @mdl/youtube-token + + - name: Type check + working-directory: packages/youtube-token + run: bun run typecheck + + - name: Test + working-directory: packages/youtube-token + run: bun run test + + - name: Deploy + working-directory: packages/youtube-token + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + run: bunx wrangler deploy diff --git a/.gitignore b/.gitignore index d87fac5..f70de68 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ music/ .DS_Store *.tsbuildinfo *.tgz +.wrangler diff --git a/AGENTS.md b/AGENTS.md index e51297c..48fc642 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,25 +1,20 @@ -You are an expert in TypeScript, Node.js, React, Tailwind CSS. +Use `./docs/CAVEMAN.md` for thinking and responses. +Env vars via Varlock (+ GCP Secret Manager). +New dependencies use the latest compatible release; the repo pins exact versions. +Use Conventional Commits for branches and commits. Subject under ~70 chars; body only when the why matters. -## Code Style and Structure +## Routing -Write concise, technical TypeScript code with accurate examples. -Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError). -Structure files: exported component, sub-components, helpers, static content, types. -Keep inline comments concise and limited for overlay complex code, favour using debug logging to explain what code is doing. Avoid adding huge chunks of commented code explaining the code. +Load the smallest relevant doc set for the task: -## Naming Conventions +- Open `./docs/TYPESCRIPT.md` only when editing TypeScript or JavaScript files. +- Open `./docs/TESTING.md` only when editing tests, mocks, or test infra. +- Open `./docs/SHELL.md` only when editing shell scripts or `run` files. +- Open `./docs/GH_WORKFLOWS.md` only when editing `.github/workflows/*`. -Use lowercase for directories and Capitalization only for Class constructor or Components (e.g., components/AuthWizard), for helpers/lib/utils use lowercase. -Favor named exports for components. +## Naming -## TypeScript Usage - -Use TypeScript for all code; prefer types over interfaces. -Avoid enums; use maps instead. -Use functional components with TypeScript types. - -## Syntax and Formatting - -Use the "function" keyword for pure functions. -Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements. -Use declarative JSX. +- `camelCase` for directories, files, and the default fallback. +- `PascalCase` for React components and class constructor files. +- `UPPER_SNAKE_CASE` for markdown files. +- Preserve framework-required route filenames. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 053883a..3048696 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,11 +22,11 @@ Run the same checks expected in pull requests before opening one: ```bash bun lint -bun check +bun typecheck bun test ``` -The end-to-end test downloads media and can take several minutes to finish. +Run `bun test:youtube-smoke` separately to download and validate media. ## Pull requests diff --git a/biome.json b/biome.json index 5f61dd7..96b1ca1 100644 --- a/biome.json +++ b/biome.json @@ -5,11 +5,21 @@ }, "files": { "ignoreUnknown": true, - "includes": ["packages/**/*.ts", "packages/**/*.tsx", "*.json", "*.md"] + "includes": [ + "packages/**/*.ts", + "packages/**/*.tsx", + "*.json", + "*.md", + "!**/.wrangler", + "!**/.output", + "!**/dist", + "!**/build" + ] }, "formatter": { "enabled": true, - "indentStyle": "space" + "indentStyle": "space", + "lineWidth": 120 }, "linter": { "enabled": true, diff --git a/bun.lock b/bun.lock index 38904c1..9c40581 100644 --- a/bun.lock +++ b/bun.lock @@ -8,12 +8,12 @@ "@biomejs/biome": "2.4.8", "@types/bun": "latest", "lefthook": "2.1.5", - "typescript": "6.0.2", + "typescript": "7.0.2", }, }, "packages/cli": { "name": "@mdlx/cli", - "version": "0.0.23", + "version": "0.0.24", "bin": { "mdl": "release/cli.js", }, @@ -58,6 +58,18 @@ "wrangler": "4.14.4", }, }, + "packages/youtube-token": { + "name": "@mdl/youtube-token", + "version": "0.0.0", + "dependencies": { + "@cloudflare/puppeteer": "1.1.0", + "bgutils-js": "3.2.0", + }, + "devDependencies": { + "@cloudflare/workers-types": "5.20260710.1", + "wrangler": "4.110.0", + }, + }, }, "packages": { "@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.3.0", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-p+CMKJ93HFmLkjXKlXiVGlMQEuRb6H0MokBSwUsX+S6BRX8eV5naFZpQJFfJHjRZY0Hmnqy1/r6UWl3x+19zYA=="], @@ -86,6 +98,8 @@ "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.0", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA=="], + "@cloudflare/puppeteer": ["@cloudflare/puppeteer@1.1.0", "", { "dependencies": { "@puppeteer/browsers": "2.2.4", "debug": "^4.3.5", "devtools-protocol": "0.0.1299070", "ws": "^8.18.0" } }, "sha512-lN10En49avRDQvz8Gpv/WiIoGvjjDiP6P+v2y9bA6rfPT5WHfnlg2O6MwjHc1POm8A9LXf1sMdgrsdW8xWapOw=="], + "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.3.1", "", { "peerDependencies": { "unenv": "2.0.0-rc.15", "workerd": "^1.20250320.0" }, "optionalPeers": ["workerd"] }, "sha512-Xq57Qd+ADpt6hibcVBO0uLG9zzRgyRhfCUgBT9s+g3+3Ivg5zDyVgLFy40ES1VdNcu8rPNSivm9A+kGP5IVaPg=="], "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250507.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-xC+8hmQuOUUNCVT9DWpLMfxhR4Xs4kI8v7Bkybh4pzGC85moH6fMfCBNaP0YQCNAA/BR56aL/AwfvMVGskTK/A=="], @@ -150,7 +164,7 @@ "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw=="], - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="], "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q=="], @@ -162,6 +176,8 @@ "@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="], + "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="], "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="], @@ -174,6 +190,10 @@ "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA=="], + "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="], + + "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="], + "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.0.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA=="], "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw=="], @@ -186,6 +206,10 @@ "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA=="], + "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="], + + "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="], + "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.0.4" }, "os": "linux", "cpu": "s390x" }, "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q=="], "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA=="], @@ -196,6 +220,8 @@ "@img/sharp-wasm32": ["@img/sharp-wasm32@0.33.5", "", { "dependencies": { "@emnapi/runtime": "^1.2.0" }, "cpu": "none" }, "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg=="], + "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="], + "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.33.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ=="], "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="], @@ -210,12 +236,22 @@ "@mdl/web": ["@mdl/web@workspace:packages/web"], + "@mdl/youtube-token": ["@mdl/youtube-token@workspace:packages/youtube-token"], + "@mdlx/cli": ["@mdlx/cli@workspace:packages/cli"], "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw=="], "@oxc-project/types": ["@oxc-project/types@0.122.0", "", {}, "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA=="], + "@poppinss/colors": ["@poppinss/colors@4.1.6", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="], + + "@poppinss/dumper": ["@poppinss/dumper@0.6.5", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw=="], + + "@poppinss/exception": ["@poppinss/exception@1.2.3", "", {}, "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw=="], + + "@puppeteer/browsers": ["@puppeteer/browsers@2.2.4", "", { "dependencies": { "debug": "^4.3.5", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.4.0", "semver": "^7.6.2", "tar-fs": "^3.0.6", "unbzip2-stream": "^1.4.3", "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" } }, "sha512-BdG2qiI1dn89OTUUsx2GZSpUzW+DRffR1wlMJyKxVHYrhnKoELSDxDd+2XImUkuWPEKk76H5FcM/gPFrEK1Tfw=="], + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.11", "", { "os": "android", "cpu": "arm64" }, "sha512-SJ+/g+xNnOh6NqYxD0V3uVN4W3VfnrGsC9/hoglicgTNfABFG9JjISvkkU0dNY84MNHLWyOgxP9v9Y9pX4S7+A=="], "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-7WQgR8SfOPwmDZGFkThUvsmd/nwAWv91oCO4I5LS7RKrssPZmOt7jONN0cW17ydGC1n/+puol1IpoieKqQidmg=="], @@ -298,6 +334,10 @@ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ=="], + "@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="], + + "@speed-highlight/core": ["@speed-highlight/core@1.2.17", "", {}, "sha512-Z92FwKpCtfaW1V0jTU/fh3QzYEZN8wDwrzRIBoADCJfn4mJCNcJN/XegifX7BDrQ8/h9Xh/JnbyMchL0FqXrkg=="], + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "@tailwindcss/node": ["@tailwindcss/node@4.1.3", "", { "dependencies": { "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.29.2", "tailwindcss": "4.1.3" } }, "sha512-H/6r6IPFJkCfBJZ2dKZiPJ7Ueb2wbL592+9bQEl2r73qbX6yGnmQVIfiUvDRB2YI0a3PWDrzUwkvQx1XW1bNkA=="], @@ -328,6 +368,8 @@ "@tailwindcss/vite": ["@tailwindcss/vite@4.1.3", "", { "dependencies": { "@tailwindcss/node": "4.1.3", "@tailwindcss/oxide": "4.1.3", "tailwindcss": "4.1.3" }, "peerDependencies": { "vite": "^5.2.0 || ^6" } }, "sha512-lUI/QaDxLtlV52Lho6pu07CG9pSnRYLOPmKGIQjyHdTBagemc6HmgZxyjGAQ/5HMPrNeWBfTVIpQl0/jLXvWHQ=="], + "@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="], + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], "@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="], @@ -340,12 +382,56 @@ "@types/react-dom": ["@types/react-dom@19.2.2", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw=="], + "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="], + + "@typescript/typescript-aix-ppc64": ["@typescript/typescript-aix-ppc64@7.0.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ=="], + + "@typescript/typescript-darwin-arm64": ["@typescript/typescript-darwin-arm64@7.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA=="], + + "@typescript/typescript-darwin-x64": ["@typescript/typescript-darwin-x64@7.0.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA=="], + + "@typescript/typescript-freebsd-arm64": ["@typescript/typescript-freebsd-arm64@7.0.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ=="], + + "@typescript/typescript-freebsd-x64": ["@typescript/typescript-freebsd-x64@7.0.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw=="], + + "@typescript/typescript-linux-arm": ["@typescript/typescript-linux-arm@7.0.2", "", { "os": "linux", "cpu": "arm" }, "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ=="], + + "@typescript/typescript-linux-arm64": ["@typescript/typescript-linux-arm64@7.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ=="], + + "@typescript/typescript-linux-loong64": ["@typescript/typescript-linux-loong64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ=="], + + "@typescript/typescript-linux-mips64el": ["@typescript/typescript-linux-mips64el@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA=="], + + "@typescript/typescript-linux-ppc64": ["@typescript/typescript-linux-ppc64@7.0.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA=="], + + "@typescript/typescript-linux-riscv64": ["@typescript/typescript-linux-riscv64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ=="], + + "@typescript/typescript-linux-s390x": ["@typescript/typescript-linux-s390x@7.0.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw=="], + + "@typescript/typescript-linux-x64": ["@typescript/typescript-linux-x64@7.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A=="], + + "@typescript/typescript-netbsd-arm64": ["@typescript/typescript-netbsd-arm64@7.0.2", "", { "os": "none", "cpu": "arm64" }, "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA=="], + + "@typescript/typescript-netbsd-x64": ["@typescript/typescript-netbsd-x64@7.0.2", "", { "os": "none", "cpu": "x64" }, "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA=="], + + "@typescript/typescript-openbsd-arm64": ["@typescript/typescript-openbsd-arm64@7.0.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ=="], + + "@typescript/typescript-openbsd-x64": ["@typescript/typescript-openbsd-x64@7.0.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg=="], + + "@typescript/typescript-sunos-x64": ["@typescript/typescript-sunos-x64@7.0.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g=="], + + "@typescript/typescript-win32-arm64": ["@typescript/typescript-win32-arm64@7.0.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ=="], + + "@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="], + "@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="], "acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="], "acorn-walk": ["acorn-walk@8.3.2", "", {}, "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A=="], + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + "ansi-escapes": ["ansi-escapes@7.3.0", "", { "dependencies": { "environment": "1.1.0" } }, "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg=="], "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], @@ -354,10 +440,36 @@ "as-table": ["as-table@1.0.55", "", { "dependencies": { "printable-characters": "^1.0.42" } }, "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ=="], + "ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="], + "auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="], + "b4a": ["b4a@1.8.1", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw=="], + + "bare-events": ["bare-events@2.9.1", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg=="], + + "bare-fs": ["bare-fs@4.7.2", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-aTvMFUWkBmjzKtEQMDGGDNF8bkfpD5N1b/FCwt7A3wrU4t1o/e/85Wzkluh6JlODCjqVESYCkQCdTXqZ9G7VFg=="], + + "bare-os": ["bare-os@3.9.1", "", {}, "sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ=="], + + "bare-path": ["bare-path@3.0.1", "", { "dependencies": { "bare-os": "^3.0.1" } }, "sha512-ghj2DSK/2e99a1anTVPCV4m4YIYtrbXhfM7V3D7XZLOTsybnYyaJloymGqssQc8l/or0UoDyRtNQkmkEF/ysgQ=="], + + "bare-stream": ["bare-stream@2.13.1", "", { "dependencies": { "streamx": "^2.25.0", "teex": "^1.0.1" }, "peerDependencies": { "bare-abort-controller": "*", "bare-buffer": "*", "bare-events": "*" }, "optionalPeers": ["bare-abort-controller", "bare-buffer", "bare-events"] }, "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow=="], + + "bare-url": ["bare-url@2.4.5", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "basic-ftp": ["basic-ftp@5.3.1", "", {}, "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw=="], + + "bgutils-js": ["bgutils-js@3.2.0", "", {}, "sha512-CacO15JvxbclbLeCAAm9DETGlLuisRGWpPigoRvNsccSCPEC4pwYwA2g2x/pv7Om/sk79d4ib35V5HHmxPBpDg=="], + "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="], + "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + + "buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], + "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], @@ -370,6 +482,8 @@ "cli-truncate": ["cli-truncate@6.0.0", "", { "dependencies": { "slice-ansi": "^9.0.0", "string-width": "^8.2.0" } }, "sha512-3+YKIUFsohD9MIoOFPFBldjAlnfCmCDcqe6aYGFqlDTRKg80p4wg35L+j83QQ63iOlKRccEkbn8IuM++HsgEjA=="], + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + "code-excerpt": ["code-excerpt@4.0.0", "", { "dependencies": { "convert-to-spaces": "2.0.1" } }, "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA=="], "color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="], @@ -390,44 +504,84 @@ "data-uri-to-buffer": ["data-uri-to-buffer@2.0.2", "", {}, "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA=="], + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], + "degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="], + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "devtools-protocol": ["devtools-protocol@0.0.1299070", "", {}, "sha512-+qtL3eX50qsJ7c+qVyagqi7AWMoQCBGNfoyJZMwm/NSXVqLYbuitrWEEIzxfUmTNy7//Xe8yhMmQ+elj3uAqSg=="], + "effect": ["effect@3.21.0", "", { "dependencies": { "@standard-schema/spec": "1.1.0", "fast-check": "3.23.2" } }, "sha512-PPN80qRokCd1f015IANNhrwOnLO7GrrMQfk4/lnZRE/8j7UPWrNNjPV0uBrZutI/nHzernbW+J0hdqQysHiSnQ=="], - "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], "enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="], "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], + "error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="], + "es-toolkit": ["es-toolkit@1.45.1", "", {}, "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw=="], "esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="], + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + "escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], + "escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "esgenerate": "bin/esgenerate.js", "escodegen": "bin/escodegen.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="], + + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "events-universal": ["events-universal@1.0.1", "", { "dependencies": { "bare-events": "^2.7.0" } }, "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="], + "exit-hook": ["exit-hook@2.2.1", "", {}, "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw=="], "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], + "extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="], + "fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], + "fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="], + + "fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="], + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + "get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="], "get-source": ["get-source@2.0.12", "", { "dependencies": { "data-uri-to-buffer": "^2.0.0", "source-map": "^0.6.1" } }, "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w=="], + "get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="], + + "get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="], + "glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="], "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], "has-flag": ["has-flag@5.0.1", "", {}, "sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA=="], + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="], "ink": ["ink@7.0.0", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.3.0", "ansi-escapes": "^7.3.0", "ansi-styles": "^6.2.3", "auto-bind": "^5.0.1", "chalk": "^5.6.2", "cli-boxes": "^4.0.1", "cli-cursor": "^4.0.0", "cli-truncate": "^6.0.0", "code-excerpt": "^4.0.0", "es-toolkit": "^1.45.1", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.33.0", "scheduler": "^0.27.0", "signal-exit": "^3.0.7", "slice-ansi": "^9.0.0", "stack-utils": "^2.0.6", "string-width": "^8.2.0", "terminal-size": "^4.0.1", "type-fest": "^5.5.0", "widest-line": "^6.0.0", "wrap-ansi": "^10.0.0", "ws": "^8.20.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.2.0", "react": ">=19.2.0", "react-devtools-core": ">=6.1.2" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-fMie5/VwIYXofMyND0s+fOVhwVBBPYx+uuqJ6V6rUBGjui+2UYp+0fWtvhSeKT4z+X1uH98a4ge5Vj3aTlL6mg=="], @@ -440,6 +594,8 @@ "ink-testing-library": ["ink-testing-library@4.0.0", "", { "optionalDependencies": { "@types/react": "19.2.2" } }, "sha512-yF92kj3pmBvk7oKbSq5vEALO//o7Z9Ck/OaLNlkzXNeYdwfpxMQkSowGTFUCS5MSu9bWfSZMewGpp7bFc66D7Q=="], + "ip-address": ["ip-address@10.2.0", "", {}, "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA=="], + "is-arrayish": ["is-arrayish@0.3.4", "", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="], "is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "1.5.0" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], @@ -448,6 +604,8 @@ "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + "kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], + "lefthook": ["lefthook@2.1.5", "", { "optionalDependencies": { "lefthook-darwin-arm64": "2.1.5", "lefthook-darwin-x64": "2.1.5", "lefthook-freebsd-arm64": "2.1.5", "lefthook-freebsd-x64": "2.1.5", "lefthook-linux-arm64": "2.1.5", "lefthook-linux-x64": "2.1.5", "lefthook-openbsd-arm64": "2.1.5", "lefthook-openbsd-x64": "2.1.5", "lefthook-windows-arm64": "2.1.5", "lefthook-windows-x64": "2.1.5" }, "bin": { "lefthook": "bin/index.js" } }, "sha512-yB9IFWurFllusbPZqvG0EavTmpNXPya2MuO7Li7YT78xAj3uCQ3AgmW9TVUbTTsSMhsegbiAMRpwfEk2TP1P0A=="], "lefthook-darwin-arm64": ["lefthook-darwin-arm64@2.1.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-VITTaw8PxxyE26gkZ8UcwIa5ZrWnKNRGLeeSrqri40cQdXvLTEoMq2tjjw7eiL9UcB0waRReDdzydevy9GOPUQ=="], @@ -494,6 +652,8 @@ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], + "meriyah": ["meriyah@6.1.4", "", {}, "sha512-Sz8FzjzI0kN13GK/6MVEsVzMZEPvOhnmmI1lU5+/1cGOiK3QUahntrNNtdVeihrO7t9JpoH75iMNXg6R6uWflQ=="], "mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="], @@ -502,20 +662,32 @@ "miniflare": ["miniflare@4.20250507.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "stoppable": "1.1.0", "undici": "^5.28.5", "workerd": "1.20250507.0", "ws": "8.18.0", "youch": "3.3.4", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-EgbQRt/Hnr8HCmW2J/4LRNE3yOzJTdNd98XJ8gnGXFKcimXxUFPiWP3k1df+ZPCtEHp6cXxi8+jP7v9vuIbIsg=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="], "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "netmask": ["netmask@2.1.1", "", {}, "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA=="], + "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + "pac-proxy-agent": ["pac-proxy-agent@7.2.0", "", { "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", "agent-base": "^7.1.2", "debug": "^4.3.4", "get-uri": "^6.0.1", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.6", "pac-resolver": "^7.0.1", "socks-proxy-agent": "^8.0.5" } }, "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA=="], + + "pac-resolver": ["pac-resolver@7.0.1", "", { "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" } }, "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg=="], + "patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="], "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + "pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], @@ -524,6 +696,14 @@ "printable-characters": ["printable-characters@1.0.42", "", {}, "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ=="], + "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], + + "proxy-agent": ["proxy-agent@6.5.0", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.5" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="], + + "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], + + "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], + "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], "react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="], @@ -534,6 +714,8 @@ "react-reconciler": ["react-reconciler@0.33.0", "", { "dependencies": { "scheduler": "0.27.0" }, "peerDependencies": { "react": "19.2.0" } }, "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA=="], + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + "restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "5.1.2", "signal-exit": "3.0.7" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="], "rolldown": ["rolldown@1.0.0-rc.11", "", { "dependencies": { "@oxc-project/types": "=0.122.0", "@rolldown/pluginutils": "1.0.0-rc.11" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.11", "@rolldown/binding-darwin-arm64": "1.0.0-rc.11", "@rolldown/binding-darwin-x64": "1.0.0-rc.11", "@rolldown/binding-freebsd-x64": "1.0.0-rc.11", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.11", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.11", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.11", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.11", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.11", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.11", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.11", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.11", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.11", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.11", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.11" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-NRjoKMusSjfRbSYiH3VSumlkgFe7kYAa3pzVOsVYVFY3zb5d7nS+a3KGQ7hJKXuYWbzJKPVQ9Wxq2UvyK+ENpw=="], @@ -556,6 +738,12 @@ "slice-ansi": ["slice-ansi@9.0.0", "", { "dependencies": { "ansi-styles": "^6.2.3", "is-fullwidth-code-point": "^5.1.0" } }, "sha512-SO/3iYL5S3W57LLEniscOGPZgOqZUPCx6d3dB+52B80yJ0XstzsC/eV8gnA4tM3MHDrKz+OCFSLNjswdSC+/bA=="], + "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], + + "socks": ["socks@2.8.9", "", { "dependencies": { "ip-address": "^10.1.1", "smart-buffer": "^4.2.0" } }, "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw=="], + + "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="], + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], @@ -566,6 +754,8 @@ "stoppable": ["stoppable@1.1.0", "", {}, "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw=="], + "streamx": ["streamx@2.27.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-WZ189TKnHoAokYHvwzaAQMpd55cgUmFIcJFzBSgGcb886jau5DL+XdDhTWV4ps3FLvk+OORp0dLRTPsLZ21CSA=="], + "string-width": ["string-width@8.2.0", "", { "dependencies": { "get-east-asian-width": "1.5.0", "strip-ansi": "7.2.0" } }, "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw=="], "strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], @@ -580,10 +770,20 @@ "tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="], + "tar-fs": ["tar-fs@3.1.2", "", { "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" }, "optionalDependencies": { "bare-fs": "^4.0.1", "bare-path": "^3.0.0" } }, "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw=="], + + "tar-stream": ["tar-stream@3.2.0", "", { "dependencies": { "b4a": "^1.6.4", "bare-fs": "^4.5.5", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg=="], + + "teex": ["teex@1.0.1", "", { "dependencies": { "streamx": "^2.12.5" } }, "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg=="], + "terminal-link": ["terminal-link@5.0.0", "", { "dependencies": { "ansi-escapes": "^7.0.0", "supports-hyperlinks": "^4.1.0" } }, "sha512-qFAy10MTMwjzjU8U16YS4YoZD+NQLHzLssFMNqgravjbvIPNiqkGFR4yjhJfmY9R5OFU7+yHxc6y+uGHkKwLRA=="], "terminal-size": ["terminal-size@4.0.1", "", {}, "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ=="], + "text-decoder": ["text-decoder@1.2.7", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ=="], + + "through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="], + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], "truncate-utf8-bytes": ["truncate-utf8-bytes@1.0.2", "", { "dependencies": { "utf8-byte-length": "1.0.5" } }, "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ=="], @@ -592,10 +792,12 @@ "type-fest": ["type-fest@5.5.0", "", { "dependencies": { "tagged-tag": "1.0.0" } }, "sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g=="], - "typescript": ["typescript@6.0.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ=="], + "typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="], "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], + "unbzip2-stream": ["unbzip2-stream@1.4.3", "", { "dependencies": { "buffer": "^5.2.1", "through": "^2.3.8" } }, "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg=="], + "undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], @@ -614,23 +816,45 @@ "wrap-ansi": ["wrap-ansi@10.0.0", "", { "dependencies": { "ansi-styles": "^6.2.3", "string-width": "^8.2.0", "strip-ansi": "^7.1.2" } }, "sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ=="], - "ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], "youch": ["youch@3.3.4", "", { "dependencies": { "cookie": "^0.7.1", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg=="], + "youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="], + "youtubei.js": ["youtubei.js@17.0.1", "", { "dependencies": { "@bufbuild/protobuf": "2.11.0", "meriyah": "6.1.4" } }, "sha512-1lO4b8UqMDzE0oh2qEGzbBOd4UYRdxn/4PdpRM7BGTHxM6ddsEsKZTu90jp8V9FHVgC2h1UirQyqoqLiKwl+Zg=="], "zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="], + "@mdl/youtube-token/@cloudflare/workers-types": ["@cloudflare/workers-types@5.20260710.1", "", {}, "sha512-4ooaY2Pb5XGwDn8Fzm6jnTAJkIX0R5LBvL9euQpp2T58sQItlAQd9yivAlkwGhpY5cM1u81/9HaXwKAjXwtyzA=="], + + "@mdl/youtube-token/wrangler": ["wrangler@4.110.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.5.0", "@cloudflare/unenv-preset": "2.16.1", "blake3-wasm": "2.1.5", "esbuild": "0.28.1", "miniflare": "4.20260708.1", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260708.1" }, "optionalDependencies": { "fsevents": "2.3.3" }, "peerDependencies": { "@cloudflare/workers-types": "^5.20260708.1" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "cf-wrangler": "bin/cf-wrangler.js", "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-xZeXKYi7hxQRF5anL+v77RkufJNpF9f3Eqeyqq2QBsETpLZgh0Agj0jJ6JPtkbgn6ukZdh8OK5egsGPWIditgg=="], + "@tailwindcss/node/lightningcss": ["lightningcss@1.29.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.29.2", "lightningcss-darwin-x64": "1.29.2", "lightningcss-freebsd-x64": "1.29.2", "lightningcss-linux-arm-gnueabihf": "1.29.2", "lightningcss-linux-arm64-gnu": "1.29.2", "lightningcss-linux-arm64-musl": "1.29.2", "lightningcss-linux-x64-gnu": "1.29.2", "lightningcss-linux-x64-musl": "1.29.2", "lightningcss-win32-arm64-msvc": "1.29.2", "lightningcss-win32-x64-msvc": "1.29.2" } }, "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA=="], "@tailwindcss/vite/vite": ["vite@6.3.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.3", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.12" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-kkzzkqtMESYklo96HKKPE5KKLkC1amlsqt+RjFMlX2AvbRB/0wghap19NdBxxwGZ+h/C6DLCrcEphPIItlGrRQ=="], "bun-types/@types/node": ["@types/node@24.6.0", "", { "dependencies": { "undici-types": "7.13.0" } }, "sha512-F1CBxgqwOMc4GKJ7eY22hWhBVQuMYTtqI8L0FcszYcpYX0fzfDGpez22Xau8Mgm7O9fI+zA/TYIdq3tGWfweBA=="], - "ink/ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="], + "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "get-uri/data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="], "ink-scroll-view/ink": ["ink@6.8.0", "", { "dependencies": { "@alcalzone/ansi-tokenize": "0.2.5", "ansi-escapes": "7.3.0", "ansi-styles": "6.2.3", "auto-bind": "5.0.1", "chalk": "5.6.2", "cli-boxes": "3.0.0", "cli-cursor": "4.0.0", "cli-truncate": "5.2.0", "code-excerpt": "4.0.0", "es-toolkit": "1.45.1", "indent-string": "5.0.0", "is-in-ci": "2.0.0", "patch-console": "2.0.0", "react-reconciler": "0.33.0", "scheduler": "0.27.0", "signal-exit": "3.0.7", "slice-ansi": "8.0.0", "stack-utils": "2.0.6", "string-width": "8.2.0", "terminal-size": "4.0.1", "type-fest": "5.5.0", "widest-line": "6.0.0", "wrap-ansi": "9.0.2", "ws": "8.19.0", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@types/react": "19.2.2" }, "peerDependencies": { "react": "19.2.0" } }, "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA=="], @@ -638,8 +862,24 @@ "miniflare/ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], + "react-devtools-core/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], + "rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.11", "", {}, "sha512-xQO9vbwBecJRv9EUcQ/y0dzSTJgA7Q6UVN7xp6B81+tBGSLVAK03yJ9NkJaUA7JFD91kbjxRSC/mDnmvXzbHoQ=="], + "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "@mdl/youtube-token/wrangler/@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.5.0", "", {}, "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg=="], + + "@mdl/youtube-token/wrangler/@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.16.1", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": ">1.20260305.0 <2.0.0-0" }, "optionalPeers": ["workerd"] }, "sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw=="], + + "@mdl/youtube-token/wrangler/esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="], + + "@mdl/youtube-token/wrangler/miniflare": ["miniflare@4.20260708.1", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "0.34.5", "undici": "7.28.0", "workerd": "1.20260708.1", "ws": "8.21.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-c94O9zRDISdqO18EHt6l0iF/fWgWt8p18PJvRsA/L/NJZ9Cfke3s/F5Blg1XXF7WDutVRzWVWy8Vy4LaT5ifsA=="], + + "@mdl/youtube-token/wrangler/unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="], + + "@mdl/youtube-token/wrangler/workerd": ["workerd@1.20260708.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260708.1", "@cloudflare/workerd-darwin-arm64": "1.20260708.1", "@cloudflare/workerd-linux-64": "1.20260708.1", "@cloudflare/workerd-linux-arm64": "1.20260708.1", "@cloudflare/workerd-windows-64": "1.20260708.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-WAK+Kt/VVCSldH2qSr8lx46XCJ4Q+bdlHNaFqUtOHthBEIB8C1N8HVW+VOLrxDoTCk0NGNv0zajnBeQK4JOB9w=="], + "@tailwindcss/node/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.29.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA=="], "@tailwindcss/node/lightningcss/lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.29.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w=="], @@ -664,6 +904,12 @@ "bun-types/@types/node/undici-types": ["undici-types@7.13.0", "", {}, "sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ=="], + "cliui/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "ink-scroll-view/ink/@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.5", "", { "dependencies": { "ansi-styles": "6.2.3", "is-fullwidth-code-point": "5.1.0" } }, "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw=="], "ink-scroll-view/ink/cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="], @@ -688,6 +934,78 @@ "ink-spinner/ink/ws": ["ws@8.19.0", "", {}, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="], + "yargs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "@mdl/youtube-token/wrangler/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], + + "@mdl/youtube-token/wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="], + + "@mdl/youtube-token/wrangler/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="], + + "@mdl/youtube-token/wrangler/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="], + + "@mdl/youtube-token/wrangler/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="], + + "@mdl/youtube-token/wrangler/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="], + + "@mdl/youtube-token/wrangler/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="], + + "@mdl/youtube-token/wrangler/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="], + + "@mdl/youtube-token/wrangler/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="], + + "@mdl/youtube-token/wrangler/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="], + + "@mdl/youtube-token/wrangler/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="], + + "@mdl/youtube-token/wrangler/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="], + + "@mdl/youtube-token/wrangler/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="], + + "@mdl/youtube-token/wrangler/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="], + + "@mdl/youtube-token/wrangler/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="], + + "@mdl/youtube-token/wrangler/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="], + + "@mdl/youtube-token/wrangler/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="], + + "@mdl/youtube-token/wrangler/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="], + + "@mdl/youtube-token/wrangler/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="], + + "@mdl/youtube-token/wrangler/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="], + + "@mdl/youtube-token/wrangler/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="], + + "@mdl/youtube-token/wrangler/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="], + + "@mdl/youtube-token/wrangler/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="], + + "@mdl/youtube-token/wrangler/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="], + + "@mdl/youtube-token/wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="], + + "@mdl/youtube-token/wrangler/miniflare/sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], + + "@mdl/youtube-token/wrangler/miniflare/undici": ["undici@7.28.0", "", {}, "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA=="], + + "@mdl/youtube-token/wrangler/miniflare/ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], + + "@mdl/youtube-token/wrangler/miniflare/youch": ["youch@4.1.0-beta.10", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.4", "@speed-highlight/core": "^1.2.7", "cookie": "^1.0.2", "youch-core": "^0.3.3" } }, "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ=="], + + "@mdl/youtube-token/wrangler/workerd/@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260708.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-HXFCvhS1wpg3uXO0CLUwmwC41i2loM5FSK69EUchOBpmYBAXxT1oHLm6EOA5lqhTk5Mu9kjRiQYxa1GwKPwfJg=="], + + "@mdl/youtube-token/wrangler/workerd/@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260708.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JVlJaKDoRTVKSroHIlf8g3UCPjKj4iDbMZE2CNYht5qQ+2rL0FAUiVlV82G3BqKnnw9kHYnnsMzC08b9zVtdzA=="], + + "@mdl/youtube-token/wrangler/workerd/@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260708.1", "", { "os": "linux", "cpu": "x64" }, "sha512-3daE60YdD7YX0Jtuzc9DE/r/qMkmx8ZvHTkF8Mzmp3F5tbzlV0DAzmu5PFUPF2WuvtKbAhZKbvC2cHmWpQYxnA=="], + + "@mdl/youtube-token/wrangler/workerd/@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260708.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-VLdNYOx5Hj+9C6isy0ACWZsbMtSxex2DIJWEe7cZxUdlphZ58ZT8zxNXK8yunFiowd34hn3VwGMopdvdj8lvmA=="], + + "@mdl/youtube-token/wrangler/workerd/@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260708.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bC/aSAwLy16Vjo24i9XU3aWH+eRgz7NeR5xPKavGbembO18ZywYTQbXh14eXtY6fAqN3RzRG8psijTdhX4xydA=="], + "@tailwindcss/vite/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], "@tailwindcss/vite/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], @@ -730,6 +1048,8 @@ "@tailwindcss/vite/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + "@tailwindcss/vite/vite/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], + "@tailwindcss/vite/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], "@tailwindcss/vite/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], @@ -741,5 +1061,51 @@ "ink-scroll-view/ink/wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "10.6.0", "get-east-asian-width": "1.5.0", "strip-ansi": "7.2.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], "ink-spinner/ink/wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "10.6.0", "get-east-asian-width": "1.5.0", "strip-ansi": "7.2.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "@mdl/youtube-token/wrangler/miniflare/sharp/@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], + + "@mdl/youtube-token/wrangler/miniflare/sharp/@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="], + + "@mdl/youtube-token/wrangler/miniflare/sharp/@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="], + + "@mdl/youtube-token/wrangler/miniflare/sharp/@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="], + + "@mdl/youtube-token/wrangler/miniflare/sharp/@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="], + + "@mdl/youtube-token/wrangler/miniflare/sharp/@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="], + + "@mdl/youtube-token/wrangler/miniflare/sharp/@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="], + + "@mdl/youtube-token/wrangler/miniflare/sharp/@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="], + + "@mdl/youtube-token/wrangler/miniflare/sharp/@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="], + + "@mdl/youtube-token/wrangler/miniflare/sharp/@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="], + + "@mdl/youtube-token/wrangler/miniflare/sharp/@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="], + + "@mdl/youtube-token/wrangler/miniflare/sharp/@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="], + + "@mdl/youtube-token/wrangler/miniflare/sharp/@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="], + + "@mdl/youtube-token/wrangler/miniflare/sharp/@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="], + + "@mdl/youtube-token/wrangler/miniflare/sharp/@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="], + + "@mdl/youtube-token/wrangler/miniflare/sharp/@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="], + + "@mdl/youtube-token/wrangler/miniflare/sharp/@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="], + + "@mdl/youtube-token/wrangler/miniflare/sharp/@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="], + + "@mdl/youtube-token/wrangler/miniflare/sharp/@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], + + "@mdl/youtube-token/wrangler/miniflare/youch/cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], + + "ink-scroll-view/ink/wrap-ansi/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + + "ink-spinner/ink/wrap-ansi/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], } } diff --git a/docs/CAVEMAN.md b/docs/CAVEMAN.md new file mode 100644 index 0000000..7e025e2 --- /dev/null +++ b/docs/CAVEMAN.md @@ -0,0 +1,26 @@ +Respond terse. Keep full technical meaning. Cut fluff. + +Persistent default for every response. No filler drift. + +## Rules + +Drop articles, filler, pleasantries, and hedging. Fragments OK. Use short words when meaning stays exact. Keep technical terms exact. Keep code blocks and error text exact. + +Pattern: `[thing] [action] [reason]. [next step].` + +Use compact forms when clear: `DB`, `auth`, `config`, `req`, `res`, `fn`, `impl`. Prefer arrows for causality: `X → Y`. + +## Auto-Clarity + +Temporarily drop caveman mode for: + +- security warnings +- irreversible actions +- multi-step instructions where order could be misread +- explicit requests for clarification + +Resume terse mode after the clear section. + +## Boundaries + +Code, commits, and PR text stay normal. diff --git a/docs/GH_WORKFLOWS.md b/docs/GH_WORKFLOWS.md new file mode 100644 index 0000000..8bec43a --- /dev/null +++ b/docs/GH_WORKFLOWS.md @@ -0,0 +1,22 @@ +If you touch a workflow and an existing action used in the repo is stale, upgrade it and keep versions consistent across workflows. +For new actions, use the current stable major. +Do not set default values for actions in env/with, rely on default values being set. + +Order keys in steps like this: + +1. id +2. name +3. working-directory +4. env +5. run + +Example: + +```yaml +- id: deploy + name: Deploy + working-directory: apps/api + env: + APP_ENV: beta + run: ./run "deploy:${APP_ENV}" +``` diff --git a/docs/PULL_REQUESTS.md b/docs/PULL_REQUESTS.md new file mode 100644 index 0000000..283f341 --- /dev/null +++ b/docs/PULL_REQUESTS.md @@ -0,0 +1,11 @@ +# Pull Requests + +## Size + +Keep PRs small. Soft target: **<= 200 lines net diff, <= 10 files** (lockfile + generated artifacts excluded). **Hard split** if the planned slice heads past **1,000 lines or 35 files**: break the deliverable into smaller PRs that ship one at a time. Reviewer fatigue sets in around 400 lines; if the review needs more than one sitting, the PR is too big. + +If the work does not naturally split, the slice goal is too big. Re-plan instead of pushing through. Mechanical-only PRs (renames, dependency bumps, generated-file regen, doc moves) can go larger; flag that in the title or first line. + +## Task Chunking + +Plan in the smallest shippable units. When breaking down work, prefer one concern per branch: multiple small PRs over one bundle. Use sequential PRs where logic requires order; use parallel PRs where slices are independent. diff --git a/docs/SHELL.md b/docs/SHELL.md new file mode 100644 index 0000000..faf836b --- /dev/null +++ b/docs/SHELL.md @@ -0,0 +1,30 @@ +When doing run args please follow this pattern. Where a function called runCommands is at the top always and it defines what other function are called based on args. + +```bash +#!/usr/bin/env bash +set -euo pipefail + +runCommands() { + if [[ "${1}" == "command1" ]]; then + command1 + elif [[ "${1}" == "command2" ]]; then + command2 + else + echo "Please provide one of the following commands: command1, command2" + exit 1 + fi +} + +command1() { + # code +} + +command2() { + # code +} + +runCommands "${@}" +``` + ++Inject variables into strings like this `"${VAR}"`. +Avoid hasty abstraction, if var is only ref once and it's not too long, then inline the var. diff --git a/docs/TESTING.md b/docs/TESTING.md new file mode 100644 index 0000000..e359c3a --- /dev/null +++ b/docs/TESTING.md @@ -0,0 +1,15 @@ +Use bun's test runner across the repo. + +- Prefer behavior tests through public interfaces. +- Mock system boundaries, not your own modules, unless the seam is truly external or non-deterministic. +- No real network in tests. + +## Tooling + +- Use MSW for fetch and HTTP mocking. + +## Picking a seam + +- Use unit tests for pure logic. +- Use integration tests for request, data, and persistence flows. +- Use E2E only for critical UI journeys that cannot be proven lower in the stack. diff --git a/docs/TYPESCRIPT.md b/docs/TYPESCRIPT.md new file mode 100644 index 0000000..a345d48 --- /dev/null +++ b/docs/TYPESCRIPT.md @@ -0,0 +1,26 @@ +## Code style + +- Prefer functional and declarative patterns. +- "AHA (Avoid Hasty Abstractions) and KISS (Keep It Simple, Stupid). Prefer duplication over premature abstraction; prefer simple, direct code over clever indirection. Don't extract one- or two-line helpers, inline them. +- Use `function` for top-level declarations; use arrow functions for callbacks and inline expressions. +- Avoid enums; prefer unions, maps, or objects. +- Keep inline comments rare and concise. + +## Types + +- Prefer `type` over `interface`, except for class contracts. +- Default to mutable types. Use `readonly` only for exported types consumers shouldn't mutate, or for frozen config. +- Prefix Zod schemas with `z` (e.g., `zUser`) so the inferred type can take the clean name: `type User = z.infer`. + +## Imports & exports + +- Use named exports. +- Follow Biome import ordering. +- Use relative imports within one directory. Use `~` aliases for deeper `src/` imports. +- Route files do not use default exports. + +## Project conventions + +- Use `void` only when intentionally discarding a non-void result. +- Prefer descriptive but compact names. Shorten long suffixes like `Opts` or `Args` when the full name gets noisy. +- If a config option has a safe default, omit it. diff --git a/package.json b/package.json index 9ff6c1a..b95380e 100644 --- a/package.json +++ b/package.json @@ -4,20 +4,20 @@ ], "scripts": { "prepare": "lefthook install --reset-hooks-path", - "fmt": "biome format --write .", + "format": "biome format --write .", "lint": "biome check .", "lint:fix": "biome check --write .", - "check": "bun run typecheck", - "typecheck": "tsc --noEmit -p packages/cli/tsconfig.json", - "test": "bun run test && bun run test:e2e", - "test:unit": "bun run --filter '@mdlx/cli' test:unit", - "test:e2e": "bun run --filter '@mdl/e2e' test:e2e" + "typecheck": "bun run --filter '@mdlx/cli' typecheck && bun run --filter '@mdl/web' typecheck && bun run --filter '@mdl/youtube-token' typecheck", + "test": "bun run test:unit && bun run test:e2e", + "test:unit": "bun run --filter '@mdlx/cli' test:unit && bun run --filter '@mdl/youtube-token' test", + "test:e2e": "bun run --filter '@mdl/e2e' test:e2e", + "test:youtube-smoke": "bun run --filter '@mdl/e2e' test:youtube-smoke" }, "devDependencies": { "@biomejs/biome": "2.4.8", "@types/bun": "latest", "lefthook": "2.1.5", - "typescript": "6.0.2" + "typescript": "7.0.2" }, "packageManager": "bun@1.3.10", "knip": { diff --git a/packages/cli/README.md b/packages/cli/README.md index e6455ae..8074f59 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -59,6 +59,12 @@ Choose the extracted audio format and quality: mdl "https://open.spotify.com/playlist/..." --format m4a --bitrate 192K ``` +Skip requesting YouTube Proof of Origin tokens: + +```bash +mdl "https://open.spotify.com/playlist/..." --no-po-token +``` + CLI help: ```bash diff --git a/packages/cli/package.json b/packages/cli/package.json index 394fcef..ee7c581 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -42,10 +42,9 @@ "url": "git+https://github.com/labithiotis/mdl.git" }, "scripts": { - "dev": "bun run src/cli.tsx", + "dev": "MDL_YOUTUBE_TOKEN_PROVIDER_URL=http://localhost:8787/token bun run src/cli.tsx", "build": "bun build --compile --minify ./src/cli.tsx --outfile release/mdl", "build:npm": "bun build --minify --target=node ./src/cli.tsx --outfile release/cli.js", - "check": "tsc --noEmit", "typecheck": "tsc --noEmit", "test": "bun test src", "test:unit": "bun test src" diff --git a/packages/cli/src/cli.tsx b/packages/cli/src/cli.tsx index 80d4cbe..468db79 100644 --- a/packages/cli/src/cli.tsx +++ b/packages/cli/src/cli.tsx @@ -6,6 +6,7 @@ import { parseCliArgs } from './lib/args'; import { ensureFfmpegExecutable } from './lib/ffmpeg'; import { loadManifest } from './lib/manifest'; import { configureNetwork } from './lib/network'; +import { configurePoToken } from './lib/poToken'; import { CliArgumentError, formatEffectError } from './lib/utils'; import { App } from './ui/app'; @@ -18,9 +19,8 @@ try { ytCookie: args.ytCookie, ytUserAgent: args.ytUserAgent, }); - const dir = path.resolve( - process.env.INIT_CWD ?? process.env.PWD ?? process.cwd() - ); + configurePoToken({ usePoToken: args.usePoToken }); + const dir = path.resolve(process.env.INIT_CWD ?? process.env.PWD ?? process.cwd()); const manifest = !args.url ? await loadManifest(dir) : null; const outputDir = path.resolve(args.outputDir ?? dir); diff --git a/packages/cli/src/lib/args.test.ts b/packages/cli/src/lib/args.test.ts index 90249b5..80b8390 100644 --- a/packages/cli/src/lib/args.test.ts +++ b/packages/cli/src/lib/args.test.ts @@ -11,6 +11,7 @@ describe('args', () => { audioFormat: 'mp3', audioQuality: 'best', downloadParallelism: 10, + usePoToken: true, }); }); @@ -29,6 +30,7 @@ describe('args', () => { audioFormat: 'mp3', audioQuality: 'best', downloadParallelism: 7, + usePoToken: true, trackCount: 3, outputDir: './music', url: 'https://open.spotify.com/playlist/example', @@ -49,10 +51,10 @@ describe('args', () => { audioFormat: 'mp3', audioQuality: 'best', downloadParallelism: 10, + usePoToken: true, proxy: 'https://user:pass@dc.oxylabs.io:8000/', ytCookie: 'SID=abc; HSID=def', - ytUserAgent: - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:149.0) Gecko/20100101 Firefox/149.0', + ytUserAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:149.0) Gecko/20100101 Firefox/149.0', }); }); @@ -61,6 +63,13 @@ describe('args', () => { audioFormat: 'm4a', audioQuality: '192K', downloadParallelism: 10, + usePoToken: true, + }); + }); + + test('parseCliArgs can disable YouTube PO tokens', () => { + expect(parseCliArgs(['--no-po-token'])).toMatchObject({ + usePoToken: false, }); }); @@ -115,19 +124,12 @@ describe('args', () => { expect(() => { parseCliArgs(['--version']); }).toThrow('exit:0'); - expect(stdoutWrite).toHaveBeenCalledWith( - expect.stringMatching(/\d+\.\d+\.\d+/) - ); + expect(stdoutWrite).toHaveBeenCalledWith(expect.stringMatching(/\d+\.\d+\.\d+/)); }); test('parseCliArgs rejects unexpected arguments', () => { expect(() => { - parseCliArgs([ - 'https://open.spotify.com/playlist/example', - 'unexpected-token', - ]); - }).toThrow( - 'Unexpected argument "unexpected-token". Please use one of these:' - ); + parseCliArgs(['https://open.spotify.com/playlist/example', 'unexpected-token']); + }).toThrow('Unexpected argument "unexpected-token". Please use one of these:'); }); }); diff --git a/packages/cli/src/lib/args.ts b/packages/cli/src/lib/args.ts index 2290ee4..b0c7594 100644 --- a/packages/cli/src/lib/args.ts +++ b/packages/cli/src/lib/args.ts @@ -39,6 +39,7 @@ export type CliOptions = DownloadCliOptions & { trackCount?: number; url?: string; ytCookie?: string; + usePoToken: boolean; ytUserAgent?: string; }; @@ -49,6 +50,7 @@ type ParsedCommanderOptions = { output?: string; parallel: number; proxy?: string; + poToken: boolean; ytCookie?: string; ytUserAgent?: string; }; @@ -66,10 +68,7 @@ export function parseCliArgs(argv: string[]): CliOptions { program.parse(argv, { from: 'user' }); } catch (error) { if (error instanceof CommanderError) { - if ( - error.code === 'commander.helpDisplayed' || - error.code === 'commander.version' - ) { + if (error.code === 'commander.helpDisplayed' || error.code === 'commander.version') { process.exit(0); } @@ -86,6 +85,7 @@ export function parseCliArgs(argv: string[]): CliOptions { audioFormat: options.format, audioQuality: options.bitrate, downloadParallelism: options.parallel, + usePoToken: options.poToken, ...(options.output ? { outputDir: options.output } : {}), ...(options.proxy ? { proxy: options.proxy } : {}), ...(typeof options.count === 'number' ? { trackCount: options.count } : {}), @@ -98,34 +98,24 @@ export function parseCliArgs(argv: string[]): CliOptions { function createCliProgram(): Command { return new Command() .name('mdl') - .description( - 'Interactive playlist and album downloader for streaming collections -> YouTube audio.' - ) + .description('Interactive playlist and album downloader for streaming collections -> YouTube audio.') .usage('[playlist-or-album-url] [options]') .helpOption('-h, --help', 'Show this help message.') .version(packageJson.version, '-v, --version', 'Show the current version.') .argument('[playlist-or-album-url]', 'Playlist or album URL.') - .addOption( - new Option( - '-o, --output ', - 'Base output directory. Playlist files are stored in a subfolder.' - ) - ) + .addOption(new Option('-o, --output ', 'Base output directory. Playlist files are stored in a subfolder.')) .addOption( new Option( '-p, --parallel ', `Number of tracks to download in parallel. Default: ${DEFAULT_DOWNLOAD_PARALLELISM}.` ) .default(DEFAULT_DOWNLOAD_PARALLELISM) - .argParser((value: string) => - parsePositiveIntegerArg(value, '--parallel') - ) + .argParser((value: string) => parsePositiveIntegerArg(value, '--parallel')) ) .addOption( - new Option( - '-c, --count ', - 'Maximum number of tracks to download from the collection.' - ).argParser((value: string) => parsePositiveIntegerArg(value, '--count')) + new Option('-c, --count ', 'Maximum number of tracks to download from the collection.').argParser( + (value: string) => parsePositiveIntegerArg(value, '--count') + ) ) .addOption( new Option( @@ -141,33 +131,18 @@ function createCliProgram(): Command { `Output audio quality. Use best, 0-10 VBR, or ${AUDIO_QUALITIES.filter((quality) => quality.endsWith('K')).join(', ')}. Default: ${DEFAULT_AUDIO_QUALITY}.` ) .default(DEFAULT_AUDIO_QUALITY) - .argParser((value: string) => - parseAudioQualityArg(normalizeAudioQualityArg(value), '--bitrate') - ) - ) - .addOption( - new Option( - '--proxy ', - 'HTTPS/HTTP proxy URL used for provider fetches and YouTube requests.' - ).argParser((value: string) => parseProxyArg(value)) + .argParser((value: string) => parseAudioQualityArg(normalizeAudioQualityArg(value), '--bitrate')) ) + .addOption(new Option('--no-po-token', 'Do not request YouTube Proof of Origin tokens.')) .addOption( - new Option( - '--yt-cookie ', - 'YouTube Cookie used for YouTube requests.' - ) - ) - .addOption( - new Option( - '--yt-user-agent ', - 'YouTube User-Agent header used for YouTube requests.' + new Option('--proxy ', 'HTTPS/HTTP proxy URL used for provider fetches and YouTube requests.').argParser( + (value: string) => parseProxyArg(value) ) ) + .addOption(new Option('--yt-cookie ', 'YouTube Cookie used for YouTube requests.')) + .addOption(new Option('--yt-user-agent ', 'YouTube User-Agent header used for YouTube requests.')) .addHelpText('before', 'mdl (MusicDownLoader)\n\n') - .addHelpText( - 'after', - `\nProviders:\n ${PROVIDERS.join(', ')}.\n Audio downloads currently come from YouTube.\n` - ) + .addHelpText('after', `\nProviders:\n ${PROVIDERS.join(', ')}.\n Audio downloads currently come from YouTube.\n`) .exitOverride() .configureOutput({ writeOut: (str: string) => process.stdout.write(str), @@ -178,12 +153,7 @@ function createCliProgram(): Command { function parsePositiveIntegerArg(value: string, flagName: string): number { const parsed = Number.parseInt(value, 10); - if ( - !Number.isInteger(parsed) || - Number.isNaN(parsed) || - parsed <= 0 || - parsed.toString() !== value - ) { + if (!Number.isInteger(parsed) || Number.isNaN(parsed) || parsed <= 0 || parsed.toString() !== value) { throw new CliArgumentError({ message: `${flagName} must be a positive integer.`, }); @@ -204,29 +174,19 @@ function parseAudioFormatArg(value: string, flagName: string): AudioFormat { }); } -function normalizeCommanderError( - program: Command, - error: CommanderError, - argv: string[] -): Error { +function normalizeCommanderError(program: Command, error: CommanderError, argv: string[]): Error { if (error.code === 'commander.excessArguments') { const positionalArgs = argv.filter((token) => !token.startsWith('-')); const unexpectedArg = positionalArgs[1]; return new CliArgumentError({ - message: formatUnexpectedArgumentMessage( - program, - unexpectedArg ?? error.message - ), + message: formatUnexpectedArgumentMessage(program, unexpectedArg ?? error.message), }); } if (error.code === 'commander.unknownOption') { const unexpectedToken = argv.find((token) => token.startsWith('-')); return new CliArgumentError({ - message: formatUnexpectedArgumentMessage( - program, - unexpectedToken ?? error.message - ), + message: formatUnexpectedArgumentMessage(program, unexpectedToken ?? error.message), }); } @@ -237,17 +197,13 @@ function normalizeCommanderError( } if (error.code === 'commander.optionMissingArgument') { - const matchedFlags = error.message.match( - /option '([^']+)' argument missing/ - ); + const matchedFlags = error.message.match(/option '([^']+)' argument missing/); const normalizedFlag = matchedFlags?.[1] ?.split(',') .map((value) => value.trim().split(' ')[0]) .find((value) => value.startsWith('--')) ?? - matchedFlags?.[1] - ?.split(',') - .map((value) => value.trim().split(' ')[0])[0] ?? + matchedFlags?.[1]?.split(',').map((value) => value.trim().split(' ')[0])[0] ?? '--option'; return new CliArgumentError({ @@ -270,15 +226,10 @@ function parseAudioQualityArg(value: string, flagName: string): AudioQuality { }); } -function formatUnexpectedArgumentMessage( - program: Command, - argument: string -): string { +function formatUnexpectedArgumentMessage(program: Command, argument: string): string { const help = program.createHelp(); const supportedArgs = [ - ...program.registeredArguments.map((registeredArgument) => - help.argumentTerm(registeredArgument) - ), + ...program.registeredArguments.map((registeredArgument) => help.argumentTerm(registeredArgument)), ...help.visibleOptions(program).map((option) => help.optionTerm(option)), ] .map((value) => ` ${value}`) diff --git a/packages/cli/src/lib/ffmpeg.ts b/packages/cli/src/lib/ffmpeg.ts index 09e4046..bd701ea 100644 --- a/packages/cli/src/lib/ffmpeg.ts +++ b/packages/cli/src/lib/ffmpeg.ts @@ -9,28 +9,20 @@ export async function ensureFfmpegExecutable(): Promise { const ffmpegPath = await findBinaryPath('ffmpeg'); if (!ffmpegPath) { - throw new Error( - 'ffmpeg is required but was not found on PATH. Install ffmpeg and try again.' - ); + throw new Error('ffmpeg is required but was not found on PATH. Install ffmpeg and try again.'); } return ffmpegPath; } async function findBinaryPath(binaryName: string): Promise { - const pathEntries = (process.env.PATH ?? '') - .split(path.delimiter) - .filter(Boolean); + const pathEntries = (process.env.PATH ?? '').split(path.delimiter).filter(Boolean); const commonEntries = process.platform === 'darwin' ? ['/opt/homebrew/bin', '/usr/local/bin', '/usr/bin', '/bin'] : ['/usr/local/bin', '/usr/bin', '/bin']; - for (const candidate of new Set( - [...pathEntries, ...commonEntries].map((entry) => - path.join(entry, binaryName) - ) - )) { + for (const candidate of new Set([...pathEntries, ...commonEntries].map((entry) => path.join(entry, binaryName)))) { try { await access(candidate); return candidate; diff --git a/packages/cli/src/lib/installation.test.ts b/packages/cli/src/lib/installation.test.ts new file mode 100644 index 0000000..8660068 --- /dev/null +++ b/packages/cli/src/lib/installation.test.ts @@ -0,0 +1,42 @@ +import { afterEach, describe, expect, test } from 'bun:test'; +import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import { loadInstallationId } from './installation'; + +const temporaryDirectories: string[] = []; + +afterEach(async () => { + await Promise.all(temporaryDirectories.splice(0).map((directory) => rm(directory, { force: true, recursive: true }))); +}); + +describe('installation ID', () => { + test('creates and reuses a UUID', async () => { + const directory = await createTemporaryDirectory(); + const installationId = await loadInstallationId(directory); + + expect(installationId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i); + expect(await loadInstallationId(directory)).toBe(installationId); + expect(JSON.parse(await readFile(path.join(directory, 'config.json'), 'utf8'))).toEqual({ installationId }); + }); + + test('replaces malformed configuration', async () => { + const directory = await createTemporaryDirectory(); + await writeFile(path.join(directory, 'config.json'), '{broken'); + + await expect(loadInstallationId(directory)).resolves.toMatch(/^[0-9a-f-]{36}$/i); + }); + + test('shares one UUID across concurrent first-run processes', async () => { + const directory = await createTemporaryDirectory(); + const installationIds = await Promise.all(Array.from({ length: 10 }, () => loadInstallationId(directory))); + + expect(new Set(installationIds).size).toBe(1); + }); +}); + +async function createTemporaryDirectory(): Promise { + const directory = await mkdtemp(path.join(os.tmpdir(), 'mdl-installation-')); + temporaryDirectories.push(directory); + return directory; +} diff --git a/packages/cli/src/lib/installation.ts b/packages/cli/src/lib/installation.ts new file mode 100644 index 0000000..76c3470 --- /dev/null +++ b/packages/cli/src/lib/installation.ts @@ -0,0 +1,77 @@ +import { mkdir, readFile, writeFile } from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; + +type InstallationConfig = { + installationId: string; +}; + +const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + +let installationIdPromise: Promise | null = null; + +export function getInstallationId(): Promise { + installationIdPromise ??= loadInstallationId(getConfigDirectory()); + return installationIdPromise; +} + +export async function loadInstallationId(configDirectory: string): Promise { + const configPath = path.join(configDirectory, 'config.json'); + const existingInstallationId = await readInstallationId(configPath); + if (existingInstallationId) return existingInstallationId; + + const installationId = crypto.randomUUID().toLowerCase(); + await mkdir(configDirectory, { recursive: true, mode: 0o700 }); + + try { + await writeInstallationId(configPath, installationId, 'wx'); + } catch (error) { + if (!isFileExistsError(error)) throw error; + + const winningInstallationId = await waitForInstallationId(configPath); + if (winningInstallationId) return winningInstallationId; + await writeInstallationId(configPath, installationId, 'w'); + } + + return installationId; +} + +async function waitForInstallationId(configPath: string): Promise { + for (let attempt = 0; attempt < 20; attempt += 1) { + const installationId = await readInstallationId(configPath); + if (installationId) return installationId; + await new Promise((resolve) => setTimeout(resolve, 50)); + } + return null; +} + +async function readInstallationId(configPath: string): Promise { + try { + const config = JSON.parse(await readFile(configPath, 'utf8')) as Partial; + return typeof config.installationId === 'string' && UUID_PATTERN.test(config.installationId) + ? config.installationId.toLowerCase() + : null; + } catch { + return null; + } +} + +function writeInstallationId(configPath: string, installationId: string, flag: 'w' | 'wx'): Promise { + return writeFile(configPath, `${JSON.stringify({ installationId }, null, 2)}\n`, { flag, mode: 0o600 }); +} + +function isFileExistsError(error: unknown): boolean { + return error instanceof Error && 'code' in error && (error as NodeJS.ErrnoException).code === 'EEXIST'; +} + +function getConfigDirectory(): string { + if (process.platform === 'darwin') { + return path.join(os.homedir(), 'Library', 'Application Support', 'mdl'); + } + + if (process.platform === 'win32') { + return path.join(process.env.LOCALAPPDATA ?? path.join(os.homedir(), 'AppData', 'Local'), 'mdl'); + } + + return path.join(process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), '.config'), 'mdl'); +} diff --git a/packages/cli/src/lib/manifest.test.ts b/packages/cli/src/lib/manifest.test.ts index 9af182f..7170c04 100644 --- a/packages/cli/src/lib/manifest.test.ts +++ b/packages/cli/src/lib/manifest.test.ts @@ -7,11 +7,7 @@ import { loadManifest, MANIFEST_FILE_NAME } from './manifest'; const temporaryDirectories: string[] = []; afterEach(async () => { - await Promise.all( - temporaryDirectories - .splice(0) - .map((directory) => rm(directory, { force: true, recursive: true })) - ); + await Promise.all(temporaryDirectories.splice(0).map((directory) => rm(directory, { force: true, recursive: true }))); }); describe('manifest', () => { @@ -26,10 +22,7 @@ describe('manifest', () => { test('loadManifest returns null when the manifest shape is invalid', async () => { const directory = await createTemporaryDirectory(); - await writeFile( - path.join(directory, MANIFEST_FILE_NAME), - `${JSON.stringify({ version: 1, tracks: 'bad' })}\n` - ); + await writeFile(path.join(directory, MANIFEST_FILE_NAME), `${JSON.stringify({ version: 1, tracks: 'bad' })}\n`); const manifest = await loadManifest(directory); diff --git a/packages/cli/src/lib/manifest.ts b/packages/cli/src/lib/manifest.ts index 0e37817..b5378f5 100644 --- a/packages/cli/src/lib/manifest.ts +++ b/packages/cli/src/lib/manifest.ts @@ -3,17 +3,11 @@ import path from 'node:path'; import { Either } from 'effect'; import { syncManifestSchema } from './schemas'; import type { SyncManifest } from './types'; -import { - decodeUnknownEither, - formatSchemaParseError, - ManifestDecodeError, -} from './utils'; +import { decodeUnknownEither, formatSchemaParseError, ManifestDecodeError } from './utils'; export const MANIFEST_FILE_NAME = '.mdl.json'; -export async function loadManifest( - directory: string -): Promise { +export async function loadManifest(directory: string): Promise { const manifestPath = path.join(directory, MANIFEST_FILE_NAME); try { @@ -30,18 +24,11 @@ export async function loadManifest( } } -export async function saveManifest( - directory: string, - manifest: SyncManifest -): Promise { +export async function saveManifest(directory: string, manifest: SyncManifest): Promise { await mkdir(directory, { recursive: true }); const manifestPath = path.join(directory, MANIFEST_FILE_NAME); - await writeFile( - manifestPath, - `${JSON.stringify(manifest, null, 2)}\n`, - 'utf8' - ); + await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8'); return manifestPath; } diff --git a/packages/cli/src/lib/metadata.ts b/packages/cli/src/lib/metadata.ts index c36e7e3..d8b99d6 100644 --- a/packages/cli/src/lib/metadata.ts +++ b/packages/cli/src/lib/metadata.ts @@ -15,13 +15,9 @@ export async function writeTrackMetadata(params: { }): Promise { const { filePath, track, signal } = params; const ffmpegPath = await ensureFfmpegExecutable(); - const tempDir = await mkdtemp( - path.join(path.dirname(filePath), METADATA_TEMP_DIR_PREFIX) - ); + const tempDir = await mkdtemp(path.join(path.dirname(filePath), METADATA_TEMP_DIR_PREFIX)); const outputPath = path.join(tempDir, path.basename(filePath)); - let coverPath = track.artworkUrl - ? path.join(tempDir, getArtworkFileName(track.artworkUrl)) - : null; + let coverPath = track.artworkUrl ? path.join(tempDir, getArtworkFileName(track.artworkUrl)) : null; try { if (coverPath && track.artworkUrl) { @@ -59,16 +55,8 @@ export async function writeTrackMetadata(params: { } } -function buildMetadataArgs( - track: PlaylistTrack, - hasArtwork: boolean -): string[] { - const metadataArgs = [ - '-metadata', - `title=${track.title}`, - '-metadata', - `artist=${track.artists.join(', ')}`, - ]; +function buildMetadataArgs(track: PlaylistTrack, hasArtwork: boolean): string[] { + const metadataArgs = ['-metadata', `title=${track.title}`, '-metadata', `artist=${track.artists.join(', ')}`]; if (track.album) { metadataArgs.push('-metadata', `album=${track.album}`); @@ -88,11 +76,7 @@ function buildMetadataArgs( return metadataArgs; } -async function downloadArtwork( - artworkUrl: string, - destinationPath: string, - signal?: AbortSignal -): Promise { +async function downloadArtwork(artworkUrl: string, destinationPath: string, signal?: AbortSignal): Promise { const response = await fetch(artworkUrl, { signal }); if (!response.ok) { diff --git a/packages/cli/src/lib/network.test.ts b/packages/cli/src/lib/network.test.ts new file mode 100644 index 0000000..a98b60a --- /dev/null +++ b/packages/cli/src/lib/network.test.ts @@ -0,0 +1,109 @@ +import { afterEach, describe, expect, mock, test } from 'bun:test'; +import { configureNetwork, getYouTubeSessionOptions } from './network'; +import { configurePoToken, getYouTubePoToken, setPoTokenWarningHandler } from './poToken'; + +const defaultFetch = globalThis.fetch; +const installationId = '123e4567-e89b-42d3-a456-426614174000'; + +afterEach(() => { + configureNetwork({}); + configurePoToken({}); + globalThis.fetch = defaultFetch; + mock.restore(); +}); + +describe('network', () => { + test('fetches YouTube PO tokens from the default worker', async () => { + const fetchMock = mock(async (input: RequestInfo | URL, init?: RequestInit) => { + expect(String(input)).toBe('https://mdl-youtube-token.canari.workers.dev/token?videoId=abc123'); + expect(new Headers(init?.headers).get('x-mdl-installation-id')).toMatch(/^[0-9a-f-]{36}$/i); + + return new Response( + JSON.stringify({ + poToken: 'po-token-123', + visitorData: 'visitor-data-123', + }) + ); + }); + + configureNetwork({}); + configurePoToken({ installationId }); + globalThis.fetch = fetchMock as unknown as typeof fetch; + + await expect(getYouTubePoToken('abc123')).resolves.toEqual({ + poToken: 'po-token-123', + visitorData: 'visitor-data-123', + }); + expect(fetchMock).toHaveBeenCalledTimes(1); + expect( + getYouTubeSessionOptions({ + poToken: 'po-token-123', + visitorData: 'visitor-data-123', + }) + ).toMatchObject({ + visitor_data: 'visitor-data-123', + }); + }); + + test('rejects token responses without visitor data', async () => { + configureNetwork({}); + configurePoToken({ installationId }); + globalThis.fetch = mock( + async () => new Response(JSON.stringify({ poToken: 'po-token-123' })) + ) as unknown as typeof fetch; + + await expect(getYouTubePoToken('abc123')).rejects.toThrow('YouTube token provider did not return visitor data.'); + }); + + test('does not request a token when disabled', async () => { + const fetchMock = mock(async () => new Response()); + globalThis.fetch = fetchMock as unknown as typeof fetch; + configureNetwork({}); + configurePoToken({ installationId, usePoToken: false }); + + await expect(getYouTubePoToken('abc123')).resolves.toBeUndefined(); + expect(fetchMock).not.toHaveBeenCalled(); + }); + + test('warns once and stops requesting tokens after a rate limit', async () => { + const warningHandler = mock(() => undefined); + const fetchMock = mock(async () => + Response.json( + { + error: { + message: 'You hit the daily limit for YouTube download tokens.', + }, + }, + { status: 429 } + ) + ); + setPoTokenWarningHandler(warningHandler); + configureNetwork({}); + configurePoToken({ installationId }); + globalThis.fetch = fetchMock as unknown as typeof fetch; + + await expect(getYouTubePoToken('abc123')).resolves.toBeUndefined(); + await expect(getYouTubePoToken('def456')).resolves.toBeUndefined(); + + expect(fetchMock).toHaveBeenCalledTimes(1); + expect(warningHandler).toHaveBeenCalledTimes(1); + expect(warningHandler).toHaveBeenCalledWith( + 'You hit the daily limit for YouTube download tokens. MDL will continue without one; some YouTube downloads may fail.' + ); + setPoTokenWarningHandler(null); + }); + + test('warns only once when parallel requests are rate limited', async () => { + const warningHandler = mock(() => undefined); + const fetchMock = mock(async () => Response.json({ error: { message: 'Daily limit reached.' } }, { status: 429 })); + setPoTokenWarningHandler(warningHandler); + configureNetwork({}); + configurePoToken({ installationId }); + globalThis.fetch = fetchMock as unknown as typeof fetch; + + await Promise.all([getYouTubePoToken('abc123'), getYouTubePoToken('def456'), getYouTubePoToken('ghi789')]); + + expect(warningHandler).toHaveBeenCalledTimes(1); + setPoTokenWarningHandler(null); + }); +}); diff --git a/packages/cli/src/lib/network.ts b/packages/cli/src/lib/network.ts index 78daed7..1f9ee14 100644 --- a/packages/cli/src/lib/network.ts +++ b/packages/cli/src/lib/network.ts @@ -1,4 +1,6 @@ import { ProxyAgent, fetch as undiciFetch } from 'undici'; +import type { SessionOptions } from 'youtubei.js'; +import type { YouTubePoToken } from './poToken'; export type NetworkOptions = { proxy?: string; @@ -25,28 +27,30 @@ export function configureNetwork(options: NetworkOptions): void { } proxyAgent = new ProxyAgent(options.proxy); - console.log('=======> Using proxy', options.proxy); globalThis.fetch = createProxyFetch(proxyAgent); } -export function getYouTubeSessionOptions(): { - cookie?: string; - fetch: typeof fetch; - po_token?: string; - user_agent?: string; -} { +export function getYouTubeSessionOptions(poToken?: YouTubePoToken): Partial { return { cookie: currentNetworkOptions.ytCookie, - fetch: globalThis.fetch.bind(globalThis), user_agent: currentNetworkOptions.ytUserAgent, + visitor_data: poToken?.visitorData, + po_token: poToken?.poToken, + fetch: globalThis.fetch.bind(globalThis), }; } +export function getYouTubeSessionCacheKey(poToken?: YouTubePoToken): string { + return JSON.stringify({ + cookie: currentNetworkOptions.ytCookie, + userAgent: currentNetworkOptions.ytUserAgent, + poToken: poToken?.poToken, + visitorData: poToken?.visitorData, + }); +} + function createProxyFetch(agent: ProxyAgent): typeof fetch { - return (async ( - input: Parameters[0], - init?: Parameters[1] - ) => + return (async (input: Parameters[0], init?: Parameters[1]) => undiciFetch( input as never, { diff --git a/packages/cli/src/lib/poToken.ts b/packages/cli/src/lib/poToken.ts new file mode 100644 index 0000000..edea11d --- /dev/null +++ b/packages/cli/src/lib/poToken.ts @@ -0,0 +1,84 @@ +import { getInstallationId } from './installation'; + +export type PoTokenOptions = { + installationId?: string; + usePoToken?: boolean; +}; + +export type YouTubePoToken = { + expiresAt?: string; + poToken: string; + visitorData?: string; +}; + +const providerUrl = process.env.MDL_YOUTUBE_TOKEN_PROVIDER_URL ?? 'https://mdl-youtube-token.canari.workers.dev/token'; + +let options: PoTokenOptions = {}; +let warningHandler: ((message: string) => void) | null = null; +let rateLimited = false; +let hasWarnedRateLimit = false; + +export function configurePoToken(nextOptions: PoTokenOptions): void { + options = { ...nextOptions }; + rateLimited = false; + hasWarnedRateLimit = false; +} + +export function setPoTokenWarningHandler(handler: ((message: string) => void) | null): void { + warningHandler = handler; +} + +export async function getYouTubePoToken(videoId: string, signal?: AbortSignal): Promise { + if (options.usePoToken === false || rateLimited) return undefined; + + const url = new URL(providerUrl); + url.searchParams.set('videoId', videoId); + const installationId = options.installationId ?? (await getInstallationId()); + const response = await fetch(url, { + signal, + headers: { 'x-mdl-installation-id': installationId }, + }); + if (response.ok) return parseYouTubePoToken(await response.json()); + + if (response.status !== 429) { + throw new Error(`YouTube token provider failed (${response.status}): ${await response.text()}`); + } + + rateLimited = true; + if (!hasWarnedRateLimit) { + hasWarnedRateLimit = true; + warningHandler?.( + `${await getRateLimitMessage(response)} MDL will continue without one; some YouTube downloads may fail.` + ); + } + return undefined; +} + +async function getRateLimitMessage(response: Response): Promise { + try { + const payload = (await response.json()) as { error?: { message?: unknown } }; + return typeof payload.error?.message === 'string' + ? payload.error.message + : 'You hit the limit for YouTube download tokens.'; + } catch { + return 'You hit the limit for YouTube download tokens.'; + } +} + +function parseYouTubePoToken(value: unknown): YouTubePoToken { + if (!value || typeof value !== 'object') { + throw new Error('YouTube token provider returned an invalid payload.'); + } + const payload = value as Partial; + if (typeof payload.poToken !== 'string' || !payload.poToken.trim()) { + throw new Error('YouTube token provider did not return a PO token.'); + } + if (typeof payload.visitorData !== 'string' || !payload.visitorData.trim()) { + throw new Error('YouTube token provider did not return visitor data.'); + } + return { + poToken: payload.poToken, + visitorData: payload.visitorData, + ...(typeof payload.expiresAt === 'string' ? { expiresAt: payload.expiresAt } : {}), + }; +} diff --git a/packages/cli/src/lib/providers/AmazonMusic.test.ts b/packages/cli/src/lib/providers/AmazonMusic.test.ts index ea97901..34f3ce9 100644 --- a/packages/cli/src/lib/providers/AmazonMusic.test.ts +++ b/packages/cli/src/lib/providers/AmazonMusic.test.ts @@ -64,8 +64,7 @@ describe('amazon-music', () => { artistUrl: 'https://music.amazon.com/artists/B01HG4E4LI', artworkUrl: 'https://m.media-amazon.com/images/I/track.jpg', durationMs: 126000, - sourceUrl: - 'https://music.amazon.com/albums/B09DS56JYZ?trackAsin=B09DS24Z6F', + sourceUrl: 'https://music.amazon.com/albums/B09DS56JYZ?trackAsin=B09DS24Z6F', title: 'Antarctica [Explicit]', }); }); diff --git a/packages/cli/src/lib/providers/AmazonMusic.ts b/packages/cli/src/lib/providers/AmazonMusic.ts index 527a493..02b0140 100644 --- a/packages/cli/src/lib/providers/AmazonMusic.ts +++ b/packages/cli/src/lib/providers/AmazonMusic.ts @@ -2,8 +2,7 @@ import type { PlaylistMetadata, PlaylistTrack } from '../types'; import { getFirstNonEmptyString } from '../utils'; import type { FetchOptions, ProviderOptions } from './Providers'; -const AMAZON_BOT_USER_AGENT = - 'facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)'; +const AMAZON_BOT_USER_AGENT = 'facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)'; export class AmazonMusicProvider implements ProviderOptions { public readonly provider = 'amazon-music'; @@ -25,10 +24,7 @@ export class AmazonMusicProvider implements ProviderOptions { ); } - public async fetch( - url: string, - options: FetchOptions - ): Promise { + public async fetch(url: string, options: FetchOptions): Promise { const { signal } = options; const trackAsin = this.extractTrackAsin(url); @@ -40,62 +36,40 @@ export class AmazonMusicProvider implements ProviderOptions { const html = await this.fetchHtml(url, signal); const playlist = this.parsePlaylistHtml(html); - const tracks = await this.mapWithConcurrency( - playlist.trackAsins, - 4, - async (asin) => { - const trackHtml = await this.fetchHtml( - `${url}?do=play&trackAsin=${asin}`, - signal - ); - const trackPage = this.parseTrackHtml(trackHtml); - const artist = trackPage.artistUrl - ? await this.getCachedName( - this.artistNameCache, - trackPage.artistUrl, - signal - ) - : undefined; - const albumTitle = trackPage.albumUrl - ? await this.getCachedName( - this.albumTitleCache, - trackPage.albumUrl, - signal - ) - : undefined; - - if (!trackPage.title || !artist) { - return null; - } - - return { - id: asin, - title: trackPage.title, - artists: [artist], - album: albumTitle, - artworkUrl: getFirstNonEmptyString( - trackPage.artworkUrl, - playlist.artworkUrl - ), - durationMs: trackPage.durationMs, - sourceUrl: trackPage.sourceUrl, - }; + const tracks = await this.mapWithConcurrency(playlist.trackAsins, 4, async (asin) => { + const trackHtml = await this.fetchHtml(`${url}?do=play&trackAsin=${asin}`, signal); + const trackPage = this.parseTrackHtml(trackHtml); + const artist = trackPage.artistUrl + ? await this.getCachedName(this.artistNameCache, trackPage.artistUrl, signal) + : undefined; + const albumTitle = trackPage.albumUrl + ? await this.getCachedName(this.albumTitleCache, trackPage.albumUrl, signal) + : undefined; + + if (!trackPage.title || !artist) { + return null; } - ); + + return { + id: asin, + title: trackPage.title, + artists: [artist], + album: albumTitle, + artworkUrl: getFirstNonEmptyString(trackPage.artworkUrl, playlist.artworkUrl), + durationMs: trackPage.durationMs, + sourceUrl: trackPage.sourceUrl, + }; + }); const normalizedTracks = tracks.filter((track) => track !== null); if (normalizedTracks.length === 0) { - throw new Error( - `No tracks were found in the Amazon Music ${collectionKind}.` - ); + throw new Error(`No tracks were found in the Amazon Music ${collectionKind}.`); } return { id: this.extractPlaylistId(url), - title: - playlist.title || - `Amazon Music ${collectionKind === 'album' ? 'Album' : 'Playlist'}`, + title: playlist.title || `Amazon Music ${collectionKind === 'album' ? 'Album' : 'Playlist'}`, owner: playlist.owner, artworkUrl: playlist.artworkUrl, provider: 'amazon-music', @@ -104,35 +78,18 @@ export class AmazonMusicProvider implements ProviderOptions { }; } - private async fetchTrack( - sourceUrl: string, - trackAsin: string, - signal?: AbortSignal - ): Promise { - const trackHtml = await this.fetchHtml( - this.buildTrackPlaybackUrl(sourceUrl, trackAsin), - signal - ); + private async fetchTrack(sourceUrl: string, trackAsin: string, signal?: AbortSignal): Promise { + const trackHtml = await this.fetchHtml(this.buildTrackPlaybackUrl(sourceUrl, trackAsin), signal); const trackPage = this.parseTrackHtml(trackHtml); const artist = trackPage.artistUrl - ? await this.getCachedName( - this.artistNameCache, - trackPage.artistUrl, - signal - ) + ? await this.getCachedName(this.artistNameCache, trackPage.artistUrl, signal) : undefined; const albumTitle = trackPage.albumUrl - ? await this.getCachedName( - this.albumTitleCache, - trackPage.albumUrl, - signal - ) + ? await this.getCachedName(this.albumTitleCache, trackPage.albumUrl, signal) : undefined; if (!trackPage.title || !artist) { - throw new Error( - 'Could not find Amazon Music track metadata in the page.' - ); + throw new Error('Could not find Amazon Music track metadata in the page.'); } const track: PlaylistTrack = { @@ -166,9 +123,7 @@ export class AmazonMusicProvider implements ProviderOptions { ); if (!title || trackAsins.length === 0) { - throw new Error( - 'Could not find Amazon Music collection metadata in the page.' - ); + throw new Error('Could not find Amazon Music collection metadata in the page.'); } return { @@ -180,23 +135,14 @@ export class AmazonMusicProvider implements ProviderOptions { } public parseTrackHtml(html: string): AmazonTrackPageData { - const sourceUrl = this.decodeHtmlAttribute( - this.extractMetaProperty(html, 'al:web:url') || '' - ); + const sourceUrl = this.decodeHtmlAttribute(this.extractMetaProperty(html, 'al:web:url') || ''); const duration = this.extractMetaProperty(html, 'music:duration'); return { - albumUrl: this.decodeHtmlAttribute( - this.extractMetaProperty(html, 'music:album') || '' - ), - artistUrl: this.decodeHtmlAttribute( - this.extractMetaProperty(html, 'music:musician') || '' - ), + albumUrl: this.decodeHtmlAttribute(this.extractMetaProperty(html, 'music:album') || ''), + artistUrl: this.decodeHtmlAttribute(this.extractMetaProperty(html, 'music:musician') || ''), artworkUrl: this.extractMetaProperty(html, 'og:image'), - durationMs: - duration && Number.isFinite(Number(duration)) - ? Math.round(Number(duration) * 1000) - : undefined, + durationMs: duration && Number.isFinite(Number(duration)) ? Math.round(Number(duration) * 1000) : undefined, sourceUrl: sourceUrl || undefined, title: this.extractMetaProperty(html, 'og:title'), }; @@ -214,9 +160,7 @@ export class AmazonMusicProvider implements ProviderOptions { } const html = await this.fetchHtml(normalizedUrl, signal); - const name = this.extractMetaProperty(html, 'og:title') - ?.split(' – ')[0] - ?.trim(); + const name = this.extractMetaProperty(html, 'og:title')?.split(' – ')[0]?.trim(); if (name) { cache.set(normalizedUrl, name); @@ -234,9 +178,7 @@ export class AmazonMusicProvider implements ProviderOptions { }); if (!response.ok) { - throw new Error( - `Amazon Music request failed with status ${response.status}.` - ); + throw new Error(`Amazon Music request failed with status ${response.status}.`); } return response.text(); @@ -244,14 +186,10 @@ export class AmazonMusicProvider implements ProviderOptions { private extractPlaylistId(sourceUrl: string): string { const parsedUrl = new URL(sourceUrl); - const match = parsedUrl.pathname.match( - /\/(?:albums|user-playlists|playlists)\/([A-Za-z0-9]+)/i - ); + const match = parsedUrl.pathname.match(/\/(?:albums|user-playlists|playlists)\/([A-Za-z0-9]+)/i); if (!match?.[1]) { - throw new Error( - 'Could not determine the Amazon Music collection id from the URL.' - ); + throw new Error('Could not determine the Amazon Music collection id from the URL.'); } return match[1]; @@ -272,22 +210,12 @@ export class AmazonMusicProvider implements ProviderOptions { } private getCollectionKind(sourceUrl: string): 'album' | 'playlist' { - return new URL(sourceUrl).pathname.includes('/albums/') - ? 'album' - : 'playlist'; + return new URL(sourceUrl).pathname.includes('/albums/') ? 'album' : 'playlist'; } - private extractMetaProperty( - html: string, - property: string - ): string | undefined { + private extractMetaProperty(html: string, property: string): string | undefined { const escapedProperty = property.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - const match = html.match( - new RegExp( - `') - .replace(/&#x([0-9a-f]+);/gi, (_, hex) => - String.fromCodePoint(Number.parseInt(hex, 16)) - ) - .replace(/&#(\d+);/g, (_, decimal) => - String.fromCodePoint(Number.parseInt(decimal, 10)) - ); + .replace(/&#x([0-9a-f]+);/gi, (_, hex) => String.fromCodePoint(Number.parseInt(hex, 16))) + .replace(/&#(\d+);/g, (_, decimal) => String.fromCodePoint(Number.parseInt(decimal, 10))); } private async mapWithConcurrency( @@ -315,19 +239,13 @@ export class AmazonMusicProvider implements ProviderOptions { const results = new Array(values.length); let cursor = 0; - const workers = Array.from( - { length: Math.min(concurrency, values.length) }, - async () => { - while (cursor < values.length) { - const currentIndex = cursor; - cursor += 1; - results[currentIndex] = await mapper( - values[currentIndex], - currentIndex - ); - } + const workers = Array.from({ length: Math.min(concurrency, values.length) }, async () => { + while (cursor < values.length) { + const currentIndex = cursor; + cursor += 1; + results[currentIndex] = await mapper(values[currentIndex], currentIndex); } - ); + }); await Promise.all(workers); return results; diff --git a/packages/cli/src/lib/providers/AppleMusic.test.ts b/packages/cli/src/lib/providers/AppleMusic.test.ts index 972af96..0c38189 100644 --- a/packages/cli/src/lib/providers/AppleMusic.test.ts +++ b/packages/cli/src/lib/providers/AppleMusic.test.ts @@ -23,20 +23,16 @@ describe('apple-music', () => { expect(playlist.id).toBe('pl.test'); expect(playlist.title).toBe('New Music Daily'); expect(playlist.owner).toBe('Apple Music'); - expect(playlist.artworkUrl).toBe( - 'https://is1-ssl.mzstatic.com/image/thumb/Features/v4/test/1200x1200SC.DN01.jpg' - ); + expect(playlist.artworkUrl).toBe('https://is1-ssl.mzstatic.com/image/thumb/Features/v4/test/1200x1200SC.DN01.jpg'); expect(playlist.tracks).toHaveLength(2); expect(playlist.tracks[0]).toEqual({ id: '1868862384', title: 'SWIM', artists: ['BTS'], album: 'ARIRANG', - artworkUrl: - 'https://is1-ssl.mzstatic.com/image/thumb/Music211/v4/test/1200x1200bb.jpg', + artworkUrl: 'https://is1-ssl.mzstatic.com/image/thumb/Music211/v4/test/1200x1200bb.jpg', durationMs: 159008, - sourceUrl: - 'https://music.apple.com/us/album/swim/1868862375?i=1868862384', + sourceUrl: 'https://music.apple.com/us/album/swim/1868862375?i=1868862384', }); }); @@ -63,11 +59,9 @@ describe('apple-music', () => { title: 'Never Gonna Give You Up', artists: ['Rick Astley'], album: 'Whenever You Need Somebody', - artworkUrl: - 'https://is1-ssl.mzstatic.com/image/thumb/Music211/v4/track/1200x1200bb.jpg', + artworkUrl: 'https://is1-ssl.mzstatic.com/image/thumb/Music211/v4/track/1200x1200bb.jpg', durationMs: 213000, - sourceUrl: - 'https://music.apple.com/us/album/never-gonna-give-you-up/1440837083?i=1440837084', + sourceUrl: 'https://music.apple.com/us/album/never-gonna-give-you-up/1440837083?i=1440837084', }); }); @@ -81,10 +75,7 @@ describe('apple-music', () => { `; - const playlist = provider.parsePlaylistHtml( - html, - 'https://music.apple.com/us/playlist/new-music-daily/pl.test' - ); + const playlist = provider.parsePlaylistHtml(html, 'https://music.apple.com/us/playlist/new-music-daily/pl.test'); expect(playlist.tracks[0]?.artworkUrl).toBe( 'https://is1-ssl.mzstatic.com/image/thumb/Features/v4/test/1200x1200SC.DN01.jpg' diff --git a/packages/cli/src/lib/providers/AppleMusic.ts b/packages/cli/src/lib/providers/AppleMusic.ts index 370adba..ff4f081 100644 --- a/packages/cli/src/lib/providers/AppleMusic.ts +++ b/packages/cli/src/lib/providers/AppleMusic.ts @@ -11,9 +11,7 @@ export class AppleMusicProvider implements ProviderOptions { const pathname = url.pathname.replace(/\/+$/, '') || '/'; return ( - ['music.apple.com', 'geo.music.apple.com'].includes( - url.hostname.toLowerCase() - ) && + ['music.apple.com', 'geo.music.apple.com'].includes(url.hostname.toLowerCase()) && [ /^\/(?:[a-z]{2}(?:-[a-z]{2})?\/)?album\/[^/]+\/\d+(?:\/)?$/i, /^\/(?:[a-z]{2}(?:-[a-z]{2})?\/)?playlist\/[^/]+\/pl\.[A-Za-z0-9.]+(?:\/)?$/i, @@ -22,10 +20,7 @@ export class AppleMusicProvider implements ProviderOptions { ); } - public async fetch( - url: string, - _options: FetchOptions - ): Promise { + public async fetch(url: string, _options: FetchOptions): Promise { const trackId = this.extractTrackId(url); const response = await fetch(url, { headers: { @@ -34,39 +29,21 @@ export class AppleMusicProvider implements ProviderOptions { }); if (!response.ok) { - throw new Error( - `Apple Music request failed with status ${response.status}.` - ); + throw new Error(`Apple Music request failed with status ${response.status}.`); } - return this.parsePlaylistHtml( - await response.text(), - url, - trackId ?? undefined - ); + return this.parsePlaylistHtml(await response.text(), url, trackId ?? undefined); } - public parsePlaylistHtml( - html: string, - sourceUrl: string, - trackId?: string - ): PlaylistMetadata { + public parsePlaylistHtml(html: string, sourceUrl: string, trackId?: string): PlaylistMetadata { const serializedData = this.extractSerializedServerData(html); const playlistHeader = this.findPlaylistHeader(serializedData); - const collectionArtworkUrl = this.normalizeArtworkUrl( - playlistHeader?.artwork - ); - const tracks = this.findTrackItems(serializedData).map((track) => - this.normalizeTrack(track, collectionArtworkUrl) - ); - const selectedTrack = trackId - ? tracks.find((track) => track.id === trackId) - : undefined; + const collectionArtworkUrl = this.normalizeArtworkUrl(playlistHeader?.artwork); + const tracks = this.findTrackItems(serializedData).map((track) => this.normalizeTrack(track, collectionArtworkUrl)); + const selectedTrack = trackId ? tracks.find((track) => track.id === trackId) : undefined; if (!playlistHeader) { - throw new Error( - 'Could not find Apple Music collection metadata in the page.' - ); + throw new Error('Could not find Apple Music collection metadata in the page.'); } if (tracks.length === 0 || (trackId && !selectedTrack)) { @@ -88,11 +65,8 @@ export class AppleMusicProvider implements ProviderOptions { const isAlbum = playlistHeader.contentDescriptor?.kind === 'album'; return { id: - playlistHeader.contentDescriptor?.identifiers?.storeAdamID ?? - `${isAlbum ? 'album' : 'playlist'}-${Date.now()}`, - title: - playlistHeader.title?.trim() || - `Apple Music ${isAlbum ? 'Album' : 'Playlist'}`, + playlistHeader.contentDescriptor?.identifiers?.storeAdamID ?? `${isAlbum ? 'album' : 'playlist'}-${Date.now()}`, + title: playlistHeader.title?.trim() || `Apple Music ${isAlbum ? 'Album' : 'Playlist'}`, owner: playlistHeader.subtitleLinks?.[0]?.title?.trim() || undefined, artworkUrl: collectionArtworkUrl, provider: 'apple-music', @@ -114,37 +88,26 @@ export class AppleMusicProvider implements ProviderOptions { } private extractSerializedServerData(html: string): AppleMusicSerializedData { - const match = html.match( - /