Initial commit

This commit is contained in:
puck 2026-04-30 12:13:22 +00:00
commit 210690698b
13 changed files with 1454 additions and 0 deletions

34
.gitignore vendored Normal file
View file

@ -0,0 +1,34 @@
# dependencies (bun install)
node_modules
# output
out
dist
*.tgz
# code coverage
coverage
*.lcov
# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# caches
.eslintcache
.cache
*.tsbuildinfo
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store

6
.prettierrc Normal file
View file

@ -0,0 +1,6 @@
{
"singleQuote": true,
"trailingComma": "es5",
"tabWidth": 2,
"bracketSameLine": true
}

229
bun.lock Normal file
View file

@ -0,0 +1,229 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "microtrmnl",
"dependencies": {
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"bun2nix": "^2.0.8",
"node-ical": "^0.26.0",
"puppeteer-core": "^24.42.0",
"react": "^19.2.5",
"react-dom": "^19.2.5",
},
"devDependencies": {
"@types/bun": "latest",
"prettier": "^3.8.3",
"typescript-language-server": "^5.1.3",
},
"peerDependencies": {
"typescript": "^5",
},
},
},
"packages": {
"@js-temporal/polyfill": ["@js-temporal/polyfill@0.5.1", "", { "dependencies": { "jsbi": "^4.3.0" } }, "sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ=="],
"@puppeteer/browsers": ["@puppeteer/browsers@2.13.0", "", { "dependencies": { "debug": "^4.4.3", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", "semver": "^7.7.4", "tar-fs": "^3.1.1", "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" } }, "sha512-46BZJYJjc/WwmKjsvDFykHtXrtomsCIrwYQPOP7VfMJoZY2bsDF9oROBABR3paDjDcmkUye1Pb1BqdcdiipaWA=="],
"@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="],
"@types/bun": ["@types/bun@1.3.12", "", { "dependencies": { "bun-types": "1.3.12" } }, "sha512-DBv81elK+/VSwXHDlnH3Qduw+KxkTIWi7TXkAeh24zpi5l0B2kUg9Ga3tb4nJaPcOFswflgi/yAvMVBPrxMB+A=="],
"@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
"@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="],
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="],
"b4a": ["b4a@1.8.0", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg=="],
"bare-events": ["bare-events@2.8.2", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ=="],
"bare-fs": ["bare-fs@4.7.1", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw=="],
"bare-os": ["bare-os@3.8.7", "", {}, "sha512-G4Gr1UsGeEy2qtDTZwL7JFLo2wapUarz7iTMcYcMFdS89AIQuBoyjgXZz0Utv7uHs3xA9LckhVbeBi8lEQrC+w=="],
"bare-path": ["bare-path@3.0.0", "", { "dependencies": { "bare-os": "^3.0.1" } }, "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw=="],
"bare-stream": ["bare-stream@2.13.0", "", { "dependencies": { "streamx": "^2.25.0", "teex": "^1.0.1" }, "peerDependencies": { "bare-abort-controller": "*", "bare-buffer": "*", "bare-events": "*" }, "optionalPeers": ["bare-abort-controller", "bare-buffer", "bare-events"] }, "sha512-3zAJRZMDFGjdn+RVnNpF9kuELw+0Fl3lpndM4NcEOhb9zwtSo/deETfuIwMSE5BXanA0FrN1qVjffGwAg2Y7EA=="],
"bare-url": ["bare-url@2.4.1", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-fZapLWNB25gS+etK27NV9KgBNXgo2yeYHuj+OyPblQd6GYAE3JVy6aKxszMV5jhGGFwraXQKA5fldvf3lMyEqw=="],
"basic-ftp": ["basic-ftp@5.3.0", "", {}, "sha512-5K9eNNn7ywHPsYnFwjKgYH8Hf8B5emh7JKcPaVjjrMJFQQwGpwowEnZNEtHs7DfR7hCZsmaK3VA4HUK0YarT+w=="],
"buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
"bun-types": ["bun-types@1.3.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="],
"bun2nix": ["bun2nix@2.0.8", "", { "dependencies": { "sade": "^1.8.1" }, "bin": { "bun2nix": "index.ts" } }, "sha512-pwq35hA81X1Kjsi5Xo69Aii9aY3zZHhWXwqF1QRz/uB35KzKbwJZ16WrhadiG9/T6bjOsRrPZtzuAyqmlXopLw=="],
"chromium-bidi": ["chromium-bidi@14.0.0", "", { "dependencies": { "mitt": "^3.0.1", "zod": "^3.24.1" }, "peerDependencies": { "devtools-protocol": "*" } }, "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw=="],
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
"data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="],
"devtools-protocol": ["devtools-protocol@0.0.1595872", "", {}, "sha512-kRfgp8vWVjBu/fbYCiVFiOqsCk3CrMKEo3WbgGT2NXK2dG7vawWPBljixajVgGK9II8rDO9G0oD0zLt3I1daRg=="],
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
"escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "esgenerate": "bin/esgenerate.js", "escodegen": "bin/escodegen.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="],
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
"events-universal": ["events-universal@1.0.1", "", { "dependencies": { "bare-events": "^2.7.0" } }, "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="],
"extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="],
"fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="],
"fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="],
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
"get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="],
"get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="],
"http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
"ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="],
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
"jsbi": ["jsbi@4.3.2", "", {}, "sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew=="],
"lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
"mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"netmask": ["netmask@2.1.1", "", {}, "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA=="],
"node-ical": ["node-ical@0.26.0", "", { "dependencies": { "rrule-temporal": "^1.5.1", "temporal-polyfill": "^0.3.2" } }, "sha512-tJZY2fMb38Gbj0P05zHMWBr90MslhGZ1qEbOWYnokBYPPX/lYskL/0NnWoeiXTBNod+kRRcTOjxAeB20kfvKyw=="],
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
"pac-proxy-agent": ["pac-proxy-agent@7.2.0", "", { "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", "agent-base": "^7.1.2", "debug": "^4.3.4", "get-uri": "^6.0.1", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.6", "pac-resolver": "^7.0.1", "socks-proxy-agent": "^8.0.5" } }, "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA=="],
"pac-resolver": ["pac-resolver@7.0.1", "", { "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" } }, "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg=="],
"pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="],
"prettier": ["prettier@3.8.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw=="],
"progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="],
"proxy-agent": ["proxy-agent@6.5.0", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.5" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="],
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
"pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="],
"puppeteer-core": ["puppeteer-core@24.42.0", "", { "dependencies": { "@puppeteer/browsers": "2.13.0", "chromium-bidi": "14.0.0", "debug": "^4.4.3", "devtools-protocol": "0.0.1595872", "typed-query-selector": "^2.12.1", "webdriver-bidi-protocol": "0.4.1", "ws": "^8.19.0" } }, "sha512-T4zXokk/izH01fYPhyyev1A4piWiOKrYq7CUFpdoYQxmOnXoV6YjUabmfIjCYkNspSoAXIxRid3Tw+Vg0fthYg=="],
"react": ["react@19.2.5", "", {}, "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA=="],
"react-dom": ["react-dom@19.2.5", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.5" } }, "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag=="],
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
"rrule-temporal": ["rrule-temporal@1.5.2", "", { "dependencies": { "@js-temporal/polyfill": "^0.5.1" } }, "sha512-I5rAiZfRlMh0vuG23HrGBMLZOSiQO7H1Uq8l9qyfA6oTD5j+UMRwpRs4aVU4XdaFhgN1p3K+cHelG8KvLTTm+g=="],
"sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="],
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
"socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="],
"socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="],
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"streamx": ["streamx@2.25.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg=="],
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"tar-fs": ["tar-fs@3.1.2", "", { "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" }, "optionalDependencies": { "bare-fs": "^4.0.1", "bare-path": "^3.0.0" } }, "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw=="],
"tar-stream": ["tar-stream@3.1.8", "", { "dependencies": { "b4a": "^1.6.4", "bare-fs": "^4.5.5", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ=="],
"teex": ["teex@1.0.1", "", { "dependencies": { "streamx": "^2.12.5" } }, "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg=="],
"temporal-polyfill": ["temporal-polyfill@0.3.2", "", { "dependencies": { "temporal-spec": "0.3.1" } }, "sha512-TzHthD/heRK947GNiSu3Y5gSPpeUDH34+LESnfsq8bqpFhsB79HFBX8+Z834IVX68P3EUyRPZK5bL/1fh437Eg=="],
"temporal-spec": ["temporal-spec@0.3.1", "", {}, "sha512-B4TUhezh9knfSIMwt7RVggApDRJZo73uZdj8AacL2mZ8RP5KtLianh2MXxL06GN9ESYiIsiuoLQhgVfwe55Yhw=="],
"text-decoder": ["text-decoder@1.2.7", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"typed-query-selector": ["typed-query-selector@2.12.1", "", {}, "sha512-uzR+FzI8qrUEIu96oaeBJmd9E7CFEiQ3goA5qCVgc4s5llSubcfGHq9yUstZx/k4s9dXHVKsE35YWoFyvEqEHA=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"typescript-language-server": ["typescript-language-server@5.1.3", "", { "bin": { "typescript-language-server": "lib/cli.mjs" } }, "sha512-r+pAcYtWdN8tKlYZPwiiHNA2QPjXnI02NrW5Sf2cVM3TRtuQ3V9EKKwOxqwaQ0krsaEXk/CbN90I5erBuf84Vg=="],
"undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
"webdriver-bidi-protocol": ["webdriver-bidi-protocol@0.4.1", "", {}, "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw=="],
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="],
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
"yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="],
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
}
}

420
bun.nix Normal file
View file

@ -0,0 +1,420 @@
# Autogenerated by `bun2nix`, editing manually is not recommended
#
# Set of Bun packages to install
#
# Consume this with `fetchBunDeps` (recommended)
# or `pkgs.callPackage` if you wish to handle
# it manually.
{
copyPathToStore,
fetchFromGitHub,
fetchgit,
fetchurl,
...
}:
{
"@js-temporal/polyfill@0.5.1" = fetchurl {
url = "https://registry.npmjs.org/@js-temporal/polyfill/-/polyfill-0.5.1.tgz";
hash = "sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ==";
};
"@puppeteer/browsers@2.13.0" = fetchurl {
url = "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.0.tgz";
hash = "sha512-46BZJYJjc/WwmKjsvDFykHtXrtomsCIrwYQPOP7VfMJoZY2bsDF9oROBABR3paDjDcmkUye1Pb1BqdcdiipaWA==";
};
"@tootallnate/quickjs-emscripten@0.23.0" = fetchurl {
url = "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz";
hash = "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==";
};
"@types/bun@1.3.12" = fetchurl {
url = "https://registry.npmjs.org/@types/bun/-/bun-1.3.12.tgz";
hash = "sha512-DBv81elK+/VSwXHDlnH3Qduw+KxkTIWi7TXkAeh24zpi5l0B2kUg9Ga3tb4nJaPcOFswflgi/yAvMVBPrxMB+A==";
};
"@types/node@25.6.0" = fetchurl {
url = "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz";
hash = "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==";
};
"@types/react-dom@19.2.3" = fetchurl {
url = "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz";
hash = "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==";
};
"@types/react@19.2.14" = fetchurl {
url = "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz";
hash = "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==";
};
"@types/yauzl@2.10.3" = fetchurl {
url = "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz";
hash = "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==";
};
"agent-base@7.1.4" = fetchurl {
url = "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz";
hash = "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==";
};
"ansi-regex@5.0.1" = fetchurl {
url = "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz";
hash = "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==";
};
"ansi-styles@4.3.0" = fetchurl {
url = "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz";
hash = "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==";
};
"ast-types@0.13.4" = fetchurl {
url = "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz";
hash = "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==";
};
"b4a@1.8.0" = fetchurl {
url = "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz";
hash = "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==";
};
"bare-events@2.8.2" = fetchurl {
url = "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz";
hash = "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==";
};
"bare-fs@4.7.1" = fetchurl {
url = "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.1.tgz";
hash = "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==";
};
"bare-os@3.8.7" = fetchurl {
url = "https://registry.npmjs.org/bare-os/-/bare-os-3.8.7.tgz";
hash = "sha512-G4Gr1UsGeEy2qtDTZwL7JFLo2wapUarz7iTMcYcMFdS89AIQuBoyjgXZz0Utv7uHs3xA9LckhVbeBi8lEQrC+w==";
};
"bare-path@3.0.0" = fetchurl {
url = "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz";
hash = "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==";
};
"bare-stream@2.13.0" = fetchurl {
url = "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.0.tgz";
hash = "sha512-3zAJRZMDFGjdn+RVnNpF9kuELw+0Fl3lpndM4NcEOhb9zwtSo/deETfuIwMSE5BXanA0FrN1qVjffGwAg2Y7EA==";
};
"bare-url@2.4.1" = fetchurl {
url = "https://registry.npmjs.org/bare-url/-/bare-url-2.4.1.tgz";
hash = "sha512-fZapLWNB25gS+etK27NV9KgBNXgo2yeYHuj+OyPblQd6GYAE3JVy6aKxszMV5jhGGFwraXQKA5fldvf3lMyEqw==";
};
"basic-ftp@5.3.0" = fetchurl {
url = "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.0.tgz";
hash = "sha512-5K9eNNn7ywHPsYnFwjKgYH8Hf8B5emh7JKcPaVjjrMJFQQwGpwowEnZNEtHs7DfR7hCZsmaK3VA4HUK0YarT+w==";
};
"buffer-crc32@0.2.13" = fetchurl {
url = "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz";
hash = "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==";
};
"bun-types@1.3.12" = fetchurl {
url = "https://registry.npmjs.org/bun-types/-/bun-types-1.3.12.tgz";
hash = "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA==";
};
"bun2nix@2.0.8" = fetchurl {
url = "https://registry.npmjs.org/bun2nix/-/bun2nix-2.0.8.tgz";
hash = "sha512-pwq35hA81X1Kjsi5Xo69Aii9aY3zZHhWXwqF1QRz/uB35KzKbwJZ16WrhadiG9/T6bjOsRrPZtzuAyqmlXopLw==";
};
"chromium-bidi@14.0.0" = fetchurl {
url = "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-14.0.0.tgz";
hash = "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==";
};
"cliui@8.0.1" = fetchurl {
url = "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz";
hash = "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==";
};
"color-convert@2.0.1" = fetchurl {
url = "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz";
hash = "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==";
};
"color-name@1.1.4" = fetchurl {
url = "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz";
hash = "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==";
};
"csstype@3.2.3" = fetchurl {
url = "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz";
hash = "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==";
};
"data-uri-to-buffer@6.0.2" = fetchurl {
url = "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz";
hash = "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==";
};
"debug@4.4.3" = fetchurl {
url = "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz";
hash = "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==";
};
"degenerator@5.0.1" = fetchurl {
url = "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz";
hash = "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==";
};
"devtools-protocol@0.0.1595872" = fetchurl {
url = "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1595872.tgz";
hash = "sha512-kRfgp8vWVjBu/fbYCiVFiOqsCk3CrMKEo3WbgGT2NXK2dG7vawWPBljixajVgGK9II8rDO9G0oD0zLt3I1daRg==";
};
"emoji-regex@8.0.0" = fetchurl {
url = "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz";
hash = "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==";
};
"end-of-stream@1.4.5" = fetchurl {
url = "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz";
hash = "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==";
};
"escalade@3.2.0" = fetchurl {
url = "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz";
hash = "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==";
};
"escodegen@2.1.0" = fetchurl {
url = "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz";
hash = "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==";
};
"esprima@4.0.1" = fetchurl {
url = "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz";
hash = "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==";
};
"estraverse@5.3.0" = fetchurl {
url = "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz";
hash = "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==";
};
"esutils@2.0.3" = fetchurl {
url = "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz";
hash = "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==";
};
"events-universal@1.0.1" = fetchurl {
url = "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz";
hash = "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==";
};
"extract-zip@2.0.1" = fetchurl {
url = "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz";
hash = "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==";
};
"fast-fifo@1.3.2" = fetchurl {
url = "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz";
hash = "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==";
};
"fd-slicer@1.1.0" = fetchurl {
url = "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz";
hash = "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==";
};
"get-caller-file@2.0.5" = fetchurl {
url = "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz";
hash = "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==";
};
"get-stream@5.2.0" = fetchurl {
url = "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz";
hash = "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==";
};
"get-uri@6.0.5" = fetchurl {
url = "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz";
hash = "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==";
};
"http-proxy-agent@7.0.2" = fetchurl {
url = "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz";
hash = "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==";
};
"https-proxy-agent@7.0.6" = fetchurl {
url = "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz";
hash = "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==";
};
"ip-address@10.1.0" = fetchurl {
url = "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz";
hash = "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==";
};
"is-fullwidth-code-point@3.0.0" = fetchurl {
url = "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz";
hash = "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==";
};
"jsbi@4.3.2" = fetchurl {
url = "https://registry.npmjs.org/jsbi/-/jsbi-4.3.2.tgz";
hash = "sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew==";
};
"lru-cache@7.18.3" = fetchurl {
url = "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz";
hash = "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==";
};
"mitt@3.0.1" = fetchurl {
url = "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz";
hash = "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==";
};
"mri@1.2.0" = fetchurl {
url = "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz";
hash = "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==";
};
"ms@2.1.3" = fetchurl {
url = "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz";
hash = "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==";
};
"netmask@2.1.1" = fetchurl {
url = "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz";
hash = "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==";
};
"node-ical@0.26.0" = fetchurl {
url = "https://registry.npmjs.org/node-ical/-/node-ical-0.26.0.tgz";
hash = "sha512-tJZY2fMb38Gbj0P05zHMWBr90MslhGZ1qEbOWYnokBYPPX/lYskL/0NnWoeiXTBNod+kRRcTOjxAeB20kfvKyw==";
};
"once@1.4.0" = fetchurl {
url = "https://registry.npmjs.org/once/-/once-1.4.0.tgz";
hash = "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==";
};
"pac-proxy-agent@7.2.0" = fetchurl {
url = "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz";
hash = "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==";
};
"pac-resolver@7.0.1" = fetchurl {
url = "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz";
hash = "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==";
};
"pend@1.2.0" = fetchurl {
url = "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz";
hash = "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==";
};
"prettier@3.8.3" = fetchurl {
url = "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz";
hash = "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==";
};
"progress@2.0.3" = fetchurl {
url = "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz";
hash = "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==";
};
"proxy-agent@6.5.0" = fetchurl {
url = "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz";
hash = "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==";
};
"proxy-from-env@1.1.0" = fetchurl {
url = "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz";
hash = "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==";
};
"pump@3.0.4" = fetchurl {
url = "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz";
hash = "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==";
};
"puppeteer-core@24.42.0" = fetchurl {
url = "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.42.0.tgz";
hash = "sha512-T4zXokk/izH01fYPhyyev1A4piWiOKrYq7CUFpdoYQxmOnXoV6YjUabmfIjCYkNspSoAXIxRid3Tw+Vg0fthYg==";
};
"react-dom@19.2.5" = fetchurl {
url = "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz";
hash = "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==";
};
"react@19.2.5" = fetchurl {
url = "https://registry.npmjs.org/react/-/react-19.2.5.tgz";
hash = "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==";
};
"require-directory@2.1.1" = fetchurl {
url = "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz";
hash = "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==";
};
"rrule-temporal@1.5.2" = fetchurl {
url = "https://registry.npmjs.org/rrule-temporal/-/rrule-temporal-1.5.2.tgz";
hash = "sha512-I5rAiZfRlMh0vuG23HrGBMLZOSiQO7H1Uq8l9qyfA6oTD5j+UMRwpRs4aVU4XdaFhgN1p3K+cHelG8KvLTTm+g==";
};
"sade@1.8.1" = fetchurl {
url = "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz";
hash = "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==";
};
"scheduler@0.27.0" = fetchurl {
url = "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz";
hash = "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==";
};
"semver@7.7.4" = fetchurl {
url = "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz";
hash = "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==";
};
"smart-buffer@4.2.0" = fetchurl {
url = "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz";
hash = "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==";
};
"socks-proxy-agent@8.0.5" = fetchurl {
url = "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz";
hash = "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==";
};
"socks@2.8.7" = fetchurl {
url = "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz";
hash = "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==";
};
"source-map@0.6.1" = fetchurl {
url = "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz";
hash = "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==";
};
"streamx@2.25.0" = fetchurl {
url = "https://registry.npmjs.org/streamx/-/streamx-2.25.0.tgz";
hash = "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==";
};
"string-width@4.2.3" = fetchurl {
url = "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz";
hash = "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==";
};
"strip-ansi@6.0.1" = fetchurl {
url = "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz";
hash = "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==";
};
"tar-fs@3.1.2" = fetchurl {
url = "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz";
hash = "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==";
};
"tar-stream@3.1.8" = fetchurl {
url = "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz";
hash = "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==";
};
"teex@1.0.1" = fetchurl {
url = "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz";
hash = "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==";
};
"temporal-polyfill@0.3.2" = fetchurl {
url = "https://registry.npmjs.org/temporal-polyfill/-/temporal-polyfill-0.3.2.tgz";
hash = "sha512-TzHthD/heRK947GNiSu3Y5gSPpeUDH34+LESnfsq8bqpFhsB79HFBX8+Z834IVX68P3EUyRPZK5bL/1fh437Eg==";
};
"temporal-spec@0.3.1" = fetchurl {
url = "https://registry.npmjs.org/temporal-spec/-/temporal-spec-0.3.1.tgz";
hash = "sha512-B4TUhezh9knfSIMwt7RVggApDRJZo73uZdj8AacL2mZ8RP5KtLianh2MXxL06GN9ESYiIsiuoLQhgVfwe55Yhw==";
};
"text-decoder@1.2.7" = fetchurl {
url = "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz";
hash = "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==";
};
"tslib@2.8.1" = fetchurl {
url = "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz";
hash = "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==";
};
"typed-query-selector@2.12.1" = fetchurl {
url = "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.1.tgz";
hash = "sha512-uzR+FzI8qrUEIu96oaeBJmd9E7CFEiQ3goA5qCVgc4s5llSubcfGHq9yUstZx/k4s9dXHVKsE35YWoFyvEqEHA==";
};
"typescript-language-server@5.1.3" = fetchurl {
url = "https://registry.npmjs.org/typescript-language-server/-/typescript-language-server-5.1.3.tgz";
hash = "sha512-r+pAcYtWdN8tKlYZPwiiHNA2QPjXnI02NrW5Sf2cVM3TRtuQ3V9EKKwOxqwaQ0krsaEXk/CbN90I5erBuf84Vg==";
};
"typescript@5.9.3" = fetchurl {
url = "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz";
hash = "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==";
};
"undici-types@7.19.2" = fetchurl {
url = "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz";
hash = "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==";
};
"webdriver-bidi-protocol@0.4.1" = fetchurl {
url = "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.1.tgz";
hash = "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==";
};
"wrap-ansi@7.0.0" = fetchurl {
url = "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz";
hash = "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==";
};
"wrappy@1.0.2" = fetchurl {
url = "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz";
hash = "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==";
};
"ws@8.20.0" = fetchurl {
url = "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz";
hash = "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==";
};
"y18n@5.0.8" = fetchurl {
url = "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz";
hash = "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==";
};
"yargs-parser@21.1.1" = fetchurl {
url = "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz";
hash = "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==";
};
"yargs@17.7.2" = fetchurl {
url = "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz";
hash = "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==";
};
"yauzl@2.10.0" = fetchurl {
url = "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz";
hash = "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==";
};
"zod@3.25.76" = fetchurl {
url = "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz";
hash = "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==";
};
}

77
default.nix Normal file
View file

@ -0,0 +1,77 @@
{
pkgs,
lib,
config,
bun2nix,
...
}: let
# This code, when _compiled/minified_, seems to break.
# I'm not debugging this right now.
server = pkgs.stdenv.mkDerivation {
pname = "trmnlc";
version = "1.0.0";
src = ./.;
nativeBuildInputs = [ bun2nix.hook ];
bunDeps = bun2nix.fetchBunDeps {
bunNix = ./bun.nix;
};
buildPhase = "true";
installPhase = ''
cp -rf . $out
'';
};
cfg = config.services.trmnlc;
json = pkgs.formats.json {};
configFile = json.generate "config.json" cfg.settings;
in
{
options.services.trmnlc = {
enable = lib.mkEnableOption "TRMNLc server software";
settings = lib.mkOption {
default = {};
type = json.type;
example = lib.literalExpression ''
{
unknown = {
urls = ["https://user.fm/calendar/....ics"];
refresh = 60;
model = "inkplate_10";
};
}
'';
description = "The TRMNLc configuration.";
};
};
config = lib.mkIf cfg.enable {
systemd.services.trmnl = {
wantedBy = [ "multi-user.target" ];
path = [ pkgs.imagemagick ];
environment = {
FIREFOX = "${pkgs.firefox}/bin/firefox";
CONFIG_FILE = configFile;
HOME = "/tmp";
TZ = "Europe/Amsterdam";
FONTCONFIG_FILE = ./fonts.conf;
COLORMAP = ./colormap.png;
BUN_PORT = "2300";
};
serviceConfig = {
ExecStart = "${pkgs.bun}/bin/bun run ${server}/src/web.ts";
KillMode = "mixed";
KillSignal = "SIGKILL";
Restart = "on-failure";
RestartSec = 5;
RestartPreventExitStatus = "SIGKILL";
DynamicUser = true;
};
};
};
}

15
flake.nix Normal file
View file

@ -0,0 +1,15 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
bun2nix.url = "github:nix-community/bun2nix";
bun2nix.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = {
self,
nixpkgs,
bun2nix,
}: {
nixosModules.default = ./default.nix;
};
}

23
package.json Normal file
View file

@ -0,0 +1,23 @@
{
"name": "microtrmnl",
"module": "index.ts",
"type": "module",
"private": true,
"devDependencies": {
"@types/bun": "latest",
"prettier": "^3.8.3",
"typescript-language-server": "^5.1.3"
},
"peerDependencies": {
"typescript": "^5"
},
"dependencies": {
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"bun2nix": "^2.0.8",
"node-ical": "^0.26.0",
"puppeteer-core": "^24.42.0",
"react": "^19.2.5",
"react-dom": "^19.2.5"
}
}

40
src/devices.ts Normal file
View file

@ -0,0 +1,40 @@
export interface DeviceModel {
name: string;
label: string;
description: string;
width: number;
height: number;
colors: number;
bit_depth: number;
scale_factor: number;
rotation: number;
mime_type: string;
offset_x: number;
offset_y: number;
kind: string;
palette_ids: string[];
preview_white_point: string;
image_size_limit: number;
image_upload_supported: boolean;
css: {
classes: {
device: string;
size: string;
density: string;
};
variables: [string, string][];
};
}
export const devices: DeviceModel[] = (
(await (
await fetch(
(globalThis as any).document
? '/api/models'
: 'https://trmnl.com/api/models'
)
).json()) as {
data: DeviceModel[];
}
).data;

46
src/render.ts Normal file
View file

@ -0,0 +1,46 @@
import puppeteer from 'puppeteer-core';
import { type DeviceModel } from './devices';
import { render, type PluginList } from './template';
import { sleep } from 'bun';
import sharp from 'sharp';
const p = await puppeteer.launch({
browser: 'firefox',
args: ['--new-instance'],
executablePath: Bun.env['FIREFOX'],
dumpio: true,
});
export const makeScreenshot = async (
drawable: PluginList,
model: DeviceModel
): Promise<Uint8Array> => {
const page = await p.newPage({});
await page.setViewport({
width: model.width,
height: model.height,
deviceScaleFactor: 1,
});
await page.setContent(await render(drawable, model));
await sleep(2000);
const screenshot = await page.screenshot({ encoding: 'binary' });
await page.close();
const processed = await Bun.spawn(
[
'magick',
'-',
'-dither',
'FloydSteinberg',
'-remap',
Bun.env['COLORMAP'],
'-define',
'png:bit-depth=2',
'-define',
'png:color-type=0',
'-strip',
'png:-',
],
{ stdin: screenshot }
).stdout.bytes();
return processed;
};

39
src/template.ts Normal file
View file

@ -0,0 +1,39 @@
import { devices, type DeviceModel } from './devices';
const BASE = `
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;350;375;400;450;600;700&amp;display=swap" rel="stylesheet">
<link href="https://trmnl.com/css/3.0.3/plugins.css" rel="stylesheet">
</head>
<body class="environment trmnl">
<div class="screen screen--1bit {{classes}}" data-pixel-perfect="true">{{contents}}</div>
<script src="https://trmnl.com/js/3.0.3/plugins.js"></script>
</body>
</html>`;
export type RenderMode =
| 'full'
| 'half_horizontal'
| 'half_vertical'
| 'quadrant';
export interface Renderable {
render(mode: RenderMode, model: DeviceModel): Promise<string>;
}
export type PluginList = [Renderable, RenderMode][];
export const render = async (plugins: PluginList, model: DeviceModel) => {
const classes = Object.values(model.css.classes).join(' ');
let contents = '';
for (let [plugin, mode] of plugins) {
contents += await plugin.render(mode, model);
}
return BASE.replace('{{classes}}', classes).replace('{{contents}}', contents);
};

112
src/web.ts Normal file
View file

@ -0,0 +1,112 @@
import { SHA256 } from 'bun';
import { devices } from './devices';
import { makeScreenshot } from './render';
import { render } from './template';
import { ICSRenderable } from './xlcalendar';
let renderMap = new Map();
interface Configuration {
[mac: string]: {
urls: string[];
refresh?: number;
model: string;
};
}
const config: Configuration = JSON.parse(
await Bun.file(Bun.env['CONFIG_FILE'] ?? 'config.json').text()
);
Bun.serve({
hostname: '0.0.0.0',
routes: {
'/api/setup': async (req) => {
const id = req.headers.get('ID') ?? 'UNKNOWN';
const newfriendly = id.replaceAll(':', '');
return Response.json({
status: 200,
message: 'hi',
api_key: id,
friendly_id: newfriendly,
image_url: 'https://trmnl.com/images/setup/setup-logo.bmp',
filename: 'empty_state',
});
},
'/api/display': async (req) => {
const id = req.headers.get('ID') ?? 'unknown';
const newfriendly = id.replaceAll(':', '');
const device = config[id] ?? {
urls: [],
refresh: undefined,
model: 'inkplate_10',
};
console.log(`Rendering for ${id}..`);
let plugin = new ICSRenderable(device.urls);
const rendered = makeScreenshot(
[[plugin, 'full']],
devices.find((a) => a.name === device.model)!
);
renderMap.set(id.toLowerCase(), rendered);
await Bun.sleep(3500);
let nextRefresh = device.refresh ?? 5 * 60;
if (plugin.nextEvent) {
let timeUntilNextEvent =
(plugin.nextEvent.valueOf() - Date.now()) / 1000;
if (timeUntilNextEvent < 60) timeUntilNextEvent = 60;
if (device.refresh && timeUntilNextEvent > device.refresh)
timeUntilNextEvent = device.refresh;
if (timeUntilNextEvent < nextRefresh) nextRefresh = timeUntilNextEvent;
}
if (!plugin.hash) {
console.log(`Had to respond before plugin for ${id} was rendered.`);
}
return Response.json({
status: 0,
image_url: `http://192.168.50.124:2300/api/render/${id.toLowerCase()}/${plugin.hash ?? Math.random()}.png`,
refresh_rate: nextRefresh.toString(),
update_firmware: false,
firmware_url: null,
reset_firmware: false,
filename: `${plugin.hash ?? Math.random()}.png`,
});
},
'/api/display/html': async (req) => {
const id = req.headers.get('ID') ?? 'unknown';
const newfriendly = id.replaceAll(':', '');
const device = config[id] ?? {
urls: [],
refresh: undefined,
model: 'inkplate_10',
};
let plugin = new ICSRenderable(device.urls);
const rendered = await render(
[[plugin, 'full']],
devices.find((a) => a.name === device.model)!
);
return new Response(rendered, {
headers: { 'Content-Type': 'text/html' },
});
},
'/api/render/:id/:ignore': async (req) => {
return new Response(await renderMap.get(req.params.id), {
headers: { 'Content-Type': 'image/png' },
});
},
},
});

383
src/xlcalendar.tsx Normal file
View file

@ -0,0 +1,383 @@
import { renderToString } from 'react-dom/server';
import * as ical from 'node-ical';
import type { Renderable, RenderMode } from '../src/template';
import type { DeviceModel } from '../src/devices';
type ProcessedEvent = ical.EventInstance & { calname: string };
const SectionLabel = ({
text,
meta,
inverted = false,
}: {
text: string;
meta?: string;
inverted?: boolean;
}) => {
const classes = inverted
? 'label lg:label--large label--underline'
: 'label lg:label--large label--filled';
return (
<div className="flex flex--row flex--between flex--center-y gap--xsmall">
<span className={classes}>{text}</span>
{meta && <span className={classes}>{meta}</span>}
</div>
);
};
const TitleBar = ({
title,
instance,
}: {
title: string;
instance?: string;
}) => {
return (
<div className="title_bar">
<span className="title">{title}</span>
{instance && <span className="instance">{instance}</span>}
</div>
);
};
interface Data {
current_event?: ProcessedEvent;
next_event?: ProcessedEvent;
secondary_event?: ProcessedEvent;
concurrent_event_count: number;
upcoming_count: number;
}
const processCalendar = (cal: ProcessedEvent[]): Data => {
const data: Data = { concurrent_event_count: 0, upcoming_count: 0 };
const now = Date.now();
for (let event of cal) {
if (event.start.valueOf() > now) data.upcoming_count++;
if (event.isFullDay) continue;
if (event.start.valueOf() <= now && event.end.valueOf() > now) {
if (data.current_event) data.concurrent_event_count++;
else {
data.current_event = event;
}
}
if (!data.next_event && event.start.valueOf() > now) {
data.next_event = event;
} else if (
!data.secondary_event &&
event.start.valueOf() > now &&
data.next_event
) {
data.secondary_event = event;
}
if (
data.current_event &&
data.next_event &&
data.secondary_event &&
event.start.valueOf() > now
)
break;
}
return data;
};
const getTimeLabel = (event: ProcessedEvent): string => {
let now = new Date(Date.now());
if (
event.start.toDateString() == event.end.toDateString() &&
now.toDateString() == event.start.toDateString()
)
return (
event.start.toLocaleTimeString('nl-NL', { timeStyle: 'short' }) +
' - ' +
event.end.toLocaleTimeString('nl-NL', { timeStyle: 'short' })
);
else
return (
event.start.toLocaleDateString('nl-NL', { dateStyle: 'medium' }) +
(event.isFullDay
? ''
: ' om ' +
event.start.toLocaleTimeString('nl-NL', { timeStyle: 'short' }))
);
};
// title bar shows either 'FREE NOW' or 'BUSY' (optionally 'BUSY · {upcoming_count} MORE')
const HeroPanel = ({
event,
now,
concurrent,
}: {
event?: ProcessedEvent;
now: boolean;
concurrent: number;
}) => {
// show current event if one, otherwise show next
// hero panel doesn't care about this but
const primaryLabel = event && now ? 'NU' : 'VOLGENDE';
let timeLabel = event ? getTimeLabel(event) : '';
if (concurrent) timeLabel += ` + ${concurrent} meer`;
if (event) {
return (
<div className="rounded--large p--2 flex flex--col gap--small h--[60cqh] bg--black text--white">
<SectionLabel text={primaryLabel} meta={timeLabel} inverted />
<span className={now ? "bg--black rounded p--[4cqw] " : ""}>
<span
className={
(now ? 'text--white' : 'text--black') +
' rounded p--[4cqw] value value--xxxlarge text--center block lg:hidden'
}
data-value-fit
data-value-fit-max-height="150">
{event?.summary as string}
</span>
<span
className={
(now ? 'text--white' : 'text--black') +
' rounded p--[4cqw] value value--xxxlarge text--center hidden lg:block lg:portrait:hidden'
}
data-value-fit
data-value-fit-max-height="320">
{event?.summary as string}
</span>
<span
className={
(now ? 'text--white' : 'text--black') +
' value value--xxxlarge text--center hidden lg:portrait:block'
}
data-value-fit
data-value-fit-max-height="480">
{event?.summary as string}
</span>
</span>
</div>
);
} else {
return (
<div className="rounded--large p--2 flex flex--col gap--small h--[60cqh]">
<div className="value value--xxxlarge">VRIJ</div>
<div className="title title--small">Geen events vandaag</div>
</div>
);
}
};
const NextEvent = ({
event,
hasNow,
}: {
event?: ProcessedEvent;
hasNow: boolean;
}) => {
let ev;
if (event)
ev = (
<>
<div
className="value text--center md:portrait:value--small lg:value--large lg:portrait:value--large"
data-clamp="2">
{event.summary as string}
</div>
<div className="value text--center value--small md:value--medium lg:value">
{getTimeLabel(event)}
</div>
</>
);
else
ev = (
<>
<div className="title title--small">VRIJ</div>
<div className="description">Geen events meer hierna.</div>
</>
);
return (
<div className="outline rounded--large p--2 flex flex--col gap--small">
<SectionLabel text={hasNow ? 'VOLGENDE' : 'DAARNA'} inverted />
{ev}
</div>
);
};
const SmallRenderer = ({ event }: { event: ProcessedEvent }) => {
const date = event.start
.toLocaleDateString('nl-NL', { dateStyle: 'medium' })
.replace(' ' + event.start.getFullYear().toString(), '');
const time = event.start.toLocaleTimeString('nl-NL', { timeStyle: 'short' });
return (
<div className="item">
<div className="meta"></div>
<div className="content">
<span
className="title title--small md:title--medium lg:title--large"
data-clamp="1">
{event.summary as string}
</span>
<span className="label label--small lg:label--base lg:label--outline">
{time} · {date}
</span>
</div>
</div>
);
};
const LaterEvents = ({
data,
events,
}: {
data: Data;
events: ProcessedEvent[];
}) => {
let eligibleEvents = [];
let now = Date.now();
for (let event of events) {
if (
event === data.current_event ||
event === data.next_event ||
event.isFullDay
)
continue;
const eligible_ts = event.isFullDay ? event.end : event.start;
if (eligible_ts.valueOf() > now) {
eligibleEvents.push(
<SmallRenderer
key={event.start.toString() + event.event.uid}
event={event}
/>
);
if (eligibleEvents.length > 2) break;
}
}
return (
<div className="outline rounded--large p--2 flex flex--col gap--small">
<SectionLabel text="LATER" inverted />
<div className="flex flex--col gap--xsmall lg:gap--small">
{eligibleEvents.length ? eligibleEvents : null}
{eligibleEvents.length ? null : (
<div className="description">Niks anders gepland.</div>
)}
</div>
</div>
);
};
const Main = ({ data, events }: { data: Data; events: ProcessedEvent[] }) => {
let titlebarFlag = 'VRIJ';
if (data.current_event) {
titlebarFlag = 'BEZET';
if (data.upcoming_count) titlebarFlag += ` · ${data.upcoming_count} MEER`;
}
const now = new Date(Date.now()).toLocaleString('nl-NL', {
dateStyle: 'medium',
timeStyle: 'short',
});
return (
<>
<div className="layout">
<div className="flex flex--col gap--medium h--full">
<HeroPanel
event={data.current_event ?? data.next_event}
now={data.current_event !== undefined}
concurrent={data.concurrent_event_count}
/>
<div className="grid grid--cols-2 gap--medium grow">
<NextEvent
event={
data.current_event ? data.secondary_event : data.next_event
}
hasNow={!!data.current_event}
/>
<LaterEvents data={data} events={events} />
</div>
</div>
</div>
<TitleBar
title={now.substring(0, now.length - 1) + 'x'}
instance={titlebarFlag}
/>
</>
);
};
export class ICSRenderable implements Renderable {
public nextEvent?: Date;
public hash: string = '';
constructor(private files: string[]) {}
private async fetch(
url: string,
start: Date,
end: Date
): Promise<ProcessedEvent[]> {
const resp = await fetch(url, { headers: [['User-Agent', 'private TRMNL fetcher']] });
let data = await resp.text();
let events = Object.values(await ical.async.parseICS(data)).filter(
(a) => a?.type === 'VEVENT'
);
let processed: ProcessedEvent[] = [];
for (let event of events) {
for (let subevent of ical.expandRecurringEvent(event, {
from: start,
to: end,
excludeExdates: true,
expandOngoing: true,
includeOverrides: true,
})) {
processed.push({ ...subevent, calname: 'Test' });
}
}
return processed;
}
async render(_mode: RenderMode): Promise<string> {
const start = new Date(Date.now());
const end = new Date(start.valueOf());
end.setDate(start.getDate() + 7);
let events = (
await Promise.all(this.files.map((a) => this.fetch(a, start, end)))
).flat();
events.sort((a, b) => a.start.valueOf() - b.start.valueOf());
let data = processCalendar(events);
const now = new Date(Date.now()).toLocaleString('nl-NL', {
dateStyle: 'medium',
timeStyle: 'short',
});
this.hash = Bun.hash
.xxHash64(
JSON.stringify([
data.current_event?.summary,
data.next_event?.summary,
data.current_event?.start.toString(),
now.substring(0, now.length - 1)
]),
2n
)
.toString();
this.nextEvent = (data.next_event ?? data.secondary_event)?.start;
return (
'<div class="view view--full">' +
renderToString(<Main data={data} events={events} />) +
'</div>'
);
}
}

30
tsconfig.json Normal file
View file

@ -0,0 +1,30 @@
{
"compilerOptions": {
// Environment setup & latest features
"lib": ["ESNext"],
"target": "ESNext",
"module": "Preserve",
"moduleDetection": "force",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false,
"jsx": "react-jsx"
}
}