Bye Bye Desktop App

This commit is contained in:
Samidy 2026-03-29 13:09:32 +03:00
parent 69d71b99d1
commit 8ed52d8843
23 changed files with 1670 additions and 5031 deletions

View file

@ -1,11 +1,10 @@
# Node Alpine -- multi-arch (amd64 + arm64)
FROM oven/bun:1.3.10-alpine AS builder
FROM oven/bun:1.3.11-alpine AS builder
WORKDIR /app
# Install system dependencies required for Bun and Neutralino
# Install system dependencies required for Bun
RUN apk add --no-cache wget curl bash
RUN apk add --no-cache python3 make g++ && ln -sf python3 /usr/bin/python
# Accept build arguments for environment variables
ARG AUTH_ENABLED
@ -24,7 +23,7 @@ RUN bun install
# Copy the rest of the project
COPY . .
# Build the project (Bun is now available for "bun x neu build")
# Build the project
RUN bun run build
# Serve with nginx

125
bun.lock
View file

@ -15,7 +15,6 @@
"@ffmpeg/ffmpeg": "^0.12.15",
"@ffmpeg/util": "^0.12.2",
"@kawarp/core": "^1.1.1",
"@neutralinojs/lib": "^6.5.0",
"@svta/common-media-library": "^0.18.1",
"@uimaxbai/am-lyrics": "^1.1.4",
"appwrite": "^23.0.0",
@ -41,7 +40,6 @@
"devDependencies": {
"@capacitor/assets": "^3.0.5",
"@capacitor/cli": "^8.2.0",
"@neutralinojs/neu": "^11.7.0",
"@types/node": "^25.3.5",
"eslint": "^9.39.3",
"eslint-config-prettier": "^10.1.8",
@ -55,7 +53,6 @@
"stylelint-config-standard-scss": "^16.0.0",
"typescript": "^5.9.3",
"vite": "^7.3.1",
"vite-plugin-neutralino": "^1.0.3",
"vite-plugin-pwa": "^1.2.0",
},
},
@ -292,8 +289,6 @@
"@dual-bundle/import-meta-resolve": ["@dual-bundle/import-meta-resolve@4.2.1", "", {}, "sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg=="],
"@electron/asar": ["@electron/asar@3.4.1", "", { "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" }, "bin": { "asar": "bin/asar.js" } }, "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA=="],
"@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q=="],
@ -476,10 +471,6 @@
"@lit/reactive-element": ["@lit/reactive-element@2.1.2", "", { "dependencies": { "@lit-labs/ssr-dom-shim": "^1.5.0" } }, "sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A=="],
"@neutralinojs/lib": ["@neutralinojs/lib@6.5.0", "", { "optionalDependencies": { "@rollup/rollup-darwin-x64": "*", "@rollup/rollup-linux-x64-gnu": "*" } }, "sha512-ECgYh+CXAfMR1JVTvDw/kHhjL6LzNNcjk8Va1DZUSBkUwROqFTQ7zseFeuFtwGvutqvlWiwpGmU3s11rg/bdvA=="],
"@neutralinojs/neu": ["@neutralinojs/neu@11.7.0", "", { "dependencies": { "@electron/asar": "^3.0.3", "chalk": "^4.1.0", "chokidar": "^4.0.3", "commander": "^7.2.0", "configstore": "^5.0.1", "edit-json-file": "^1.6.2", "follow-redirects": "^1.13.1", "fs-extra": "^9.0.1", "pe-library": "^1.0.1", "png2icons": "^2.0.1", "postject": "1.0.0-alpha.6", "recursive-readdir": "^2.2.2", "resedit": "^2.0.2", "spawn-command": "^1.0.0", "tcp-port-used": "^1.0.2", "uuid": "^8.3.2", "websocket": "^1.0.35", "zip-lib": "^1.0.4" }, "bin": { "neu": "bin/neu.js" } }, "sha512-fUqvR70a+BpKI9mrD92ldZkVC24Rs8XL/9m7zmOCLgCRys3yuWy7vEsxpHzKMzqTiQJkTYIsLmcR8VMzNIjuSw=="],
"@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
@ -728,8 +719,6 @@
"chevrotain": ["chevrotain@7.1.1", "", { "dependencies": { "regexp-to-ast": "0.5.0" } }, "sha512-wy3mC1x4ye+O+QkEinVJkPf5u2vsrDIYW9G7ZuwFl6v/Yu0LwUuT2POsb+NUWApebyxfkQq6+yDfRExbnI5rcw=="],
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
"clean-stack": ["clean-stack@2.2.0", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="],
@ -756,8 +745,6 @@
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
"configstore": ["configstore@5.0.1", "", { "dependencies": { "dot-prop": "^5.2.0", "graceful-fs": "^4.1.2", "make-dir": "^3.0.0", "unique-string": "^2.0.0", "write-file-atomic": "^3.0.0", "xdg-basedir": "^4.0.0" } }, "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA=="],
"conventional-changelog": ["conventional-changelog@3.1.25", "", { "dependencies": { "conventional-changelog-angular": "^5.0.12", "conventional-changelog-atom": "^2.0.8", "conventional-changelog-codemirror": "^2.0.8", "conventional-changelog-conventionalcommits": "^4.5.0", "conventional-changelog-core": "^4.2.1", "conventional-changelog-ember": "^2.0.9", "conventional-changelog-eslint": "^3.0.9", "conventional-changelog-express": "^2.0.6", "conventional-changelog-jquery": "^3.0.11", "conventional-changelog-jshint": "^2.0.9", "conventional-changelog-preset-loader": "^2.3.4" } }, "sha512-ryhi3fd1mKf3fSjbLXOfK2D06YwKNic1nC9mWqybBHdObPd8KJ2vjaXZfYj1U23t+V8T8n0d7gwnc9XbIdFbyQ=="],
"conventional-changelog-angular": ["conventional-changelog-angular@5.0.13", "", { "dependencies": { "compare-func": "^2.0.0", "q": "^1.5.1" } }, "sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA=="],
@ -822,8 +809,6 @@
"csso": ["csso@5.0.5", "", { "dependencies": { "css-tree": "~2.2.0" } }, "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ=="],
"d": ["d@1.0.2", "", { "dependencies": { "es5-ext": "^0.10.64", "type": "^2.7.2" } }, "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw=="],
"dargs": ["dargs@7.0.0", "", {}, "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg=="],
"data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
@ -880,8 +865,6 @@
"ecma-proposal-math-extensions": ["ecma-proposal-math-extensions@0.0.2", "", {}, "sha512-80BnDp2Fn7RxXlEr5HHZblniY4aQ97MOAicdWWpSo0vkQiISSE9wLR4SqxKsu4gCtXFBIPPzy8JMhay4NWRg/Q=="],
"edit-json-file": ["edit-json-file@1.8.1", "", { "dependencies": { "find-value": "^1.0.12", "iterate-object": "^1.3.4", "r-json": "^1.2.10", "set-value": "^4.1.0", "w-json": "^1.3.10" } }, "sha512-x8L381+GwqxQejPipwrUZIyAg5gDQ9tLVwiETOspgXiaQztLsrOm7luBW5+Pe31aNezuzDY79YyzF+7viCRPXA=="],
"ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
"electron-to-chromium": ["electron-to-chromium@1.5.328", "", {}, "sha512-QNQ5l45DzYytThO21403XN3FvK0hOkWDG8viNf6jqS42msJ8I4tGDSpBCgvDRRPnkffafiwAym2X2eHeGD2V0w=="],
@ -912,12 +895,6 @@
"es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="],
"es5-ext": ["es5-ext@0.10.64", "", { "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", "esniff": "^2.0.1", "next-tick": "^1.1.0" } }, "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg=="],
"es6-iterator": ["es6-iterator@2.0.3", "", { "dependencies": { "d": "1", "es5-ext": "^0.10.35", "es6-symbol": "^3.1.1" } }, "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g=="],
"es6-symbol": ["es6-symbol@3.1.4", "", { "dependencies": { "d": "^1.0.2", "ext": "^1.7.0" } }, "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg=="],
"esbuild": ["esbuild@0.27.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ=="],
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
@ -932,8 +909,6 @@
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
"esniff": ["esniff@2.0.1", "", { "dependencies": { "d": "^1.0.1", "es5-ext": "^0.10.62", "event-emitter": "^0.3.5", "type": "^2.7.2" } }, "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg=="],
"espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
"esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="],
@ -946,8 +921,6 @@
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
"event-emitter": ["event-emitter@0.3.5", "", { "dependencies": { "d": "1", "es5-ext": "~0.10.14" } }, "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA=="],
"eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
"events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
@ -956,8 +929,6 @@
"expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="],
"ext": ["ext@1.7.0", "", { "dependencies": { "type": "^2.7.2" } }, "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="],
@ -986,14 +957,10 @@
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
"find-value": ["find-value@1.0.13", "", {}, "sha512-epNL4mnl3HUYrwVQtZ8s0nxkE4ogAoSqO1V1fa670Ww1fXp8Yr74zNS9Aib/vLNf0rq0AF/4mboo7ev5XkikXQ=="],
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
"flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="],
"follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
@ -1118,8 +1085,6 @@
"internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
"ip-regex": ["ip-regex@4.3.0", "", {}, "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q=="],
"is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
"is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
@ -1160,7 +1125,7 @@
"is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="],
"is-obj": ["is-obj@2.0.0", "", {}, "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="],
"is-obj": ["is-obj@1.0.1", "", {}, "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg=="],
"is-path-cwd": ["is-path-cwd@2.2.0", "", {}, "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ=="],
@ -1170,8 +1135,6 @@
"is-plain-object": ["is-plain-object@5.0.0", "", {}, "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="],
"is-primitive": ["is-primitive@3.0.1", "", {}, "sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w=="],
"is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
"is-regexp": ["is-regexp@1.0.0", "", {}, "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA=="],
@ -1190,10 +1153,6 @@
"is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="],
"is-typedarray": ["is-typedarray@1.0.0", "", {}, "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="],
"is-url": ["is-url@1.2.4", "", {}, "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww=="],
"is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="],
"is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="],
@ -1202,16 +1161,10 @@
"is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
"is2": ["is2@2.0.9", "", { "dependencies": { "deep-is": "^0.1.3", "ip-regex": "^4.1.0", "is-url": "^1.2.4" } }, "sha512-rZkHeBn9Zzq52sd9IUIV3a5mfwBY+o2HePMh0wkGBM4z4qjvy2GwVxQ6nNXSfw6MmVP6gf1QIlWjiOavhM3x5g=="],
"isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"isobject": ["isobject@3.0.1", "", {}, "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg=="],
"iterate-object": ["iterate-object@1.3.5", "", {}, "sha512-eL23u8oFooYTq6TtJKjp2RYjZnCkUYQvC0T/6fJfWykXJ3quvdDdzKZ3CEjy8b3JGOvLTjDYMEMIp5243R906A=="],
"jackspeak": ["jackspeak@4.2.3", "", { "dependencies": { "@isaacs/cliui": "^9.0.0" } }, "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg=="],
"jake": ["jake@10.9.4", "", { "dependencies": { "async": "^3.2.6", "filelist": "^1.0.4", "picocolors": "^1.1.1" }, "bin": { "jake": "bin/cli.js" } }, "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA=="],
@ -1290,8 +1243,6 @@
"magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="],
"make-dir": ["make-dir@3.1.0", "", { "dependencies": { "semver": "^6.0.0" } }, "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw=="],
"make-error": ["make-error@1.3.6", "", {}, "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="],
"map-obj": ["map-obj@4.3.0", "", {}, "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ=="],
@ -1346,8 +1297,6 @@
"neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="],
"next-tick": ["next-tick@1.1.0", "", {}, "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="],
"node-abi": ["node-abi@3.89.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA=="],
"node-addon-api": ["node-addon-api@6.1.0", "", {}, "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="],
@ -1412,8 +1361,6 @@
"path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
"pe-library": ["pe-library@1.0.1", "", {}, "sha512-nh39Mo1eGWmZS7y+mK/dQIqg7S1lp38DpRxkyoHf0ZcUs/HDc+yyTjuOtTvSMZHmfSLuSQaX945u05Y2Q6UWZg=="],
"pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
@ -1424,8 +1371,6 @@
"plist": ["plist@3.1.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ=="],
"png2icons": ["png2icons@2.0.1", "", { "bin": { "png2icons": "./png2icons-cli.js" } }, "sha512-GDEQJr8OG4e6JMp7mABtXFSEpgJa1CCpbQiAR+EjhkHJHnUL9zPPtbOrjsMD8gUbikgv3j7x404b0YJsV3aVFA=="],
"pocketbase": ["pocketbase@0.26.8", "", {}, "sha512-aQ/ewvS7ncvAE8wxoW10iAZu6ElgbeFpBhKPnCfvRovNzm2gW8u/sQNPGN6vNgVEagz44kK//C61oKjfa+7Low=="],
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
@ -1444,8 +1389,6 @@
"postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
"postject": ["postject@1.0.0-alpha.6", "", { "dependencies": { "commander": "^9.4.0" }, "bin": { "postject": "dist/cli.js" } }, "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A=="],
"prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="],
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
@ -1470,8 +1413,6 @@
"quick-lru": ["quick-lru@4.0.1", "", {}, "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g=="],
"r-json": ["r-json@1.3.1", "", { "dependencies": { "w-json": "1.3.10" } }, "sha512-5nhRFfjVMQdrwKUfUlRpDUCocdKtjSnYZ1R/86mpZDV3MfsZ3dYYNjSGuMX+mPBvFvQBhdzxSqxkuLPLv4uFGg=="],
"rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="],
"read-pkg": ["read-pkg@3.0.0", "", { "dependencies": { "load-json-file": "^4.0.0", "normalize-package-data": "^2.3.2", "path-type": "^3.0.0" } }, "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA=="],
@ -1480,10 +1421,6 @@
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"recursive-readdir": ["recursive-readdir@2.2.3", "", { "dependencies": { "minimatch": "^3.0.5" } }, "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA=="],
"redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="],
"reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
@ -1512,8 +1449,6 @@
"require-main-filename": ["require-main-filename@2.0.0", "", {}, "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="],
"resedit": ["resedit@2.0.3", "", { "dependencies": { "pe-library": "^1.0.1" } }, "sha512-oTeemxwoMuxxTYxXUwjkrOPfngTQehlv0/HoYFNkB4uzsP1Un1A9nI8JQKGOFkxpqkC7qkMs0lUsGrvUlbLNUA=="],
"resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
"resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="],
@ -1548,8 +1483,6 @@
"set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="],
"set-value": ["set-value@4.1.0", "", { "dependencies": { "is-plain-object": "^2.0.4", "is-primitive": "^3.0.1" } }, "sha512-zTEg4HL0RwVrqcWs3ztF+x1vkxfm0lP+MQQFPiMJTKVceBwEV0A569Ou8l9IYQG8jOZdMVI1hGsc0tmeD2o/Lw=="],
"shaka-player": ["shaka-player@5.0.8", "", {}, "sha512-f886rKRvQ0IKhWGk+rINS++YTjTJyc4DT5YypTsHW6wiNV9fiHi2n35+lg5R+hj9RfhqkmJHMjJb3gprUTNa8w=="],
"sharp": ["sharp@0.32.6", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.2", "node-addon-api": "^6.1.0", "prebuild-install": "^7.1.1", "semver": "^7.5.4", "simple-get": "^4.0.1", "tar-fs": "^3.0.4", "tunnel-agent": "^0.6.0" } }, "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w=="],
@ -1594,8 +1527,6 @@
"sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
"spawn-command": ["spawn-command@1.0.0", "", {}, "sha512-zsboEQnjeF6tSJ8SRnojMr22HyFEaRaohgTt0Kgx3BgzkXYiboh09vpmZVIq1HOLzkFZDgFJJfwGUqSbb5fQQQ=="],
"spdx-correct": ["spdx-correct@3.2.0", "", { "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA=="],
"spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="],
@ -1668,8 +1599,6 @@
"tar-stream": ["tar-stream@3.1.8", "", { "dependencies": { "b4a": "^1.6.4", "bare-fs": "^4.5.5", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ=="],
"tcp-port-used": ["tcp-port-used@1.0.2", "", { "dependencies": { "debug": "4.3.1", "is2": "^2.0.6" } }, "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA=="],
"teex": ["teex@1.0.1", "", { "dependencies": { "streamx": "^2.12.5" } }, "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg=="],
"temp-dir": ["temp-dir@2.0.0", "", {}, "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg=="],
@ -1706,8 +1635,6 @@
"tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
"type": ["type@2.7.3", "", {}, "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="],
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
"type-fest": ["type-fest@0.16.0", "", {}, "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg=="],
@ -1720,8 +1647,6 @@
"typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="],
"typedarray-to-buffer": ["typedarray-to-buffer@3.1.5", "", { "dependencies": { "is-typedarray": "^1.0.0" } }, "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="],
@ -1766,16 +1691,10 @@
"vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
"vite-plugin-neutralino": ["vite-plugin-neutralino@1.0.3", "", {}, "sha512-E/PSTCp7m7efk7fa4eE12WQ+5XGNP72gkhOv61X4i0n+NcnrwKtJgEgeCDGUXeLp4ngTfU/CP+EF55jmZs/0Jw=="],
"vite-plugin-pwa": ["vite-plugin-pwa@1.2.0", "", { "dependencies": { "debug": "^4.3.6", "pretty-bytes": "^6.1.1", "tinyglobby": "^0.2.10", "workbox-build": "^7.4.0", "workbox-window": "^7.4.0" }, "peerDependencies": { "@vite-pwa/assets-generator": "^1.0.0", "vite": "^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@vite-pwa/assets-generator"] }, "sha512-a2xld+SJshT9Lgcv8Ji4+srFJL4k/1bVbd1x06JIkvecpQkwkvCncD1+gSzcdm3s+owWLpMJerG3aN5jupJEVw=="],
"w-json": ["w-json@1.3.11", "", {}, "sha512-Xa8vTinB5XBIYZlcN8YyHpE625pBU6k+lvCetTQM+FKxRtLJxAY9zUVZbRqCqkMeEGbQpKvGUzwh4wZKGem+ag=="],
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
"websocket": ["websocket@1.0.35", "", { "dependencies": { "bufferutil": "^4.0.1", "debug": "^2.2.0", "es5-ext": "^0.10.63", "typedarray-to-buffer": "^3.1.5", "utf-8-validate": "^5.0.2", "yaeti": "^0.0.6" } }, "sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q=="],
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
@ -1838,8 +1757,6 @@
"xcode": ["xcode@3.0.1", "", { "dependencies": { "simple-plist": "^1.1.0", "uuid": "^7.0.3" } }, "sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA=="],
"xdg-basedir": ["xdg-basedir@4.0.0", "", {}, "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q=="],
"xml": ["xml@1.0.1", "", {}, "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw=="],
"xml-js": ["xml-js@1.6.11", "", { "dependencies": { "sax": "^1.2.4" }, "bin": { "xml-js": "./bin/cli.js" } }, "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g=="],
@ -1854,8 +1771,6 @@
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
"yaeti": ["yaeti@0.0.6", "", {}, "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug=="],
"yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
"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=="],
@ -1864,8 +1779,6 @@
"yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="],
"yazl": ["yazl@3.3.1", "", { "dependencies": { "buffer-crc32": "^1.0.0" } }, "sha512-BbETDVWG+VcMUle37k5Fqp//7SDOK2/1+T7X8TD96M3D9G8jK5VLUdQVdVjGi8im7FGkazX7kk5hkU8X4L5Bng=="],
"yn": ["yn@3.1.1", "", {}, "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q=="],
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
@ -1874,8 +1787,6 @@
"youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="],
"zip-lib": ["zip-lib@1.3.1", "", { "dependencies": { "yauzl": "^3.2.1", "yazl": "^3.3.1" } }, "sha512-fgtJsv4yuqbgo7MC2F2n2pVlAGO9VjHOzmK2p9yPw8LKNRfAbm7Mmvw0Ji6AZRDAqJqT3XGe6AzwwI6LggobVA=="],
"@apideck/better-ajv-errors/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
"@babel/core/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
@ -1912,10 +1823,6 @@
"@capacitor/cli/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@electron/asar/commander": ["commander@5.1.0", "", {}, "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="],
"@electron/asar/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
"@emnapi/runtime/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
@ -1968,12 +1875,6 @@
"@keyv/bigmap/keyv": ["keyv@5.6.0", "", { "dependencies": { "@keyv/serialize": "^1.1.1" } }, "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw=="],
"@neutralinojs/neu/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
"@neutralinojs/neu/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="],
"@neutralinojs/neu/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
"@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
"@rollup/plugin-babel/rollup": ["rollup@2.80.0", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ=="],
@ -2000,8 +1901,6 @@
"cacheable/keyv": ["keyv@5.6.0", "", { "dependencies": { "@keyv/serialize": "^1.1.1" } }, "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw=="],
"configstore/write-file-atomic": ["write-file-atomic@3.0.3", "", { "dependencies": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", "signal-exit": "^3.0.2", "typedarray-to-buffer": "^3.1.5" } }, "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q=="],
"conventional-changelog-writer/meow": ["meow@8.1.2", "", { "dependencies": { "@types/minimist": "^1.2.0", "camelcase-keys": "^6.2.2", "decamelize-keys": "^1.1.0", "hard-rejection": "^2.1.0", "minimist-options": "4.1.0", "normalize-package-data": "^3.0.0", "read-pkg-up": "^7.0.1", "redent": "^3.0.0", "trim-newlines": "^3.0.0", "type-fest": "^0.18.0", "yargs-parser": "^20.2.3" } }, "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q=="],
"conventional-changelog-writer/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
@ -2018,6 +1917,8 @@
"del/rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
"dot-prop/is-obj": ["is-obj@2.0.0", "", {}, "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="],
"elementtree/sax": ["sax@1.1.4", "", {}, "sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg=="],
"eslint/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
@ -2060,8 +1961,6 @@
"load-json-file/pify": ["pify@3.0.0", "", {}, "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg=="],
"make-dir/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"mergexml/xpath": ["xpath@0.0.27", "", {}, "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ=="],
"micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
@ -2360,16 +2259,12 @@
"plist/@xmldom/xmldom": ["@xmldom/xmldom@0.8.11", "", {}, "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw=="],
"postject/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="],
"prebuild-install/tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="],
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
"qified/hookified": ["hookified@2.1.0", "", {}, "sha512-ootKng4eaxNxa7rx6FJv2YKef3DuhqbEj3l70oGXwddPQEEnISm50TEZQclqiLTAtilT2nu7TErtCO523hHkyg=="],
"r-json/w-json": ["w-json@1.3.10", "", {}, "sha512-XadVyw0xE+oZ5FGApXsdswv96rOhStzKqL53uSe5UaTadABGkWIg1+DTx8kiZ/VqTZTBneoL0l65RcPe4W3ecw=="],
"rc/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
"rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="],
@ -2386,14 +2281,10 @@
"replace/yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="],
"set-value/is-plain-object": ["is-plain-object@2.0.4", "", { "dependencies": { "isobject": "^3.0.1" } }, "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og=="],
"simple-plist/bplist-parser": ["bplist-parser@0.3.1", "", { "dependencies": { "big-integer": "1.6.x" } }, "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA=="],
"simple-swizzle/is-arrayish": ["is-arrayish@0.3.4", "", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="],
"stringify-object/is-obj": ["is-obj@1.0.1", "", {}, "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg=="],
"stylelint/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"stylelint/file-entry-cache": ["file-entry-cache@11.1.2", "", { "dependencies": { "flat-cache": "^6.1.20" } }, "sha512-N2WFfK12gmrK1c1GXOqiAJ1tc5YE+R53zvQ+t5P8S5XhnmKYVB5eZEiLNZKDSmoG8wqqbF9EXYBBW/nef19log=="],
@ -2404,16 +2295,12 @@
"table/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
"tcp-port-used/debug": ["debug@4.3.1", "", { "dependencies": { "ms": "2.1.2" } }, "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ=="],
"terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
"ts-node/diff": ["diff@4.0.4", "", {}, "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ=="],
"vite-plugin-pwa/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"websocket/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"workbox-build/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
"workbox-build/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="],
@ -2432,10 +2319,6 @@
"xml2js/xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
"yazl/buffer-crc32": ["buffer-crc32@1.0.0", "", {}, "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w=="],
"zip-lib/yauzl": ["yauzl@3.2.1", "", { "dependencies": { "buffer-crc32": "~0.2.3", "pend": "~1.2.0" } }, "sha512-k1isifdbpNSFEHFJ1ZY4YDewv0IH9FR61lDetaRMD3j2ae3bIXGV+7c+LHCqtQGofSd8PIyV4X6+dHMAnSr60A=="],
"@apideck/better-ajv-errors/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"@babel/core/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
@ -2584,8 +2467,6 @@
"vite-plugin-pwa/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"websocket/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"workbox-build/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"workbox-build/glob/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],

View file

@ -11,7 +11,6 @@ export default [
'**/bin/**',
'**/www/**',
'**/public/lib/**',
'**/public/neutralino.js',
],
},
js.configs.recommended,
@ -23,7 +22,6 @@ export default [
globals: {
...globals.browser,
...globals.node,
Neutralino: 'readonly',
},
},
rules: {

View file

@ -1,102 +0,0 @@
# bridge.ps1 - Diagnostic Version
$Log = Join-Path $PSScriptRoot "bridge.log"
function Log($m) { Add-Content $Log "$(Get-Date -f 'HH:mm:ss') - $m" }
Log "--- START (DIAGNOSTIC) ---"
# 1. PID
$p = Get-Process Monochrome -ErrorAction SilentlyContinue | Select-Object -First 1
if (-not $p) { $p = Get-Process neutralino-win_x64 -ErrorAction SilentlyContinue | Select-Object -First 1 }
$pid_to_send = if ($p) { $p.Id } else { [System.Diagnostics.Process]::GetCurrentProcess().Id }
# 2. Discord Connection
function Get-Pipe {
for ($i = 0; $i -le 9; $i++) {
try {
$pn = "discord-ipc-$i"
$p = New-Object System.IO.Pipes.NamedPipeClientStream(".", $pn, [System.IO.Pipes.PipeDirection]::InOut)
$p.Connect(100)
return $p
} catch { }
}
return $null
}
$pipe = Get-Pipe; if (-not $pipe) { Log "Discord Fail"; exit }
function Send-Packet($op, $json) {
if ($op -eq 1) { Log "Sending Activity: $json" }
$j = [System.Text.Encoding]::UTF8.GetBytes($json)
[byte[]]$pkt = [BitConverter]::GetBytes([int]$op) + [BitConverter]::GetBytes([int]$j.Length) + $j
$pipe.Write($pkt, 0, $pkt.Length); $pipe.Flush()
}
# 3. Handshake
Send-Packet 0 (@{ v = 1; client_id = "1462186088184549661" } | ConvertTo-Json -Compress)
$h = New-Object byte[] 8; if ($pipe.Read($h, 0, 8) -eq 8) {
$l = [BitConverter]::ToInt32($h, 4); $b = New-Object byte[] $l; $pipe.Read($b, 0, $l) | Out-Null
Log "Handshake OK"
}
function Set-Activity($d, $s, $img, $start, $end, $large_text, $small_img, $small_txt) {
$activity = @{
details = [string]$d
state = [string]$s
type = 2
assets = @{
large_image = if ($img -and $img.StartsWith("http")) { [string]$img } else { "monochrome" }
large_text = if ($large_text) { [string]$large_text } else { "Monochrome" }
}
}
if ($small_img) {
$activity.assets.small_image = [string]$small_img
$activity.assets.small_text = [string]$small_txt
}
if ($start -or $end) {
$activity.timestamps = @{}
if ($start) { $activity.timestamps.start = [long]$start }
if ($end) { $activity.timestamps.end = [long]$end }
}
# CRITICAL: -Depth 10 ensures 'assets' is not stringified as a class name
$payload = @{
cmd = "SET_ACTIVITY"
args = @{ pid = [int]$pid_to_send; activity = $activity }
nonce = [Guid]::NewGuid().ToString()
} | ConvertTo-Json -Compress -Depth 10
Send-Packet 1 $payload
}
Start-Sleep -Seconds 1
Set-Activity "Idling" "Monochrome" $null $null $null $null $null $null
# 4. Config & WS
$line = [Console]::In.ReadLine()
if (-not $line) { exit }
$config = $line | ConvertFrom-Json
$ws = New-Object System.Net.WebSockets.ClientWebSocket
try {
$uri = [Uri]"ws://127.0.0.1:$($config.nlPort)?extensionId=$($config.nlExtensionId)&connectToken=$($config.nlConnectToken)"
$ws.ConnectAsync($uri, [System.Threading.CancellationToken]::None).Wait()
Log "WS Connected"
} catch { exit }
# 5. Loop
$buf = New-Object byte[] 65536
while ($ws.State -eq "Open") {
$task = $ws.ReceiveAsync((New-Object ArraySegment[byte] -ArgumentList @(,$buf)), [System.Threading.CancellationToken]::None)
while (-not $task.Wait(1000)) { if (-not (Get-Process -Id $pid_to_send -ErrorAction SilentlyContinue)) { exit } }
if ($task.Result.Count -gt 0) {
try {
$raw = [System.Text.Encoding]::UTF8.GetString($buf, 0, $task.Result.Count)
$msg = $raw | ConvertFrom-Json
if ($msg.event -eq "discord:update") {
Set-Activity $msg.data.details $msg.data.state $msg.data.largeImageKey $msg.data.startTimestamp $msg.data.endTimestamp $msg.data.largeImageText $msg.data.smallImageKey $msg.data.smallImageText
}
elseif ($msg.event -eq "discord:clear") { Set-Activity "Idling" "Monochrome" $null $null $null $null $null $null }
} catch {}
}
}

View file

@ -1,165 +0,0 @@
# bridge.py - Production Discord RPC Bridge (Linux/macOS)
import sys, json, socket, struct, os, uuid, base64, time
CLIENT_ID = "1462186088184549661"
LAST_STATUS = ""
def get_discord_path():
for i in range(10):
path = os.path.join(os.environ.get('XDG_RUNTIME_DIR', '/tmp'), f'discord-ipc-{i}')
if os.path.exists(path): return path
return None
def send_packet(s, op, data):
payload = json.dumps(data).encode('utf-8')
header = struct.pack('<II', op, len(payload))
s.sendall(header + payload)
def recv_packet(s):
try:
header = s.recv(8)
if len(header) < 8: return None
op, length = struct.unpack('<II', header)
payload = s.recv(length)
return json.loads(payload.decode('utf-8'))
except Exception:
# Ignore errors and return None
return None
def set_activity(ds, pid, details, state, img=None, start=None, end=None, large_text=None, small_img=None, small_txt=None):
global LAST_STATUS
current = f"{details}-{state}-{img}-{start}-{end}-{large_text}-{small_img}-{small_txt}"
if current == LAST_STATUS: return
LAST_STATUS = current
activity = {
"details": str(details or "Idling"),
"state": str(state or "Monochrome"),
"type": 2, # Listening
"assets": {
"large_image": img if img and img.startswith('http') else "monochrome",
"large_text": str(large_text or "Monochrome")
}
}
if small_img:
activity["assets"]["small_image"] = str(small_img)
activity["assets"]["small_text"] = str(small_txt or "")
if start or end:
activity["timestamps"] = {}
if start: activity["timestamps"]["start"] = int(start)
if end: activity["timestamps"]["end"] = int(end)
send_packet(ds, 1, {
"cmd": "SET_ACTIVITY",
"args": {"pid": pid, "activity": activity},
"nonce": str(uuid.uuid4())
})
def main():
# 1. Read config
try:
line = sys.stdin.readline()
if not line: return
config = json.loads(line)
except Exception:
# Ignore errors and exit
return
ppid = os.getppid()
# 2. Connect to Discord
ipc_path = get_discord_path()
if not ipc_path: return
try:
ds = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
ds.connect(ipc_path)
except Exception:
# Ignore connection errors and exit
return
# 3. Handshake
send_packet(ds, 0, {"v": 1, "client_id": CLIENT_ID})
recv_packet(ds) # Mandatory read
time.sleep(0.5)
set_activity(ds, ppid, "Idling", "Monochrome")
# 4. Minimal WebSocket Client
ws = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ws.settimeout(1.0)
try:
ws.connect(('127.0.0.1', int(config['nlPort'])))
except Exception:
# Ignore connection errors and exit
return
key = base64.b64encode(os.urandom(16)).decode()
handshake = (
f"GET /?extensionId={config['nlExtensionId']}&connectToken={config['nlConnectToken']} HTTP/1.1\r\n"
f"Host: 127.0.0.1:{config['nlPort']}\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
f"Sec-WebSocket-Key: {key}\r\n"
"Sec-WebSocket-Version: 13\r\n\r\n"
)
ws.sendall(handshake.encode())
# Skip HTTP response header
resp = b""
while b"\r\n\r\n" not in resp:
try:
chunk = ws.recv(1024)
if not chunk: break
resp += chunk
except socket.timeout: continue
# 5. Loop
while True:
# Watchdog
try:
os.kill(ppid, 0)
except OSError: break
try:
head = ws.recv(2)
if not head: break
length = head[1] & 127
if length == 126: length = struct.unpack(">H", ws.recv(2))[0]
elif length == 127: length = struct.unpack(">Q", ws.recv(8))[0]
data = b""
while len(data) < length:
data += ws.recv(length - len(data))
msg = json.loads(data.decode('utf-8'))
if msg['event'] == 'discord:update':
d = msg['data']
set_activity(ds, ppid, d.get('details'), d.get('state'), d.get('largeImageKey'), d.get('startTimestamp'), d.get('endTimestamp'), d.get('largeImageText'), d.get('smallImageKey'), d.get('smallImageText'))
elif msg['event'] == 'discord:clear':
set_activity(ds, ppid, "Idling", "Monochrome")
elif msg['event'] == 'windowClose':
break
except socket.timeout:
# Timeout is expected, continue polling
continue
except Exception:
# Ignore other errors and continue
continue
# Cleanup
try:
send_packet(ds, 1, {
"cmd": "SET_ACTIVITY",
"args": {"pid": ppid, "activity": None},
"nonce": str(uuid.uuid4())
})
time.sleep(0.1)
ds.close()
except Exception:
# Ignore cleanup errors
pass
if __name__ == "__main__":
main()

View file

@ -1432,7 +1432,7 @@
</div>
</div>
<div id="desktop-update-modal" class="modal">
<div id="desktop-update-modal" class="modal">
<div class="modal-overlay"></div>
<div class="modal-content">
<h3>Update Available</h3>
@ -1455,7 +1455,7 @@
</div>
</div>
</div>
<div id="command-palette-overlay" style="display: none">
<div class="command-palette">
<div class="command-palette-header">
@ -4394,15 +4394,6 @@
<span class="slider"></span>
</label>
</div>
<div class="setting-item" id="desktop-update-container" style="display: none">
<div class="info">
<span class="label">Desktop Update</span>
<span class="description">Check for updates to the desktop application</span>
</div>
<button id="check-desktop-updates-btn" class="btn-secondary">
Check for Updates
</button>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Analytics</span>

201
js/app.js
View file

@ -470,77 +470,6 @@ document.addEventListener('DOMContentLoaded', async () => {
// Initialize tracker
initTracker();
// Linux Media Keys Fix
if (window.NL_MODE) {
import('./desktop/neutralino-bridge.js').then(({ events }) => {
events.on('mediaNext', () => Player.instance.playNext());
events.on('mediaPrevious', () => Player.instance.playPrev());
events.on('mediaPlayPause', () => Player.instance.handlePlayPause());
events.on('mediaStop', () => {
const el = Player.instance.activeElement;
el.pause();
el.currentTime = 0;
});
console.log('Media keys initialized via bridge');
});
}
// Initialize desktop features if in Neutralino mode
if (
typeof window !== 'undefined' &&
(window.NL_MODE ||
window.location.search.includes('mode=neutralino') ||
window.location.search.includes('nl_port='))
) {
window.NL_MODE = true;
try {
const desktopModule = await import('./desktop/desktop.js');
await desktopModule.initDesktop(Player.instance);
import('./desktop/neutralino-bridge.js').then(({ updater }) => {
setTimeout(async () => {
try {
// my worker should detect a users OS and serve the right ver
const update = await updater.checkForUpdates('https://update.samidy.xyz/update.json');
if (update && update.available) {
const modal = document.getElementById('desktop-update-modal');
const notes = document.getElementById('desktop-update-notes');
const confirmBtn = document.getElementById('desktop-update-confirm');
const cancelBtn = document.getElementById('desktop-update-cancel');
if (modal) {
notes.innerHTML = update.notes || 'Bug fixes and improvements.';
modal.classList.add('active');
confirmBtn.onclick = async () => {
confirmBtn.disabled = true;
confirmBtn.textContent = 'Updating...';
try {
await updater.install();
} catch (err) {
console.error(err);
confirmBtn.textContent = 'Failed';
setTimeout(() => {
confirmBtn.disabled = false;
confirmBtn.textContent = 'Update Now';
}, 2000);
}
};
cancelBtn.onclick = () => modal.classList.remove('active');
}
}
} catch (e) {
console.warn('Failed to check for desktop updates:', e);
}
}, 3000);
});
} catch (err) {
console.error('Failed to load desktop module:', err);
}
}
const castBtn = document.getElementById('cast-btn');
initializeCasting(audioPlayer, castBtn);
@ -568,83 +497,42 @@ document.addEventListener('DOMContentLoaded', async () => {
const handle = await db.getSetting('local_folder_handle');
if (!handle) return;
const isNeutralino =
window.Neutralino && (window.NL_MODE || window.location.search.includes('mode=neutralino'));
const tracks = (window.localFilesCache = []);
let idCounter = 0;
const { readTrackMetadata } = await loadMetadataModule();
if (isNeutralino) {
async function scanNeu(dirPath) {
const entries = await window.Neutralino.filesystem.readDirectory(dirPath);
for (const entry of entries) {
if (entry.entry === '.' || entry.entry === '..') continue;
const fullPath = `${dirPath}/${entry.entry}`;
if (entry.type === 'FILE') {
const name = entry.entry.toLowerCase();
if (
name.endsWith('.flac') ||
name.endsWith('.mp3') ||
name.endsWith('.m4a') ||
name.endsWith('.wav') ||
name.endsWith('.ogg')
) {
try {
const buffer = await window.Neutralino.filesystem.readBinaryFile(fullPath);
const stats = await window.Neutralino.filesystem.getStats(fullPath);
const file = new File([buffer], entry.entry, { lastModified: stats.mtime });
const metadata = await readTrackMetadata(file);
metadata.id = `local-${idCounter++}-${entry.entry}`;
tracks.push(metadata);
UIRenderer.instance.renderLocalFiles(
document.getElementById('library-local-container')
);
} catch (e) {
console.error('Failed to read file:', fullPath, e);
}
}
} else if (entry.type === 'DIRECTORY') {
await scanNeu(fullPath);
}
}
}
await scanNeu(handle.path);
} else {
// Request read permission before iterating. When the browser has
// already granted it (e.g. within the same session or via a
// persistent grant) this succeeds without a user gesture.
if (typeof handle.requestPermission === 'function') {
const permission = await handle.requestPermission({ mode: 'read' });
if (permission !== 'granted') return;
}
async function scanBrowser(dirHandle) {
for await (const entry of dirHandle.values()) {
if (entry.kind === 'file') {
const name = entry.name.toLowerCase();
if (
name.endsWith('.flac') ||
name.endsWith('.mp3') ||
name.endsWith('.m4a') ||
name.endsWith('.wav') ||
name.endsWith('.ogg')
) {
const file = await entry.getFile();
const metadata = await readTrackMetadata(file);
metadata.id = `local-${idCounter++}-${file.name}`;
tracks.push(metadata);
UIRenderer.instance.renderLocalFiles(
document.getElementById('library-local-container')
);
}
} else if (entry.kind === 'directory') {
await scanBrowser(entry);
}
}
}
await scanBrowser(handle);
// Request read permission before iterating. When the browser has
// already granted it (e.g. within the same session or via a
// persistent grant) this succeeds without a user gesture.
if (typeof handle.requestPermission === 'function') {
const permission = await handle.requestPermission({ mode: 'read' });
if (permission !== 'granted') return;
}
async function scanBrowser(dirHandle) {
for await (const entry of dirHandle.values()) {
if (entry.kind === 'file') {
const name = entry.name.toLowerCase();
if (
name.endsWith('.flac') ||
name.endsWith('.mp3') ||
name.endsWith('.m4a') ||
name.endsWith('.wav') ||
name.endsWith('.ogg')
) {
const file = await entry.getFile();
const metadata = await readTrackMetadata(file);
metadata.id = `local-${idCounter++}-${file.name}`;
tracks.push(metadata);
UIRenderer.instance.renderLocalFiles(document.getElementById('library-local-container'));
}
} else if (entry.kind === 'directory') {
await scanBrowser(entry);
}
}
}
await scanBrowser(handle);
tracks.sort((a, b) => (a.artist.name || '').localeCompare(b.artist.name || ''));
// Update only the local-files section without navigating to the library page.
UIRenderer.instance.renderLocalFiles(document.getElementById('library-local-container'));
@ -712,17 +600,10 @@ document.addEventListener('DOMContentLoaded', async () => {
const ua = navigator.userAgent;
const isChromeOrEdge = (ua.indexOf('Chrome') > -1 || ua.indexOf('Edg') > -1) && !/Mobile|Android/.test(ua);
const hasFileSystemApi = 'showDirectoryPicker' in window;
const isNeutralino =
window.NL_MODE ||
window.location.search.includes('mode=neutralino') ||
window.location.search.includes('nl_port=');
if (!isNeutralino && (!isChromeOrEdge || !hasFileSystemApi)) {
if (!isChromeOrEdge || !hasFileSystemApi) {
selectLocalBtn.style.display = 'none';
browserWarning.style.display = 'block';
} else if (isNeutralino) {
selectLocalBtn.style.display = 'flex';
browserWarning.style.display = 'none';
}
}
@ -2563,22 +2444,10 @@ document.addEventListener('DOMContentLoaded', async () => {
if (e.target.closest('#select-local-folder-btn') || e.target.closest('#change-local-folder-btn')) {
const isChange = e.target.closest('#change-local-folder-btn') !== null;
try {
const isNeutralino =
window.Neutralino && (window.NL_MODE || window.location.search.includes('mode=neutralino'));
let handle;
let path;
if (isNeutralino) {
path = await window.Neutralino.os.showFolderDialog('Select Music Folder');
if (!path) return;
// Mock a handle object for UI compatibility
handle = { name: path.split(/[/\\]/).pop() || path, isNeutralino: true, path };
} else {
handle = await window.showDirectoryPicker({
id: 'music-folder',
mode: 'read',
});
}
const handle = await window.showDirectoryPicker({
id: 'music-folder',
mode: 'read',
});
await db.saveSetting('local_folder_handle', handle);
if (isChange) {

View file

@ -1,5 +1,4 @@
import { triggerDownload } from './download-utils';
import { readableStreamIterator } from './readableStreamIterator';
/**
* A single entry to be included in a ZIP archive or written directly to a folder.
@ -10,22 +9,6 @@ export interface WriterEntry {
input: Blob | File | string | ArrayBuffer | Uint8Array;
}
/** Minimal interface for the Neutralino bridge used by ZipNeutralinoWriter */
interface NeutralinoBridge {
os: {
showSaveDialog(
title: string,
options: { defaultPath: string; filters: Array<{ name: string; extensions: string[] }> }
): Promise<string | null>;
showFolderDialog(title: string, options?: Record<string, unknown>): Promise<string | null>;
};
filesystem: {
writeBinaryFile(path: string, buffer: ArrayBuffer): Promise<void>;
appendBinaryFile(path: string, buffer: ArrayBuffer): Promise<void>;
createDirectory(path: string): Promise<void>;
};
}
async function loadClientZip() {
try {
return await import('client-zip');
@ -115,44 +98,6 @@ export class ZipBlobWriter implements IBulkDownloadWriter {
}
}
/**
* Writes a ZIP archive to the filesystem via the Neutralino desktop bridge,
* showing a native save dialog first.
*/
export class ZipNeutralinoWriter implements IBulkDownloadWriter {
constructor(private readonly folderName: string) {}
async write(files: AsyncIterable<WriterEntry>): Promise<void> {
const bridge = (await import('./desktop/neutralino-bridge.js')) as unknown as NeutralinoBridge;
const savePath = await bridge.os.showSaveDialog(`Select save location for ${this.folderName}.zip`, {
defaultPath: `${this.folderName}.zip`,
filters: [{ name: 'ZIP Archive', extensions: ['zip'] }],
});
if (!savePath) {
throw new DOMException('User cancelled save dialog', 'AbortError');
}
const { downloadZip } = await loadClientZip();
const response = downloadZip(files);
if (!response.body) throw new Error('ZIP response body is null');
await bridge.filesystem.writeBinaryFile(savePath, new ArrayBuffer(0));
const reader = response.body.getReader();
let receivedLength = 0;
for await (const value of readableStreamIterator(response.body)) {
const chunk = value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength);
await bridge.filesystem.appendBinaryFile(savePath, chunk);
receivedLength += value.length;
}
console.log(`[ZIP] Download complete. Total size: ${receivedLength} bytes.`);
}
}
/**
* Writes files directly into a user-chosen folder using the standard browser
* File System Access API (showDirectoryPicker). Subdirectories embedded in
@ -250,54 +195,3 @@ export class FolderPickerWriter implements IBulkDownloadWriter {
}
}
}
/**
* Writes files directly into a folder on the local filesystem via the
* Neutralino desktop bridge. Subdirectories are created automatically.
*/
export class NeutralinoFolderWriter implements IBulkDownloadWriter {
constructor(private readonly basePath: string) {}
async write(files: AsyncIterable<WriterEntry>): Promise<void> {
// Import once per write() call; the module system caches the result.
const bridge = (await import('./desktop/neutralino-bridge.js')) as unknown as NeutralinoBridge;
const createdDirs = new Set<string>();
for await (const file of files) {
const parts = file.name.split('/').filter(Boolean);
if (parts.length === 0) continue;
// Ensure all parent directories exist
for (let i = 1; i < parts.length; i++) {
const dirPath = this.basePath + '/' + parts.slice(0, i).join('/');
if (!createdDirs.has(dirPath)) {
try {
await bridge.filesystem.createDirectory(dirPath);
} catch {
// Directory may already exist; ignore
}
createdDirs.add(dirPath);
}
}
const filePath = this.basePath + '/' + file.name;
let buffer: ArrayBuffer;
const { input } = file;
if (input instanceof Blob) {
buffer = await input.arrayBuffer();
} else if (typeof input === 'string') {
const encoded = new TextEncoder().encode(input);
buffer = encoded.buffer.slice(
encoded.byteOffset,
encoded.byteOffset + encoded.byteLength
) as ArrayBuffer;
} else if (input instanceof Uint8Array) {
buffer = input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength) as ArrayBuffer;
} else {
buffer = input;
}
await bridge.filesystem.writeBinaryFile(filePath, buffer);
}
}
}

View file

@ -1,22 +0,0 @@
// js/desktop/desktop.js
import Neutralino from './neutralino-bridge.js';
import { initializeDiscordRPC } from './discord-rpc.js';
export async function initDesktop(player) {
console.log('[Desktop] Initializing desktop features...');
// Assign to window for modules that use global Neutralino (like Player.js)
window.Neutralino = Neutralino;
try {
await Neutralino.init();
console.log('[Desktop] Neutralino initialized.');
if (player) {
console.log('[Desktop] Starting Discord RPC...');
initializeDiscordRPC(player);
}
} catch (error) {
console.error('[Desktop] Failed to initialize desktop environment:', error);
}
}

View file

@ -1,70 +0,0 @@
// js/desktop/discord-rpc.js
import { getTrackTitle, getTrackArtists } from '../utils.js';
export function initializeDiscordRPC(player) {
const EXTENSION_ID = 'js.neutralino.discordrpc';
function sendUpdate(track, isPaused = false) {
if (!track) return;
let coverUrl = 'monochrome';
if (track.album?.cover) {
const coverId = String(track.album.cover).replace(/-/g, '/');
coverUrl = `https://resources.tidal.com/images/${coverId}/320x320.jpg`;
}
const data = {
details: getTrackTitle(track),
state: getTrackArtists(track),
largeImageKey: coverUrl,
largeImageText: track.album?.title || 'Monochrome',
smallImageKey: isPaused ? 'pause' : 'play',
smallImageText: isPaused ? 'Paused' : 'Playing',
instance: false,
};
if (!isPaused && track.duration) {
const now = Date.now();
const elapsed = player.audio.currentTime * 1000;
const remaining = (track.duration - player.audio.currentTime) * 1000;
data.startTimestamp = Math.floor((now - elapsed) / 1000);
data.endTimestamp = Math.floor((now + remaining) / 1000);
}
Neutralino.events.broadcast('discord:update', data).catch((e) => console.error('Broadcast failed', e));
Neutralino.extensions
.dispatch(EXTENSION_ID, 'discord:update', data)
.catch((e) => console.error('Dispatch failed', e));
}
player.audio.addEventListener('play', () => {
sendUpdate(player.currentTrack);
});
player.audio.addEventListener('pause', () => {
sendUpdate(player.currentTrack, true);
});
player.audio.addEventListener('loadedmetadata', () => {
if (!player.audio.paused) {
sendUpdate(player.currentTrack);
}
});
// Send initial status
if (player.currentTrack) {
sendUpdate(player.currentTrack, player.audio.paused);
} else {
Neutralino.events
.broadcast('discord:update', {
details: 'Idling',
state: 'Monochrome',
largeImageKey: 'monochrome',
largeImageText: 'Monochrome',
smallImageKey: 'pause',
smallImageText: 'Paused',
})
.catch(() => {});
}
}

View file

@ -1,267 +0,0 @@
// js/desktop/neutralino-bridge.js
const isNeutralino =
typeof window !== 'undefined' &&
(window.NL_MODE || window.location.search.includes('mode=neutralino') || window.parent !== window);
const listeners = new Map();
// Listen for events from the Shell (Parent)
if (isNeutralino) {
window.addEventListener('message', (event) => {
if (event.data?.type === 'NL_EVENT') {
const { eventName, detail } = event.data;
if (listeners.has(eventName)) {
listeners.get(eventName).forEach((handler) => {
try {
handler(detail);
} catch (e) {
console.error('[Bridge] Error in event handler:', e);
}
});
}
}
});
}
export const init = async () => {
if (!isNeutralino) return;
// Notify Shell we are ready
window.parent.postMessage({ type: 'NL_INIT' }, '*');
};
export const events = {
on: (eventName, handler) => {
if (!isNeutralino) return;
if (!listeners.has(eventName)) {
listeners.set(eventName, []);
}
listeners.get(eventName).push(handler);
},
off: (eventName, handler) => {
if (!isNeutralino) return;
if (!listeners.has(eventName)) return;
const handlers = listeners.get(eventName);
const index = handlers.indexOf(handler);
if (index > -1) handlers.splice(index, 1);
},
broadcast: async (eventName, data) => {
if (!isNeutralino) return;
window.parent.postMessage({ type: 'NL_BROADCAST', eventName, data }, '*');
},
};
export const extensions = {
dispatch: async (extensionId, eventName, data) => {
if (!isNeutralino) return;
window.parent.postMessage({ type: 'NL_EXTENSION', extensionId, eventName, data }, '*');
},
};
export const app = {
exit: async () => {
if (!isNeutralino) return;
window.parent.postMessage({ type: 'NL_APP_EXIT' }, '*');
},
};
export const os = {
open: async (url) => {
if (!isNeutralino) return;
window.parent.postMessage({ type: 'NL_OS_OPEN', url }, '*');
},
showSaveDialog: async (title, options) => {
if (!isNeutralino) return;
return new Promise((resolve) => {
const id = Math.random().toString(36).substring(7);
const handler = (event) => {
if (event.data?.type === 'NL_RESPONSE' && event.data.id === id) {
window.removeEventListener('message', handler);
resolve(event.data.result);
}
};
window.addEventListener('message', handler);
window.parent.postMessage({ type: 'NL_OS_SHOW_SAVE_DIALOG', id, title, options }, '*');
});
},
showFolderDialog: async (title, options) => {
if (!isNeutralino) return;
return new Promise((resolve) => {
const id = Math.random().toString(36).substring(7);
const handler = (event) => {
if (event.data?.type === 'NL_RESPONSE' && event.data.id === id) {
window.removeEventListener('message', handler);
resolve(event.data.result);
}
};
window.addEventListener('message', handler);
window.parent.postMessage({ type: 'NL_OS_SHOW_FOLDER_DIALOG', id, title, options }, '*');
});
},
};
export const filesystem = {
readBinaryFile: async (path) => {
if (!isNeutralino) return;
return new Promise((resolve, reject) => {
const id = Math.random().toString(36).substring(7);
const handler = (event) => {
if (event.data?.type === 'NL_RESPONSE' && event.data.id === id) {
window.removeEventListener('message', handler);
if (event.data.error) reject(event.data.error);
else resolve(event.data.result);
}
};
window.addEventListener('message', handler);
window.parent.postMessage({ type: 'NL_FS_READ_BINARY', id, path }, '*');
});
},
readDirectory: async (path) => {
if (!isNeutralino) return;
return new Promise((resolve, reject) => {
const id = Math.random().toString(36).substring(7);
const handler = (event) => {
if (event.data?.type === 'NL_RESPONSE' && event.data.id === id) {
window.removeEventListener('message', handler);
if (event.data.error) reject(event.data.error);
else resolve(event.data.result);
}
};
window.addEventListener('message', handler);
window.parent.postMessage({ type: 'NL_FS_READ_DIR', id, path }, '*');
});
},
getStats: async (path) => {
if (!isNeutralino) return;
return new Promise((resolve, reject) => {
const id = Math.random().toString(36).substring(7);
const handler = (event) => {
if (event.data?.type === 'NL_RESPONSE' && event.data.id === id) {
window.removeEventListener('message', handler);
if (event.data.error) reject(event.data.error);
else resolve(event.data.result);
}
};
window.addEventListener('message', handler);
window.parent.postMessage({ type: 'NL_FS_STATS', id, path }, '*');
});
},
writeBinaryFile: async (path, buffer) => {
if (!isNeutralino) return;
return new Promise((resolve, reject) => {
const id = Math.random().toString(36).substring(7);
const handler = (event) => {
if (event.data?.type === 'NL_RESPONSE' && event.data.id === id) {
window.removeEventListener('message', handler);
if (event.data.error) reject(event.data.error);
else resolve(event.data.result);
}
};
window.addEventListener('message', handler);
window.parent.postMessage({ type: 'NL_FS_WRITE_BINARY', id, path, buffer }, '*', [buffer]);
});
},
appendBinaryFile: async (path, buffer) => {
if (!isNeutralino) return;
return new Promise((resolve, reject) => {
const id = Math.random().toString(36).substring(7);
const handler = (event) => {
if (event.data?.type === 'NL_RESPONSE' && event.data.id === id) {
window.removeEventListener('message', handler);
if (event.data.error) reject(event.data.error);
else resolve(event.data.result);
}
};
window.addEventListener('message', handler);
// Transfer buffer if possible to save memory
window.parent.postMessage({ type: 'NL_FS_APPEND_BINARY', id, path, buffer }, '*', [buffer]);
});
},
createDirectory: async (path) => {
if (!isNeutralino) return;
return new Promise((resolve, reject) => {
const id = Math.random().toString(36).substring(7);
const handler = (event) => {
if (event.data?.type === 'NL_RESPONSE' && event.data.id === id) {
window.removeEventListener('message', handler);
if (event.data.error) reject(event.data.error);
else resolve(event.data.result);
}
};
window.addEventListener('message', handler);
window.parent.postMessage({ type: 'NL_FS_CREATE_DIR', id, path }, '*');
});
},
};
export const updater = {
checkForUpdates: async (url) => {
if (!isNeutralino) return;
return new Promise((resolve, reject) => {
const id = Math.random().toString(36).substring(7);
const handler = (event) => {
if (event.data?.type === 'NL_RESPONSE' && event.data.id === id) {
window.removeEventListener('message', handler);
if (event.data.error) reject(event.data.error);
else resolve(event.data.result);
}
};
window.addEventListener('message', handler);
window.parent.postMessage({ type: 'NL_UPDATER_CHECK', id, url }, '*');
});
},
install: async () => {
if (!isNeutralino) return;
return new Promise((resolve, reject) => {
const id = Math.random().toString(36).substring(7);
const handler = (event) => {
if (event.data?.type === 'NL_RESPONSE' && event.data.id === id) {
window.removeEventListener('message', handler);
if (event.data.error) reject(event.data.error);
else resolve(event.data.result);
}
};
window.addEventListener('message', handler);
window.parent.postMessage({ type: 'NL_UPDATER_INSTALL', id }, '*');
});
},
};
export const _window = {
minimize: async () => {
if (!isNeutralino) return;
window.parent.postMessage({ type: 'NL_WINDOW_MIN' }, '*');
},
maximize: async () => {
if (!isNeutralino) return;
window.parent.postMessage({ type: 'NL_WINDOW_MAX' }, '*');
},
show: async () => {
if (!isNeutralino) return;
window.parent.postMessage({ type: 'NL_WINDOW_SHOW' }, '*');
},
hide: async () => {
if (!isNeutralino) return;
window.parent.postMessage({ type: 'NL_WINDOW_HIDE' }, '*');
},
isVisible: async () => {
return true; // Mock response
},
setTitle: async (title) => {
if (!isNeutralino) return;
window.parent.postMessage({ type: 'NL_WINDOW_SET_TITLE', title }, '*');
},
};
// Expose generically for other modules
export { _window as window };
export default {
init,
events,
extensions,
app,
os,
filesystem,
updater,
window: _window,
};

View file

@ -1,5 +1,4 @@
//js/downloads.js
//@ts-check
import {
buildTrackFilename,
sanitizeForFilename,
@ -9,19 +8,15 @@ import {
formatPathTemplate,
getCoverBlob,
getExtensionFromBlob,
formatTemplate,
escapeHtml,
getTrackDiscNumber,
} from './utils.js';
import { AbortError } from './errorTypes.ts';
import { lyricsSettings, playlistSettings } from './storage.js';
import { generateM3U, generateM3U8, generateCUE, generateNFO, generateJSON } from './playlist-generator.js';
import {
ZipStreamWriter,
ZipBlobWriter,
ZipNeutralinoWriter,
FolderPickerWriter,
NeutralinoFolderWriter,
SequentialFileWriter,
} from './bulk-download-writer.ts';
import { FfmpegProgress } from './ffmpeg.types.js';
@ -29,7 +24,6 @@ import { DownloadProgress, ProgressMessage, SegmentedDownloadProgress } from './
import { db } from './db.js';
import { modernSettings } from './ModernSettings.js';
import { SVG_CLOSE } from './icons.ts';
import { MusicAPI } from './music-api.js';
import { LyricsManager } from './lyrics.js';
const downloadTasks = new Map();
@ -438,13 +432,13 @@ async function bulkDownload({
// For albums, generate CUE file (one per disc if multi-disc)
if (type === 'album' && playlistSettings.shouldGenerateCUE()) {
const tracksByVolume = Object.groupBy(
tracks.map((track, index) => ({
...track,
trackPath: trackPaths[index],
})),
(track) => String(getTrackDiscNumber(track) || 1)
);
const tracksByVolume = tracks.reduce((acc, track, index) => {
const discNumber = String(getTrackDiscNumber(track) || 1);
if (!acc[discNumber]) acc[discNumber] = [];
acc[discNumber].push({ ...track, trackPath: trackPaths[index] });
return acc;
}, {});
const multiDisc = Object.keys(tracksByVolume).length > 1;
for (const [volumeNumber, volumeTracks] of Object.entries(tracksByVolume)) {
@ -511,17 +505,12 @@ async function bulkDownload({
async function createSingleTrackFolderWriter() {
if (!modernSettings.downloadSinglesToFolder) return null;
const isNeutralino =
typeof window !== 'undefined' &&
(window.NL_MODE || window.location.search.includes('mode=neutralino') || window.parent !== window);
const method = modernSettings.bulkDownloadMethod;
const hasFolderPicker = 'showDirectoryPicker' in window;
if (method === 'local') {
const localHandle = await db.getSetting('local_folder_handle');
if (isNeutralino) {
if (localHandle?.path) return new NeutralinoFolderWriter(localHandle.path);
} else if (hasFolderPicker && localHandle && typeof localHandle.requestPermission === 'function') {
if (hasFolderPicker && localHandle && typeof localHandle.requestPermission === 'function') {
try {
const permission = await localHandle.requestPermission({ mode: 'readwrite' });
if (permission === 'granted') return FolderPickerWriter.fromHandle(localHandle);
@ -534,7 +523,7 @@ async function createSingleTrackFolderWriter() {
if (method === 'folder' && hasFolderPicker) {
const rememberFolder = modernSettings.rememberBulkDownloadFolder;
const savedHandle = rememberFolder ? modernSettings.bulkDownloadFolder : null;
const savedHandle = rememberFolder ? await db.getSetting('bulk_download_folder_handle') : null;
// Try to reuse the saved handle silently first.
if (savedHandle && typeof savedHandle.requestPermission === 'function') {
try {
@ -548,8 +537,7 @@ async function createSingleTrackFolderWriter() {
try {
const writer = await FolderPickerWriter.create();
if (rememberFolder) {
modernSettings.bulkDownloadFolder = writer.getDirHandle();
await modernSettings.waitPending();
await db.saveSetting('bulk_download_folder_handle', writer.getDirHandle());
}
return writer;
} catch (error) {
@ -570,9 +558,6 @@ async function createSingleTrackFolderWriter() {
* or null when individual sequential downloads should be used.
*/
async function createBulkWriter(folderName) {
const isNeutralino =
typeof window !== 'undefined' &&
(window.NL_MODE || window.location.search.includes('mode=neutralino') || window.parent !== window);
const method = modernSettings.bulkDownloadMethod;
const forceZipBlob = modernSettings.forceZipBlob;
const hasFileSystemAccess = 'showSaveFilePicker' in window && 'createWritable' in FileSystemFileHandle.prototype;
@ -581,23 +566,7 @@ async function createBulkWriter(folderName) {
// ── Local Media Folder method ────────────────────────────────────────────
if (method === 'local') {
const localHandle = await db.getSetting('local_folder_handle');
if (isNeutralino) {
if (localHandle?.path) {
return new NeutralinoFolderWriter(localHandle.path);
}
// No folder configured prompt now
const bridge = await import('./desktop/neutralino-bridge.js');
const pickedPath = await bridge.os.showFolderDialog('Select Download Folder');
if (!pickedPath) return null; // user cancelled fall back to default
// Persist as the local media folder so future downloads reuse it
const handle = {
name: pickedPath.split(/[/\\]/).pop() || pickedPath,
isNeutralino: true,
path: pickedPath,
};
await db.saveSetting('local_folder_handle', handle);
return new NeutralinoFolderWriter(pickedPath);
} else if (hasFolderPicker) {
if (hasFolderPicker) {
// Browser mode: try to reuse the stored handle with write permission
if (localHandle && typeof localHandle.requestPermission === 'function') {
try {
@ -624,11 +593,6 @@ async function createBulkWriter(folderName) {
// Browser without File System Access API fall through to ZIP
}
// ── Neutralino default (ZIP) ─────────────────────────────────────────────
if (isNeutralino) {
return new ZipNeutralinoWriter(folderName);
}
// ── Folder Picker method ─────────────────────────────────────────────────
if (method === 'folder' && hasFolderPicker) {
const rememberFolder = modernSettings.rememberBulkDownloadFolder;

View file

@ -801,7 +801,6 @@ export class Player {
this.updatePlayingTrackIndicator();
this.updateMediaSession(track);
this.updateMediaSessionPlaybackState();
this.updateNativeWindow(track);
try {
let streamUrl;
@ -2070,16 +2069,4 @@ export class Player {
updateBtn(timerBtn);
updateBtn(timerBtnDesktop);
}
async updateNativeWindow(track) {
if (!window.Neutralino) return;
const trackTitle = getTrackTitle(track);
const artist = getTrackArtists(track);
try {
await Neutralino.window.setTitle(`${trackTitle}${artist}`);
} catch (e) {
console.error('Failed to set window title:', e);
}
}
}

View file

@ -220,10 +220,7 @@ export async function initializeSettings(scrobbler, player, api, ui) {
return;
}
let authWindow = null;
if (!window.Neutralino) {
authWindow = window.open('', '_blank');
}
let authWindow = window.open('', '_blank');
lastfmConnectBtn.disabled = true;
lastfmConnectBtn.textContent = 'Opening Last.fm...';
@ -231,16 +228,7 @@ export async function initializeSettings(scrobbler, player, api, ui) {
try {
const { token, url } = await scrobbler.lastfm.getAuthUrl();
if (window.Neutralino) {
try {
await Neutralino.os.open(url);
} catch (e) {
// Fallback if os.open fails
console.error('Neutralino open failed, falling back to window.open', e);
if (!authWindow) authWindow = window.open(url, '_blank');
else authWindow.location.href = url;
}
} else if (authWindow) {
if (authWindow) {
authWindow.location.href = url;
} else {
alert('Popup blocked! Please allow popups.');
@ -587,10 +575,7 @@ export async function initializeSettings(scrobbler, player, api, ui) {
return;
}
let authWindow = null;
if (!window.Neutralino) {
authWindow = window.open('', '_blank');
}
let authWindow = window.open('', '_blank');
librefmConnectBtn.disabled = true;
librefmConnectBtn.textContent = 'Opening Libre.fm...';
@ -598,9 +583,7 @@ export async function initializeSettings(scrobbler, player, api, ui) {
try {
const { token, url } = await scrobbler.librefm.getAuthUrl();
if (window.Neutralino) {
await Neutralino.os.open(url);
} else if (authWindow) {
if (authWindow) {
authWindow.location.href = url;
} else {
alert('Popup blocked! Please allow popups.');
@ -1037,20 +1020,7 @@ export async function initializeSettings(scrobbler, player, api, ui) {
if (!existingHandle) {
let picked = false;
try {
const isNeutralino =
window.Neutralino && (window.NL_MODE || window.location.search.includes('mode=neutralino'));
if (isNeutralino) {
const path = await window.Neutralino.os.showFolderDialog('Select Local Media Folder');
if (path) {
picked = true;
const handle = {
name: path.split(/[/\\]/).pop() || path,
isNeutralino: true,
path,
};
await db.saveSetting('local_folder_handle', handle);
}
} else if (hasFolderPicker) {
if (hasFolderPicker) {
const handle = await window.showDirectoryPicker({ mode: 'readwrite' });
if (handle) {
picked = true;

View file

@ -1,60 +0,0 @@
{
"applicationId": "com.monochrome.app",
"applicationName": "Monochrome",
"applicationIcon": "public/assets/appicon.png",
"author": "Monochrome",
"description": "Monochrome - Lossless music streaming",
"version": "1.0.0",
"defaultMode": "window",
"documentRoot": "/",
"url": "/public/neutralino_loader.html",
"enableServer": true,
"enableNativeAPI": true,
"enableExtensions": true,
"tokenSecurity": "none",
"modes": {
"window": {
"title": "Monochrome (DEV)",
"icon": "public/assets/appicon.png",
"width": 1280,
"height": 800,
"minWidth": 800,
"minHeight": 600,
"center": true,
"resizable": true,
"hidden": false,
"borderless": false,
"enableInspector": true,
"openInspectorOnStartup": true,
"exitProcessOnClose": true
}
},
"port": 5050,
"cli": {
"binaryName": "Monochrome",
"resourcesPath": "/",
"binaryVersion": "6.5.0",
"clientVersion": "6.5.0"
},
"extensions": [
{
"id": "js.neutralino.discordrpc",
"commandLinux": "python3 \"${NL_PATH}/extensions/js.neutralino.discordrpc/bridge.py\"",
"commandMac": "python3 \"${NL_PATH}/extensions/js.neutralino.discordrpc/bridge.py\"",
"commandWindows": "powershell.exe -ExecutionPolicy Bypass -File \"${NL_PATH}/extensions/js.neutralino.discordrpc/bridge.ps1\""
}
],
"nativeAllowList": [
"app.*",
"window.*",
"extensions.*",
"events.*",
"os.*",
"filesystem.*",
"debug.*",
"storage.*",
"computer.*",
"clipboard.*",
"updater.*"
]
}

View file

@ -1,60 +0,0 @@
{
"applicationId": "com.monochrome.app",
"applicationName": "Monochrome",
"applicationIcon": "public/assets/appicon.png",
"author": "Monochrome",
"description": "Monochrome - Lossless music streaming",
"version": "1.0.0",
"defaultMode": "window",
"documentRoot": "dist/",
"url": "/neutralino_loader.html",
"enableServer": true,
"enableNativeAPI": true,
"enableExtensions": true,
"tokenSecurity": "none",
"modes": {
"window": {
"title": "Monochrome",
"icon": "public/assets/appicon.png",
"width": 1280,
"height": 800,
"minWidth": 800,
"minHeight": 600,
"center": true,
"resizable": true,
"hidden": false,
"borderless": false,
"enableInspector": true,
"openInspectorOnStartup": false,
"exitProcessOnClose": true
}
},
"port": 5050,
"cli": {
"binaryName": "Monochrome",
"resourcesPath": "dist/",
"binaryVersion": "6.5.0",
"clientVersion": "6.5.0"
},
"extensions": [
{
"id": "js.neutralino.discordrpc",
"commandLinux": "python3 \"${NL_PATH}/extensions/js.neutralino.discordrpc/bridge.py\"",
"commandMac": "python3 \"${NL_PATH}/extensions/js.neutralino.discordrpc/bridge.py\"",
"commandWindows": "powershell.exe -ExecutionPolicy Bypass -File \"${NL_PATH}/extensions/js.neutralino.discordrpc/bridge.ps1\""
}
],
"nativeAllowList": [
"app.*",
"window.*",
"extensions.*",
"events.*",
"os.*",
"filesystem.*",
"debug.*",
"storage.*",
"computer.*",
"clipboard.*",
"updater.*"
]
}

4129
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -5,9 +5,7 @@
"description": "[<img src=\"https://github.com/monochrome-music/monochrome/blob/main/assets/512.png?raw=true\" alt=\"Monochrome Logo\">](https://monochrome.tf)",
"scripts": {
"dev": "vite",
"dev:desktop": "start bun run dev & node scripts/dev-runner.js",
"build": "vite build",
"build:desktop": "bun x neu build",
"postbuild": "node -e \"const fs = require('fs'); const path = require('path'); const src = 'extensions'; const dest = path.join('dist', 'Monochrome', 'extensions'); if (fs.existsSync(src)) { fs.mkdirSync(dest, { recursive: true }); fs.cpSync(src, dest, { recursive: true }); console.log('Extensions manually copied to ' + dest); }\"",
"preview": "vite preview",
"lint:js": "eslint .",
@ -30,7 +28,6 @@
"devDependencies": {
"@capacitor/assets": "^3.0.5",
"@capacitor/cli": "^8.2.0",
"@neutralinojs/neu": "^11.7.0",
"@types/node": "^25.3.5",
"eslint": "^9.39.3",
"eslint-config-prettier": "^10.1.8",
@ -44,7 +41,6 @@
"stylelint-config-standard-scss": "^16.0.0",
"typescript": "^5.9.3",
"vite": "^7.3.1",
"vite-plugin-neutralino": "^1.0.3",
"vite-plugin-pwa": "^1.2.0"
},
"overrides": {
@ -63,7 +59,6 @@
"@ffmpeg/ffmpeg": "^0.12.15",
"@ffmpeg/util": "^0.12.2",
"@kawarp/core": "^1.1.1",
"@neutralinojs/lib": "^6.5.0",
"@svta/common-media-library": "^0.18.1",
"@uimaxbai/am-lyrics": "^1.1.4",
"appwrite": "^23.0.0",

View file

@ -1,815 +0,0 @@
var Neutralino = (function (e) {
'use strict';
function t(e, t) {
return (window.addEventListener(e, t), Promise.resolve({ success: !0, message: 'Event listener added' }));
}
function n(e, t) {
const n = new CustomEvent(e, { detail: t });
return (window.dispatchEvent(n), Promise.resolve({ success: !0, message: 'Message dispatched' }));
}
function r(e) {
const t = window.atob(e),
n = t.length,
r = new Uint8Array(n);
for (let e = 0; e < n; e++) r[e] = t.charCodeAt(e);
return r.buffer;
}
function o(e) {
let t = new Uint8Array(e),
n = '';
for (let e of t) n += String.fromCharCode(e);
return window.btoa(n);
}
function i(e) {
const t = [];
for (const n of e) {
const e = Array.isArray(n) ? n : [n];
for (const n of e) {
const e = n instanceof HTMLElement ? n : document.getElementById(n);
e && t.push(e);
}
}
return t;
}
let a;
const s = {},
c = [],
u = {};
function d() {
window.NL_TOKEN && sessionStorage.setItem('NL_TOKEN', window.NL_TOKEN);
const e = g().split('.')[1],
o = window.NL_GINJECTED || window.NL_CINJECTED ? '127.0.0.1' : window.location.hostname;
((a = new WebSocket(`ws://${o}:${window.NL_PORT}?connectToken=${e}`)),
(function () {
if (
(t('ready', async () => {
if ((await f(c), !window.NL_EXTENABLED)) return;
let e = await l('extensions.getStats');
for (let t of e.connected) n('extensionReady', t);
}),
t('extClientConnect', (e) => {
n('extensionReady', e.detail);
}),
!window.NL_EXTENABLED)
)
return;
t('extensionReady', async (e) => {
e.detail in u && (await f(u[e.detail]), delete u[e.detail]);
});
})(),
a.addEventListener('message', (e) => {
const t = JSON.parse(e.data);
t.id && t.id in s
? (t.data?.error
? (s[t.id].reject(t.data.error),
'NE_RT_INVTOKN' == t.data.error.code &&
(a.close(),
(document.body.innerText = ''),
document.write(
'<code>NE_RT_INVTOKN</code>: Neutralinojs application cannot execute native methods since <code>NL_TOKEN</code> is invalid.'
)))
: t.data?.success &&
s[t.id].resolve(t.data.hasOwnProperty('returnValue') ? t.data.returnValue : t.data),
delete s[t.id])
: t.event &&
('openedFile' == t.event && 'dataBinary' == t?.data?.action && (t.data.data = r(t.data.data)),
n(t.event, t.data));
}),
a.addEventListener('open', async (e) => {
n('ready');
}),
a.addEventListener('close', async (e) => {
n('serverOffline', {
code: 'NE_CL_NSEROFF',
message: 'Neutralino server is offline. Try restarting the application',
});
}),
a.addEventListener('error', async (e) => {
((document.body.innerText = ''),
document.write(
'<code>NE_CL_IVCTOKN</code>: Neutralinojs application cannot connect with the framework core using <code>NL_TOKEN</code>.'
));
}));
}
function l(e, t) {
return new Promise((n, r) => {
if (a?.readyState != WebSocket.OPEN)
return ((o = { method: e, data: t, resolve: n, reject: r }), void c.push(o));
var o;
const i = '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, (e) =>
(e ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (e / 4)))).toString(16)
),
u = g();
((s[i] = { resolve: n, reject: r }), a.send(JSON.stringify({ id: i, method: e, data: t, accessToken: u })));
});
}
async function f(e) {
for (; e.length > 0; ) {
const t = e.shift();
try {
const e = await l(t.method, t.data);
t.resolve(e);
} catch (e) {
t.reject(e);
}
}
}
function g() {
return window.NL_TOKEN || sessionStorage.getItem('NL_TOKEN') || '';
}
function w(e, t) {
return l('filesystem.writeBinaryFile', { path: e, data: o(t) });
}
var m = {
__proto__: null,
appendBinaryFile: function (e, t) {
return l('filesystem.appendBinaryFile', { path: e, data: o(t) });
},
appendFile: function (e, t) {
return l('filesystem.appendFile', { path: e, data: t });
},
copy: function (e, t, n) {
return l('filesystem.copy', { source: e, destination: t, ...n });
},
createDirectory: function (e) {
return l('filesystem.createDirectory', { path: e });
},
createWatcher: function (e) {
return l('filesystem.createWatcher', { path: e });
},
getAbsolutePath: function (e) {
return l('filesystem.getAbsolutePath', { path: e });
},
getJoinedPath: function (...e) {
return l('filesystem.getJoinedPath', { paths: e });
},
getNormalizedPath: function (e) {
return l('filesystem.getNormalizedPath', { path: e });
},
getOpenedFileInfo: function (e) {
return l('filesystem.getOpenedFileInfo', { id: e });
},
getPathParts: function (e) {
return l('filesystem.getPathParts', { path: e });
},
getPermissions: function (e) {
return l('filesystem.getPermissions', { path: e });
},
getRelativePath: function (e, t) {
return l('filesystem.getRelativePath', { path: e, base: t });
},
getStats: function (e) {
return l('filesystem.getStats', { path: e });
},
getUnnormalizedPath: function (e) {
return l('filesystem.getUnnormalizedPath', { path: e });
},
getWatchers: function () {
return l('filesystem.getWatchers');
},
move: function (e, t) {
return l('filesystem.move', { source: e, destination: t });
},
openFile: function (e) {
return l('filesystem.openFile', { path: e });
},
readBinaryFile: function (e, t) {
return new Promise((n, o) => {
l('filesystem.readBinaryFile', { path: e, ...t })
.then((e) => {
n(r(e));
})
.catch((e) => {
o(e);
});
});
},
readDirectory: function (e, t) {
return l('filesystem.readDirectory', { path: e, ...t });
},
readFile: function (e, t) {
return l('filesystem.readFile', { path: e, ...t });
},
remove: function (e) {
return l('filesystem.remove', { path: e });
},
removeWatcher: function (e) {
return l('filesystem.removeWatcher', { id: e });
},
setPermissions: function (e, t, n) {
return l('filesystem.setPermissions', { path: e, ...t, mode: n });
},
updateOpenedFile: function (e, t, n) {
return l('filesystem.updateOpenedFile', { id: e, event: t, data: n });
},
writeBinaryFile: w,
writeFile: function (e, t) {
return l('filesystem.writeFile', { path: e, data: t });
},
};
function p(e, t) {
return l('os.execCommand', { command: e, ...t });
}
var h = {
__proto__: null,
execCommand: p,
getEnv: function (e) {
return l('os.getEnv', { key: e });
},
getEnvs: function () {
return l('os.getEnvs');
},
getPath: function (e) {
return l('os.getPath', { name: e });
},
getSpawnedProcesses: function () {
return l('os.getSpawnedProcesses');
},
open: function (e) {
return l('os.open', { url: e });
},
setTray: function (e) {
return l('os.setTray', e);
},
showFolderDialog: function (e, t) {
return l('os.showFolderDialog', { title: e, ...t });
},
showMessageBox: function (e, t, n, r) {
return l('os.showMessageBox', { title: e, content: t, choice: n, icon: r });
},
showNotification: function (e, t, n) {
return l('os.showNotification', { title: e, content: t, icon: n });
},
showOpenDialog: function (e, t) {
return l('os.showOpenDialog', { title: e, ...t });
},
showSaveDialog: function (e, t) {
return l('os.showSaveDialog', { title: e, ...t });
},
spawnProcess: function (e, t) {
return l('os.spawnProcess', { command: e, ...t });
},
updateSpawnedProcess: function (e, t, n) {
return l('os.updateSpawnedProcess', { id: e, event: t, data: n });
},
};
var y = {
__proto__: null,
getArch: function () {
return l('computer.getArch');
},
getCPUInfo: function () {
return l('computer.getCPUInfo');
},
getDisplays: function () {
return l('computer.getDisplays');
},
getKernelInfo: function () {
return l('computer.getKernelInfo');
},
getMemoryInfo: function () {
return l('computer.getMemoryInfo');
},
getMousePosition: function () {
return l('computer.getMousePosition');
},
getOSInfo: function () {
return l('computer.getOSInfo');
},
};
var _ = {
__proto__: null,
clear: function () {
return l('storage.clear');
},
getData: function (e) {
return l('storage.getData', { key: e });
},
getKeys: function () {
return l('storage.getKeys');
},
removeData: function (e) {
return l('storage.removeData', { key: e });
},
setData: function (e, t) {
return l('storage.setData', { key: e, data: t });
},
};
function v(e, t) {
return l('debug.log', { message: e, type: t });
}
var N = { __proto__: null, log: v };
function E(e) {
return l('app.exit', { code: e });
}
var P = {
__proto__: null,
broadcast: function (e, t) {
return l('app.broadcast', { event: e, data: t });
},
exit: E,
getConfig: function () {
return l('app.getConfig');
},
killProcess: function () {
return l('app.killProcess');
},
readProcessInput: function (e) {
return l('app.readProcessInput', { readAll: e });
},
restartProcess: function (e) {
return new Promise(async (t) => {
let n = window.NL_ARGS.reduce((e, t) => (t.includes(' ') && (t = `"${t}"`), (e += ' ' + t)), '');
(e?.args && (n += ' ' + e.args), await p(n, { background: !0 }), E(), t());
});
},
writeProcessError: function (e) {
return l('app.writeProcessError', { data: e });
},
writeProcessOutput: function (e) {
return l('app.writeProcessOutput', { data: e });
},
};
const b = new Set(),
D = new Map(),
T = new Map();
function O(e = 0, t = 0) {
return l('window.beginDrag', { screenX: e, screenY: t });
}
function S() {
return l('window.getSize');
}
var L = {
__proto__: null,
beginDrag: O,
center: function () {
return l('window.center');
},
create: function (e, t) {
return new Promise((n, r) => {
function o(e) {
return ('string' != typeof e || ((e = e.trim()).includes(' ') && (e = `"${e}"`)), e);
}
t = { ...t, useSavedState: !1 };
let i = window.NL_ARGS.reduce(
(e, t, n) => (
(t.includes('--path=') ||
t.includes('--debug-mode') ||
t.includes('--load-dir-res') ||
0 == n) &&
(e += ' ' + o(t)),
e
),
''
);
i += ' --url=' + o(e);
for (let e in t) {
if ('processArgs' == e) continue;
i += ` --window${'-' + e.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}=${o(t[e])}`;
}
(t && t.processArgs && (i += ' ' + t.processArgs),
p(i, { background: !0 })
.then((e) => {
n(e);
})
.catch((e) => {
r(e);
}));
});
},
exitFullScreen: function () {
return l('window.exitFullScreen');
},
focus: function () {
return l('window.focus');
},
getPosition: function () {
return l('window.getPosition');
},
getSize: S,
getTitle: function () {
return l('window.getTitle');
},
hide: function () {
return l('window.hide');
},
isFullScreen: function () {
return l('window.isFullScreen');
},
isMaximized: function () {
return l('window.isMaximized');
},
isMinimized: function () {
return l('window.isMinimized');
},
isVisible: function () {
return l('window.isVisible');
},
maximize: function () {
return l('window.maximize');
},
minimize: function () {
return l('window.minimize');
},
move: function (e, t) {
return l('window.move', { x: e, y: t });
},
print: function () {
return l('window.print');
},
setAlwaysOnTop: function (e) {
return l('window.setAlwaysOnTop', { onTop: e });
},
setBorderless: function (e) {
return l('window.setBorderless', { borderless: e });
},
setDraggableRegion: function (e, t) {
return new Promise((n, r) => {
const o = e instanceof HTMLElement ? e : document.getElementById(e);
if (!o) return r({ code: 'NE_WD_DOMNOTF', message: 'Unable to find DOM element' });
if (b.has(o))
return r({
code: 'NE_WD_ALRDREL',
message: 'This DOM element is already an active draggable region',
});
if (t?.exclude?.length) {
const e = new Set();
for (const n of t.exclude) {
const t = n instanceof HTMLElement ? n : document.getElementById(n);
t && e.add(t);
}
e.size && D.set(o, e);
}
const a =
((s = o),
async function (e) {
if (0 !== e.button) return;
const t = D.get(s);
if (t) for (const n of t) if (n.contains(e.target)) return;
(await O(e.screenX, e.screenY), e.preventDefault());
});
var s;
(o.addEventListener('pointerdown', a), b.add(o), T.set(o, a));
n({
success: !0,
message: 'Draggable region was activated',
exclusions: {
add(...e) {
if (!b.has(o))
throw {
code: 'NE_WD_NOTDRRE',
message:
'DOM element is no longer an active draggable region. You likely called unsetDraggableRegion on this element too early!',
};
let t = D.get(o);
t || ((t = new Set()), D.set(o, t));
const n = i(e);
for (const e of n) t.add(e);
},
remove(...e) {
if (!b.has(o))
throw {
code: 'NE_WD_NOTDRRE',
message:
'DOM element is no longer an active draggable region. You likely called unsetDraggableRegion on this element too early!',
};
const t = D.get(o);
if (!t) return;
const n = i(e);
for (const e of n) t.delete(e);
},
removeAll() {
if (!b.has(o))
throw {
code: 'NE_WD_NOTDRRE',
message:
'DOM element is no longer an active draggable region. You likely called unsetDraggableRegion on this element too early!',
};
D.delete(o);
},
},
});
});
},
setFullScreen: function () {
return l('window.setFullScreen');
},
setIcon: function (e) {
return l('window.setIcon', { icon: e });
},
setMainMenu: function (e) {
return l('window.setMainMenu', e);
},
setSize: function (e) {
return new Promise(async (t, n) => {
let r = await S();
l('window.setSize', (e = { ...r, ...e }))
.then((e) => {
t(e);
})
.catch((e) => {
n(e);
});
});
},
setTitle: function (e) {
return l('window.setTitle', { title: e });
},
show: function () {
return l('window.show');
},
snapshot: function (e) {
return l('window.snapshot', { path: e });
},
unmaximize: function () {
return l('window.unmaximize');
},
unminimize: function () {
return l('window.unminimize');
},
unsetDraggableRegion: function (e) {
return new Promise((t, n) => {
const r = e instanceof HTMLElement ? e : document.getElementById(e);
if (!r) return n({ code: 'NE_WD_DOMNOTF', message: 'Unable to find DOM element' });
if (!b.has(r))
return n({ code: 'NE_WD_NOTDRRE', message: 'DOM element is not an active draggable region' });
const o = T.get(r);
(o && (r.removeEventListener('pointerdown', o), T.delete(r)),
b.delete(r),
D.delete(r),
t({ success: !0, message: 'Draggable region was deactivated' }));
});
},
};
var M = {
__proto__: null,
broadcast: function (e, t) {
return l('events.broadcast', { event: e, data: t });
},
dispatch: n,
off: function (e, t) {
return (
window.removeEventListener(e, t),
Promise.resolve({ success: !0, message: 'Event listener removed' })
);
},
on: t,
};
function x() {
return l('extensions.getStats');
}
var F = {
__proto__: null,
broadcast: function (e, t) {
return l('extensions.broadcast', { event: e, data: t });
},
dispatch: function (e, t, n) {
return new Promise(async (r, o) => {
const i = await x();
if (i.loaded.includes(e))
if (i.connected.includes(e))
try {
r(await l('extensions.dispatch', { extensionId: e, event: t, data: n }));
} catch (e) {
o(e);
}
else
!(function (e, t) {
e in u ? u[e].push(t) : (u[e] = [t]);
})(e, {
method: 'extensions.dispatch',
data: { extensionId: e, event: t, data: n },
resolve: r,
reject: o,
});
else o({ code: 'NE_EX_EXTNOTL', message: `${e} is not loaded` });
});
},
getStats: x,
};
let R = null;
var A = {
__proto__: null,
checkForUpdates: function (e) {
return new Promise(async (t, n) => {
if (!e) return n({ code: 'NE_RT_NATRTER', message: 'Missing require parameter: url' });
try {
const r = await fetch(e);
((R = JSON.parse(await r.text())),
!(function (e) {
return !!(
e.applicationId &&
e.applicationId == window.NL_APPID &&
e.version &&
e.resourcesURL
);
})(R)
? n({
code: 'NE_UP_CUPDMER',
message: 'Invalid update manifest or mismatching applicationId',
})
: t(R));
} catch (e) {
n({ code: 'NE_UP_CUPDERR', message: 'Unable to fetch update manifest' });
}
});
},
install: function () {
return new Promise(async (e, t) => {
if (!R)
return t({
code: 'NE_UP_UPDNOUF',
message:
'No update manifest loaded. Make sure that updater.checkForUpdates() is called before install().',
});
try {
const t = await fetch(R.resourcesURL),
n = await t.arrayBuffer();
(await w(window.NL_PATH + '/resources.neu', n),
e({ success: !0, message: 'Update installed. Restart the process to see updates' }));
} catch (e) {
t({ code: 'NE_UP_UPDINER', message: 'Update installation error' });
}
});
},
};
var I = {
__proto__: null,
clear: function () {
return l('clipboard.clear');
},
getFormat: function () {
return l('clipboard.getFormat');
},
readHTML: function () {
return l('clipboard.readHTML');
},
readImage: function (e = '') {
return new Promise((t, n) => {
l('clipboard.readImage')
.then((n) => {
if (n) {
const r = window.atob(n.data);
let o,
i,
a,
s = 32 == n.bpp ? 4 : 3;
switch (e.toLowerCase()) {
case 'rgb':
((o = n.width * n.height * 3), (i = [0, 1, 2]));
break;
case 'rgba':
((o = n.width * n.height * 4), (i = [0, 1, 2, 3]));
break;
case 'argb':
((o = n.width * n.height * 4), (i = [3, 0, 1, 2]));
break;
case 'bgra':
((o = n.width * n.height * 4), (i = [2, 1, 0, 3]));
break;
default:
((o = r.length), (a = new Uint8Array(o)));
for (let e = 0; e < o; e++) a[e] = r.charCodeAt(e);
return ((n.data = a), void t(n));
}
a = new Uint8Array(o);
let c,
u,
d,
l,
f,
g = 255 == new Uint8Array(new Uint32Array([255]).buffer)[0],
w = [],
m = 0;
for (let e = 0; e < r.length; e += s)
((c = r.charCodeAt(e)),
(u = r.charCodeAt(e + 1)),
(d = r.charCodeAt(e + 2)),
(l = 4 == s ? r.charCodeAt(e + 3) : 255),
(f = g
? ((l << 24) | (d << 16) | (u << 8) | c) >>> 0
: ((c << 24) | (u << 16) | (d << 8) | l) >>> 0),
(w = [
(f >> n.redShift) & 255,
(f >> n.greenShift) & 255,
(f >> n.blueShift) & 255,
(f >> n.alphaShift) & 255,
]),
i.forEach((e, t) => {
a[t + m] = w[e];
}),
(m += i.length));
n.data = a;
}
t(n);
})
.catch((e) => {
n(e);
});
});
},
readText: function () {
return l('clipboard.readText');
},
writeHTML: function (e) {
return l('clipboard.writeHTML', { data: e });
},
writeImage: function (e) {
const t = { ...e };
return (e?.data && (t.data = o(e.data)), l('clipboard.writeImage', t));
},
writeText: function (e) {
return l('clipboard.writeText', { data: e });
},
};
var C = {
__proto__: null,
extractDirectory: function (e, t) {
return l('resources.extractDirectory', { path: e, destination: t });
},
extractFile: function (e, t) {
return l('resources.extractFile', { path: e, destination: t });
},
getFiles: function () {
return l('resources.getFiles');
},
getStats: function (e) {
return l('resources.getStats', { path: e });
},
readBinaryFile: function (e) {
return new Promise((t, n) => {
l('resources.readBinaryFile', { path: e })
.then((e) => {
t(r(e));
})
.catch((e) => {
n(e);
});
});
},
readFile: function (e) {
return l('resources.readFile', { path: e });
},
};
var U = {
__proto__: null,
getMounts: function () {
return l('server.getMounts');
},
mount: function (e, t) {
return l('server.mount', { path: e, target: t });
},
unmount: function (e) {
return l('server.unmount', { path: e });
},
};
var k = {
__proto__: null,
getMethods: function () {
return l('custom.getMethods');
},
};
let z = !1;
return (
(e.app = P),
(e.clipboard = I),
(e.computer = y),
(e.custom = k),
(e.debug = N),
(e.events = M),
(e.extensions = F),
(e.filesystem = m),
(e.init = function (e = {}) {
if (((e = { exportCustomMethods: !0, ...e }), !z)) {
if (
(d(),
window.NL_ARGS.find((e) => '--neu-dev-auto-reload' == e) &&
t('neuDev_reloadApp', async () => {
(await v('Reloading the application...'), location.reload());
}),
e.exportCustomMethods && window.NL_CMETHODS && window.NL_CMETHODS.length > 0)
)
for (const e of window.NL_CMETHODS)
Neutralino.custom[e] = (...t) => {
let n = {};
for (const [e, r] of t.entries())
n =
'object' != typeof r || Array.isArray(r) || null == r
? { ...n, ['arg' + e]: r }
: { ...n, ...r };
return l('custom.' + e, n);
};
((window.NL_CVERSION = '6.5.0'),
(window.NL_CCOMMIT = '425c526c318342e0e5d0f17caceef2a53049eda4'),
(z = !0));
}
}),
(e.os = h),
(e.resources = C),
(e.server = U),
(e.storage = _),
(e.updater = A),
(e.window = L),
e
);
})({});

View file

@ -1,374 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Monochrome Shell</title>
<style>
body,
html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: #000;
/* Seamless blend */
}
iframe {
width: 100%;
height: 100%;
border: none;
display: block;
}
</style>
</head>
<body>
<script src="__neutralino_globals.js"></script>
<script src="neutralino.js"></script>
<!-- Load the app from the local Neutralino server -->
<iframe id="app-frame" allow="autoplay; fullscreen; microphone; clipboard-read; clipboard-write"></iframe>
<script>
// initialize Neutralino in the Shell (Local Context)
try {
Neutralino.init();
console.log('[Shell] Neutralino initialized.');
const setupTray = async () => {
const iconPath = '/dist/assets/appicon.png';
console.log('[Shell] Setting tray icon:', iconPath);
const tray = {
icon: iconPath,
menuItems: [
{ id: 'show', text: 'Show Monochrome' },
{ id: 'sep', text: '-' },
{ id: 'quit', text: 'Quit' },
],
};
try {
await Neutralino.os.setTray(tray);
console.log('[Shell] Tray set successfully');
} catch (e) {
console.error('[Shell] Tray error:', e);
await Neutralino.os.showMessageBox(
'Tray Error',
`Failed to set tray: ${JSON.stringify(e)}`,
'ERROR'
);
}
};
Neutralino.events.on('ready', setupTray);
Neutralino.events.on('trayMenuItemClicked', async (event) => {
switch (event.detail.id) {
case 'show':
await Neutralino.window.show();
await Neutralino.window.unminimize();
await Neutralino.window.focus();
break;
case 'quit':
await Neutralino.app.exit();
break;
}
});
} catch (e) {
console.error('[Shell] Failed to init Neutralino:', e);
}
// Point iframe to local server using the port from Neutralino
// NL_PORT is available globally after init (or we can parse it/wait for it)
// Neutralino.init() usually populates window.NL_PORT or we read it from sessionStorage
const initFrame = async () => {
// Simplified Dev Mode Detection using Neutralino's internal args
// 'neu run' adds --neu-dev-auto-reload, which we can use to detect dev environment.
const args = window.NL_ARGS || [];
const isDev = args.some((arg) => arg.includes('--neu-dev-auto-reload') || arg.includes('--debug-mode'));
// Static Dev Port
const DEV_PORT = '5173';
let port = window.NL_PORT || sessionStorage.getItem('NL_PORT') || '5050';
const iframe = document.getElementById('app-frame');
const targetPort = isDev ? DEV_PORT : port;
const targetUrl = `http://localhost:${targetPort}/?mode=neutralino`;
if (isDev) {
console.log(`[Shell] Dev mode detected via NL_ARGS. Waiting 2s for Vite on port ${targetPort}...`);
await new Promise((r) => setTimeout(r, 2000));
} else {
console.log(`[Shell] Production mode detected.`);
}
console.log(`[Shell] Loading app from: ${targetUrl}`);
iframe.src = targetUrl;
};
initFrame();
const iframe = document.getElementById('app-frame');
// Forward generic Neutralino events to the Iframe
const forwardEvent = (eventName, detail) => {
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(
{
type: 'NL_EVENT',
eventName: eventName,
detail: detail,
},
'*'
);
}
};
// Listen for specific events to forward
// Add more here if the app needs them (e.g., tray events)
Neutralino.events.on('windowFocus', () => forwardEvent('windowFocus'));
Neutralino.events.on('windowBlur', () => forwardEvent('windowBlur'));
// Media Key Events (Linux Fix)
Neutralino.events.on('mediaNext', () => forwardEvent('mediaNext'));
Neutralino.events.on('mediaPrevious', () => forwardEvent('mediaPrevious'));
Neutralino.events.on('mediaPlayPause', () => forwardEvent('mediaPlayPause'));
Neutralino.events.on('mediaStop', () => forwardEvent('mediaStop'));
// Handle commands from the Iframe (via Bridge)
window.addEventListener('message', async (event) => {
const { type, eventName, data, extensionId } = event.data;
// Security: In a real scenario, check event.origin if possible.
// But since this loads valid HTTPS content, it's generally safe for this context.
switch (type) {
case 'NL_INIT':
console.log('[Shell] Bridge connected.');
break;
case 'NL_BROADCAST':
// e.g. Discord RPC updates
try {
// console.log('[Shell] Broadcasting:', eventName, data);
await Neutralino.events.broadcast(eventName, data);
} catch (e) {
console.error('[Shell] Broadcast failed:', e);
}
break;
case 'NL_EXTENSION':
// e.g. specific extension dispatch
try {
// console.log('[Shell] Dispatching to extension:', extensionId, eventName);
await Neutralino.extensions.dispatch(extensionId, eventName, data);
} catch (e) {
console.error('[Shell] Extension dispatch failed:', e);
}
break;
case 'NL_APP_EXIT':
Neutralino.app.exit();
break;
case 'NL_WINDOW_MIN':
Neutralino.window.minimize();
break;
case 'NL_WINDOW_MAX':
try {
const isMax = await Neutralino.window.isMaximized();
if (isMax) Neutralino.window.unmaximize();
else Neutralino.window.maximize();
} catch (e) {
console.error('[Shell] Window toggle failed:', e);
}
break;
case 'NL_WINDOW_SET_TITLE':
try {
await Neutralino.window.setTitle(event.data.title);
} catch (e) {
console.error('[Shell] Set title failed:', e);
}
break;
case 'NL_OS_OPEN':
try {
console.log('[Shell] Opening external URL:', event.data.url);
await Neutralino.os.open(event.data.url);
} catch (e) {
console.error('[Shell] Failed to open URL:', e);
}
break;
case 'NL_OS_SHOW_SAVE_DIALOG':
try {
const result = await Neutralino.os.showSaveDialog(event.data.title, event.data.options);
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(
{ type: 'NL_RESPONSE', id: event.data.id, result },
'*'
);
}
} catch (e) {
console.error('[Shell] Show Save Dialog failed:', e);
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(
{ type: 'NL_RESPONSE', id: event.data.id, error: e },
'*'
);
}
}
break;
case 'NL_OS_SHOW_FOLDER_DIALOG':
try {
const result = await Neutralino.os.showFolderDialog(event.data.title, event.data.options);
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(
{ type: 'NL_RESPONSE', id: event.data.id, result },
'*'
);
}
} catch (e) {
console.error('[Shell] Show Folder Dialog failed:', e);
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(
{ type: 'NL_RESPONSE', id: event.data.id, error: e },
'*'
);
}
}
break;
case 'NL_FS_READ_BINARY':
try {
const result = await Neutralino.filesystem.readBinaryFile(event.data.path);
if (iframe && iframe.contentWindow) {
// result is ArrayBuffer, should be transferable
iframe.contentWindow.postMessage(
{ type: 'NL_RESPONSE', id: event.data.id, result },
'*',
[result]
);
}
} catch (e) {
console.error('[Shell] Read Binary File failed:', e);
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(
{ type: 'NL_RESPONSE', id: event.data.id, error: e },
'*'
);
}
}
break;
case 'NL_FS_READ_DIR':
try {
const result = await Neutralino.filesystem.readDirectory(event.data.path);
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(
{ type: 'NL_RESPONSE', id: event.data.id, result },
'*'
);
}
} catch (e) {
console.error('[Shell] Read Directory failed:', e);
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(
{ type: 'NL_RESPONSE', id: event.data.id, error: e },
'*'
);
}
}
break;
case 'NL_FS_STATS':
try {
const result = await Neutralino.filesystem.getStats(event.data.path);
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(
{ type: 'NL_RESPONSE', id: event.data.id, result },
'*'
);
}
} catch (e) {
console.error('[Shell] Get Stats failed:', e);
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(
{ type: 'NL_RESPONSE', id: event.data.id, error: e },
'*'
);
}
}
break;
case 'NL_FS_WRITE_BINARY':
try {
// buffer comes as ArrayBuffer in event.data.buffer (if transferred) or event.data.buffer
await Neutralino.filesystem.writeBinaryFile(event.data.path, event.data.buffer);
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(
{ type: 'NL_RESPONSE', id: event.data.id, result: 'success' },
'*'
);
}
} catch (e) {
console.error('[Shell] Write Binary File failed:', e);
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(
{ type: 'NL_RESPONSE', id: event.data.id, error: e },
'*'
);
}
}
break;
case 'NL_FS_APPEND_BINARY':
try {
await Neutralino.filesystem.appendBinaryFile(event.data.path, event.data.buffer);
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(
{ type: 'NL_RESPONSE', id: event.data.id, result: 'success' },
'*'
);
}
} catch (e) {
console.error('[Shell] Append Binary File failed:', e);
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(
{ type: 'NL_RESPONSE', id: event.data.id, error: e },
'*'
);
}
}
break;
case 'NL_FS_CREATE_DIR':
try {
await Neutralino.filesystem.createDirectory(event.data.path);
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(
{ type: 'NL_RESPONSE', id: event.data.id, result: 'success' },
'*'
);
}
} catch (e) {
console.error('[Shell] Create Directory failed:', e);
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(
{ type: 'NL_RESPONSE', id: event.data.id, error: e },
'*'
);
}
}
break;
}
});
</script>
</body>
</html>

View file

@ -1,64 +0,0 @@
import fs from 'fs';
import { spawn } from 'child_process';
const CONFIG_FILE = 'neutralino.config.json';
const DEV_CONFIG_FILE = 'neutralino.config.dev.json';
const BACKUP_CONFIG_FILE = 'neutralino.config.prod.bak';
function restoreConfig() {
if (fs.existsSync(BACKUP_CONFIG_FILE)) {
try {
// If the current config is the dev one (we can check via content or assume), remove it
if (fs.existsSync(CONFIG_FILE)) {
fs.unlinkSync(CONFIG_FILE);
}
fs.renameSync(BACKUP_CONFIG_FILE, CONFIG_FILE);
console.log('Restored production configuration.');
} catch (e) {
console.error('Failed to restore configuration:', e);
}
}
}
// Ensure we clean up on exit
process.on('SIGINT', () => {
restoreConfig();
process.exit();
});
process.on('exit', () => {
restoreConfig();
});
async function run() {
if (!fs.existsSync(DEV_CONFIG_FILE)) {
console.error('Error: neutralino.config.dev.json not found.');
process.exit(1);
}
try {
// Backup production config
if (fs.existsSync(CONFIG_FILE)) {
fs.renameSync(CONFIG_FILE, BACKUP_CONFIG_FILE);
}
// Copy dev config to main
fs.copyFileSync(DEV_CONFIG_FILE, CONFIG_FILE);
console.log('Switched to development configuration.');
// Run neu
const neu = spawn('npx', ['neu', 'run'], { stdio: 'inherit', shell: true });
neu.on('close', (code) => {
console.log(`Neutralino process exited with code ${code}`);
restoreConfig();
process.exit(code);
});
} catch (e) {
console.error('Error running dev environment:', e);
restoreConfig();
process.exit(1);
}
}
run();

View file

@ -1,6 +1,5 @@
import { defineConfig } from 'vite';
import { VitePWA } from 'vite-plugin-pwa';
import neutralino from 'vite-plugin-neutralino';
import authGatePlugin from './vite-plugin-auth-gate.js';
import path from 'path';
import uploadPlugin from './vite-plugin-upload.js';
@ -17,7 +16,6 @@ function getGitCommitHash() {
}
export default defineConfig(({ mode }) => {
const IS_NEUTRALINO = mode === 'neutralino';
const commitHash = getGitCommitHash();
return {
@ -59,7 +57,6 @@ export default defineConfig(({ mode }) => {
sourcemap: true,
},
plugins: [
IS_NEUTRALINO && neutralino(),
authGatePlugin(),
uploadPlugin(),
blobAssetPlugin(),