diff --git a/package-lock.json b/package-lock.json index afe275d..96e5eee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "cross-env": "^10.1.0", "crypto": "^1.0.1", "dotenv": "^18.0.5", + "exceljs": "^4.4.0", "express": "^5.2.1", "google-auth-library": "^11.1.0", "ink": "^8.0.0", @@ -25,6 +26,7 @@ "libllmorbiter": "file:../llmorbiter", "multer": "^2.4.0", "pdfconvert-wasm": "^0.3.0", + "pdfkit": "^0.20.2", "react": "^19.3.0", "react-canvas-label-editor": "github:sebseb7/react-canvas-label-editor", "selfsigned": "^5.5.0", @@ -154,6 +156,35 @@ "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", "license": "MIT" }, + "node_modules/@fast-csv/format": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@fast-csv/format/-/format-4.3.5.tgz", + "integrity": "sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==", + "license": "MIT", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isboolean": "^3.0.3", + "lodash.isequal": "^4.5.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0" + } + }, + "node_modules/@fast-csv/parse": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@fast-csv/parse/-/parse-4.3.6.tgz", + "integrity": "sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==", + "license": "MIT", + "dependencies": { + "@types/node": "^14.0.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.groupby": "^4.6.0", + "lodash.isfunction": "^3.0.9", + "lodash.isnil": "^4.0.0", + "lodash.isundefined": "^3.0.1", + "lodash.uniq": "^4.5.0" + } + }, "node_modules/@img/colour": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", @@ -712,6 +743,18 @@ "node": ">=18" } }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@noble/hashes": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", @@ -918,6 +961,15 @@ "hasInstallScript": true, "license": "Apache-2.0" }, + "node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -925,6 +977,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "14.18.63", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz", + "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", + "license": "MIT" + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -1048,6 +1106,108 @@ "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", "license": "MIT" }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "license": "MIT", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/archiver-utils/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/archiver-utils/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/archiver/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1069,6 +1229,12 @@ "node": ">=12.0.0" } }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, "node_modules/auto-bind": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", @@ -1123,6 +1289,15 @@ "node": ">=22" } }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "license": "Unlicense", + "engines": { + "node": ">=0.6" + } + }, "node_modules/bignumber.js": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", @@ -1132,6 +1307,19 @@ "node": "*" } }, + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", + "license": "MIT", + "dependencies": { + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" + } + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -1157,6 +1345,12 @@ "node": ">= 6" } }, + "node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "license": "MIT" + }, "node_modules/body-parser": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", @@ -1207,6 +1401,15 @@ "node": "20 || >=22" } }, + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.1.2" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -1231,12 +1434,38 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, + "node_modules/buffer-indexof-polyfill": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz", + "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", + "engines": { + "node": ">=0.2.0" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -1331,6 +1560,18 @@ "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", "license": "MIT" }, + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", + "license": "MIT/X11", + "dependencies": { + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" + } + }, "node_modules/chalk": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-6.0.1.tgz", @@ -1476,6 +1717,15 @@ "node": ">=8" } }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, "node_modules/code-excerpt": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", @@ -1516,6 +1766,41 @@ "node": ">= 6" } }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, "node_modules/content-disposition": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", @@ -1581,6 +1866,45 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "license": "MIT" }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/cross-env": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz", @@ -1628,6 +1952,12 @@ "node": ">= 12" } }, + "node_modules/dayjs": { + "version": "1.11.23", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.23.tgz", + "integrity": "sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1696,6 +2026,12 @@ "node": ">=8" } }, + "node_modules/dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", + "license": "MIT" + }, "node_modules/dijkstrajs": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", @@ -1744,6 +2080,15 @@ "node": ">= 0.4" } }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -1871,6 +2216,40 @@ "node": ">= 0.6" } }, + "node_modules/exceljs": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/exceljs/-/exceljs-4.4.0.tgz", + "integrity": "sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==", + "license": "MIT", + "dependencies": { + "archiver": "^5.0.0", + "dayjs": "^1.8.34", + "fast-csv": "^4.3.1", + "jszip": "^3.10.1", + "readable-stream": "^3.6.0", + "saxes": "^5.0.1", + "tmp": "^0.2.0", + "unzipper": "^0.10.11", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/exceljs/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -1938,11 +2317,23 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, + "node_modules/fast-csv": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz", + "integrity": "sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==", + "license": "MIT", + "dependencies": { + "@fast-csv/format": "4.3.5", + "@fast-csv/parse": "4.3.6" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-uri": { @@ -1985,6 +2376,12 @@ "node": "^12.20 || >= 14.13" } }, + "node_modules/fflate": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", + "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", + "license": "MIT" + }, "node_modules/finalhandler": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", @@ -2019,6 +2416,23 @@ "node": ">=8" } }, + "node_modules/fontkit": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz", + "integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==", + "license": "MIT", + "dependencies": { + "@swc/helpers": "^0.5.12", + "brotli": "^1.3.2", + "clone": "^2.1.2", + "dfa": "^1.2.0", + "fast-deep-equal": "^3.1.3", + "restructure": "^3.0.0", + "tiny-inflate": "^1.0.3", + "unicode-properties": "^1.4.0", + "unicode-trie": "^2.0.0" + } + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -2085,6 +2499,28 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "license": "MIT" }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + }, + "engines": { + "node": ">=0.6" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -2249,6 +2685,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -2360,6 +2802,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -2645,6 +3098,18 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, "node_modules/libdashstore": { "resolved": "../dashdbschema", "link": true @@ -2662,6 +3127,31 @@ "immediate": "~3.0.5" } }, + "node_modules/linebreak": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz", + "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==", + "license": "MIT", + "dependencies": { + "base64-js": "0.0.8", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/linebreak/node_modules/base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/listenercount": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", + "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==", + "license": "ISC" + }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -2674,6 +3164,36 @@ "node": ">=8" } }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "license": "MIT" + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "license": "MIT" + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "license": "MIT" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "license": "MIT" + }, + "node_modules/lodash.groupby": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", + "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==", + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -2686,12 +3206,31 @@ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", "license": "MIT" }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, + "node_modules/lodash.isfunction": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz", + "integrity": "sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==", + "license": "MIT" + }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", "license": "MIT" }, + "node_modules/lodash.isnil": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isnil/-/lodash.isnil-4.0.0.tgz", + "integrity": "sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==", + "license": "MIT" + }, "node_modules/lodash.isnumber": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", @@ -2710,6 +3249,12 @@ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", "license": "MIT" }, + "node_modules/lodash.isundefined": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", + "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==", + "license": "MIT" + }, "node_modules/lodash.mergewith": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", @@ -2723,6 +3268,18 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "license": "MIT" }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" + }, "node_modules/lru-cache": { "version": "11.5.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", @@ -2848,6 +3405,18 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", @@ -3015,6 +3584,15 @@ "url": "https://opencollective.com/node-fetch" } }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -3147,6 +3725,15 @@ "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -3189,6 +3776,20 @@ "integrity": "sha512-FbEk+Grg0UST4kOPuN7OFfVsut1FvDOJNPS3pt4rzCS8VlFsFzD7ptg7YtFZjEOKgJ4MgqlfCgjK4RYaRBpT0Q==", "license": "GPLv3" }, + "node_modules/pdfkit": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/pdfkit/-/pdfkit-0.20.2.tgz", + "integrity": "sha512-Q/w03ICAQyXfHNfTsg1udp0ADerdBN0s7a6XSPL7J7Ro6ABnafoBOCDBstIO9U02mvzHEV8HrvFIw5iV5ejIRA==", + "license": "MIT", + "dependencies": { + "@noble/ciphers": "^1.3.0", + "@noble/hashes": "^1.8.0", + "fflate": "^0.8.3", + "fontkit": "^2.0.4", + "linebreak": "^1.1.0", + "png-js": "^2.0.0" + } + }, "node_modules/pkijs": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.1.tgz", @@ -3209,6 +3810,14 @@ "node": ">=16.0.0" } }, + "node_modules/png-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/png-js/-/png-js-2.0.0.tgz", + "integrity": "sha512-GdzJuUMc6ZSpxFJWVxtOH1bzYHym+TOnveqUjb+VJIbZWbZzyiRGFiKhbiielfpYbgMlhHVhsJ0FTazfuRFkMA==", + "dependencies": { + "fflate": "^0.8.2" + } + }, "node_modules/pngjs": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", @@ -3456,6 +4065,42 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/reflect-metadata": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", @@ -3503,6 +4148,74 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/restructure": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz", + "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==", + "license": "MIT" + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/rimraf/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -3545,6 +4258,18 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/scheduler": { "version": "0.28.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", @@ -4028,6 +4753,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -4037,6 +4777,15 @@ "node": ">=0.6" } }, + "node_modules/traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==", + "license": "MIT/X11", + "engines": { + "node": "*" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -4119,6 +4868,32 @@ "url": "https://opencollective.com/express" } }, + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "license": "MIT", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "node_modules/unicode-trie/node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "license": "MIT" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -4128,12 +4903,40 @@ "node": ">= 0.8" } }, + "node_modules/unzipper": { + "version": "0.10.14", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", + "integrity": "sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==", + "license": "MIT", + "dependencies": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "graceful-fs": "^4.2.2", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -4226,6 +5029,12 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, "node_modules/y18n": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", @@ -4326,6 +5135,104 @@ "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", "license": "MIT" + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "license": "MIT", + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/zip-stream/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/zip-stream/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/zip-stream/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/zip-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } } } } diff --git a/package.json b/package.json index 4890cfd..6b31ba2 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "cross-env": "^10.1.0", "crypto": "^1.0.1", "dotenv": "^18.0.5", + "exceljs": "^4.4.0", "express": "^5.2.1", "google-auth-library": "^11.1.0", "ink": "^8.0.0", @@ -30,6 +31,7 @@ "libllmorbiter": "file:../llmorbiter", "multer": "^2.4.0", "pdfconvert-wasm": "^0.3.0", + "pdfkit": "^0.20.2", "react": "^19.3.0", "react-canvas-label-editor": "github:sebseb7/react-canvas-label-editor", "selfsigned": "^5.5.0", diff --git a/src/defaultSkills/exceljs.SKILL b/src/defaultSkills/exceljs.SKILL new file mode 100644 index 0000000..16fd0a2 --- /dev/null +++ b/src/defaultSkills/exceljs.SKILL @@ -0,0 +1,206 @@ +ExcelJS Spreadsheet Guide: Reading, Writing, Formatting, and VFS Integration + +# Goal +Provide actionable instructions, code patterns, and best practices for creating, reading, modifying, and styling Excel (`.xlsx`) and CSV spreadsheets in the `javascript_execute` sandbox using `ExcelJS`. + +# Environment & Globals +- Both `ExcelJS` and `exceljs` are pre-injected in global scope. Do NOT use `require()` or `import`. +- `Workbook`, `stream.xlsx.WorkbookWriter`, and `stream.xlsx.WorkbookReader` are pre-bridged to the Virtual Filesystem (VFS). +- Wrap async scripts in an IIFE: `module.exports = (async () => { ... })();`. + +--- + +# 1. Creating and Populating a Workbook + +```javascript +module.exports = (async () => { + const wb = new ExcelJS.Workbook(); + wb.creator = 'QuixPOS Assistant'; + wb.created = new Date(); + + const ws = wb.addWorksheet('Articles', { + properties: { tabColor: { argb: 'FF1F4E79' } }, + views: [{ state: 'frozen', xSplit: 0, ySplit: 1 }] // freeze header row + }); + + // Define column schemas + ws.columns = [ + { header: 'SKU', key: 'sku', width: 14 }, + { header: 'Title', key: 'title', width: 36 }, + { header: 'Price (Gross)', key: 'price', width: 14, style: { numFmt: '#,##0.00 €' } }, + { header: 'Stock', key: 'stock', width: 10, style: { numFmt: '#,##0' } }, + { header: 'Supplier SKU', key: 'supplierSku', width: 16 } + ]; + + // Add rows via object matching column keys + ws.addRow({ sku: 'ART-1001', title: 'Espresso Arabica Premium', price: 14.99, stock: 45, supplierSku: 'SUP-01' }); + ws.addRow({ sku: 'ART-1002', title: 'Earl Grey Organic Tea', price: 8.50, stock: 20, supplierSku: 'SUP-02' }); + + // Or add multiple rows at once + ws.addRows([ + { sku: 'ART-1003', title: 'Single Origin Colombia', price: 16.20, stock: 12, supplierSku: 'SUP-03' }, + { sku: 'ART-1004', title: 'Matcha Ceremonial Grade', price: 24.00, stock: 8, supplierSku: 'SUP-04' } + ]); + + // Save directly to VFS + await wb.xlsx.writeFile('articles-export.xlsx'); + + return { success: true, rows: ws.rowCount, file: 'articles-export.xlsx' }; +})(); +``` + +--- + +# 2. Header & Cell Styling + +```javascript +// Header styling: bold white text with dark blue background +const headerRow = ws.getRow(1); +headerRow.height = 24; +headerRow.font = { name: 'Arial', size: 11, bold: true, color: { argb: 'FFFFFFFF' } }; +headerRow.fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: 'FF1F4E79' } +}; +headerRow.alignment = { vertical: 'middle', horizontal: 'center' }; + +// Zebra striping data rows +for (let i = 2; i <= ws.rowCount; i++) { + const row = ws.getRow(i); + row.height = 20; + row.alignment = { vertical: 'middle' }; + if (i % 2 === 0) { + row.fill = { + type: 'pattern', + pattern: 'solid', + fgColor: { argb: 'FFF9FAFB' } + }; + } + // Thin borders + row.eachCell({ includeEmpty: true }, (cell) => { + cell.border = { + bottom: { style: 'thin', color: { argb: 'FFE5E7EB' } } + }; + }); +} +``` + +--- + +# 3. Formulas & Summary Rows + +```javascript +// Add a total row at the end +const totalRow = ws.addRow({ + title: 'Total Stock & Value', + price: { formula: 'SUMPRODUCT(C2:C5, D2:D5)', result: 0 }, + stock: { formula: 'SUM(D2:D5)', result: 85 } +}); + +totalRow.font = { bold: true }; +totalRow.getCell('title').alignment = { horizontal: 'right' }; +totalRow.border = { + top: { style: 'thin', color: { argb: 'FF000000' } }, + bottom: { style: 'double', color: { argb: 'FF000000' } } +}; +``` + +--- + +# 4. Reading & Modifying Existing Spreadsheets from VFS + +```javascript +module.exports = (async () => { + const wb = new ExcelJS.Workbook(); + // Path is relative to VFS (e.g. user attachment or previous artifact) + await wb.xlsx.readFile('attachments/inventory.xlsx'); + + const ws = wb.getWorksheet(1); // 1-based index or name 'Sheet1' + const records = []; + + ws.eachRow({ includeEmpty: false }, (row, rowNumber) => { + if (rowNumber === 1) return; // skip header + records.push({ + sku: row.getCell(1).text, + name: row.getCell(2).text, + price: Number(row.getCell(3).value) || 0, + stock: Number(row.getCell(4).value) || 0 + }); + }); + + // You can modify cells and write back + ws.getCell('C2').value = 19.99; + await wb.xlsx.writeFile('attachments/inventory-updated.xlsx'); + + return { totalRead: records.length, records }; +})(); +``` + +--- + +# 5. Working with CSV Files + +```javascript +// Read CSV from VFS +const csvWb = new ExcelJS.Workbook(); +await csvWb.csv.readFile('data.csv'); +const sheet = csvWb.getWorksheet(1); + +// Write CSV to VFS +await csvWb.csv.writeFile('export.csv'); +``` + +--- + +# 6. Streaming Massive Datasets + +For large tables (>5,000 rows) use `WorkbookWriter` to prevent high memory consumption: + +```javascript +module.exports = (async () => { + const writer = new ExcelJS.stream.xlsx.WorkbookWriter({ filename: 'large-catalog.xlsx' }); + const ws = writer.addWorksheet('Catalog'); + + ws.columns = [ + { header: 'ID', key: 'id', width: 10 }, + { header: 'Name', key: 'name', width: 30 }, + { header: 'Stock', key: 'stock', width: 12 } + ]; + + for (let i = 1; i <= 10000; i++) { + ws.addRow({ id: i, name: `Product ${i}`, stock: Math.floor(Math.random() * 100) }).commit(); + } + + ws.commit(); + await writer.commit(); + + return { exportedRows: 10000, file: 'large-catalog.xlsx' }; +})(); +``` + +--- + +# 7. Embedding Product / Company Images + +```javascript +// Image filename is automatically resolved from VFS +const logoId = wb.addImage({ + filename: 'images/logo.png', + extension: 'png' +}); + +// Position image by cell coordinates +ws.addImage(logoId, { + tl: { col: 5, row: 1 }, + ext: { width: 120, height: 40 } +}); +``` + +--- + +# 8. Presenting Output to User + +When you generate or modify an Excel file in `javascript_execute`, **always include a clickable Markdown link in your response summary**: +- `[Download Articles Spreadsheet](articles-export.xlsx)` +- `[Download Updated Inventory](attachments/inventory-updated.xlsx)` diff --git a/src/defaultSkills/pdfkit.SKILL b/src/defaultSkills/pdfkit.SKILL new file mode 100644 index 0000000..e4ea25b --- /dev/null +++ b/src/defaultSkills/pdfkit.SKILL @@ -0,0 +1,274 @@ +PDFKit PDF Generation Guide: Documents, Styling, Layout, and VFS Integration + +# Goal +Provide actionable instructions, code patterns, and best practices for creating beautiful, production-ready PDF documents (invoices, reports, labels, catalogs) in the `javascript_execute` sandbox using `PDFKit`. + +# Environment & Globals +- `PDFDocument`, `PDFKit`, and `pdfkit` are pre-injected in global scope. Do NOT use `require()` or `import`. +- Instances of `PDFDocument` provide built-in VFS file writing: `await doc.toFile('path/to/file.pdf')` or `const buf = await doc.toBuffer()`. +- Standard stream piping also works: `doc.pipe(fs.createWriteStream('path.pdf'))`. +- Wrap async scripts in an IIFE: `module.exports = (async () => { ... })();`. + +--- + +# 1. Quick Start: Simple Document + +```javascript +module.exports = (async () => { + const doc = new PDFDocument({ size: 'A4', margin: 50 }); + + doc.fontSize(22) + .fillColor('#1F4E79') + .text('QuixPOS Monthly Summary', { align: 'center' }); + + doc.moveDown(); + doc.fontSize(12) + .fillColor('#333333') + .text('This report summarizes inventory levels and recent sales transactions.'); + + // Save directly to VFS + await doc.toFile('monthly-summary.pdf'); + + return { file: 'monthly-summary.pdf' }; +})(); +``` + +--- + +# 2. Typography, Colors & Standard Fonts + +PDFKit comes with 14 standard built-in fonts that require no external font files: +- **Helvetica**: `'Helvetica'`, `'Helvetica-Bold'`, `'Helvetica-Oblique'`, `'Helvetica-BoldOblique'` +- **Times**: `'Times-Roman'`, `'Times-Bold'`, `'Times-Italic'`, `'Times-BoldItalic'` +- **Courier**: `'Courier'`, `'Courier-Bold'`, `'Courier-Oblique'`, `'Courier-BoldOblique'` + +```javascript +// Header +doc.font('Helvetica-Bold') + .fontSize(20) + .fillColor('#1F4E79') + .text('INVOICE', 50, 50); + +// Subheaders & meta +doc.font('Helvetica') + .fontSize(10) + .fillColor('#555555') + .text('Invoice #: INV-2026-001', 50, 80) + .text('Date: 2026-10-06', 50, 95) + .text('Due Date: 2026-11-05', 50, 110); +``` + +--- + +# 3. Vector Shapes, Lines, and Boxes + +Coordinates in PDFKit use points (72 points = 1 inch). A4 dimensions: 595.28 x 841.89 points. + +```javascript +// Horizontal rule / separator +doc.moveTo(50, 135) + .lineTo(545, 135) + .strokeColor('#E5E7EB') + .lineWidth(1) + .stroke(); + +// Filled info box with rounded corners +doc.roundedRect(50, 150, 495, 60, 4) + .fillColor('#F9FAFB') + .fill(); + +doc.font('Helvetica-Bold').fontSize(10).fillColor('#1F4E79') + .text('Billed To:', 65, 160); +doc.font('Helvetica').fontSize(9).fillColor('#333333') + .text('Acme Retail GmbH\nIndustriestrasse 12, 10115 Berlin\nVAT ID: DE123456789', 65, 175); +``` + +--- + +# 4. Building Clean Data Tables + +```javascript +const tableTop = 230; +const colX = { + pos: 50, + sku: 80, + title: 170, + qty: 370, + price: 430, + total: 495 +}; + +// Table Header Background +doc.rect(50, tableTop, 495, 20) + .fillColor('#1F4E79') + .fill(); + +// Table Header Text +doc.font('Helvetica-Bold').fontSize(9).fillColor('#FFFFFF'); +doc.text('#', colX.pos + 5, tableTop + 5); +doc.text('SKU', colX.sku, tableTop + 5); +doc.text('Description', colX.title, tableTop + 5); +doc.text('Qty', colX.qty, tableTop + 5, { width: 50, align: 'right' }); +doc.text('Price', colX.price, tableTop + 5, { width: 55, align: 'right' }); +doc.text('Total', colX.total, tableTop + 5, { width: 50, align: 'right' }); + +// Table Rows +const items = [ + { sku: 'ART-101', title: 'Premium Espresso Roast 1kg', qty: 5, price: 18.50 }, + { sku: 'ART-102', title: 'Single Origin Filter Roast 250g', qty: 10, price: 7.20 }, + { sku: 'ART-103', title: 'Ceremonial Matcha Tin 100g', qty: 2, price: 24.00 } +]; + +let y = tableTop + 24; +items.forEach((item, index) => { + const lineTotal = item.qty * item.price; + + // Zebra striping + if (index % 2 === 1) { + doc.rect(50, y - 4, 495, 20).fillColor('#F9FAFB').fill(); + } + + doc.font('Helvetica').fontSize(9).fillColor('#333333'); + doc.text(String(index + 1), colX.pos + 5, y); + doc.text(item.sku, colX.sku, y); + doc.text(item.title, colX.title, y, { width: 190, ellipsis: true }); + doc.text(String(item.qty), colX.qty, y, { width: 50, align: 'right' }); + doc.text(`${item.price.toFixed(2)} €`, colX.price, y, { width: 55, align: 'right' }); + doc.text(`${lineTotal.toFixed(2)} €`, colX.total, y, { width: 50, align: 'right' }); + + // Underline + doc.moveTo(50, y + 16).lineTo(545, y + 16).strokeColor('#F3F4F6').lineWidth(0.5).stroke(); + y += 20; +}); + +// Summary block +y += 10; +doc.font('Helvetica-Bold').fontSize(10).fillColor('#1F4E79') + .text('Total Net:', 380, y, { width: 90, align: 'right' }) + .text('212.50 €', 480, y, { width: 65, align: 'right' }); + +doc.font('Helvetica').fontSize(9).fillColor('#555555') + .text('VAT (19%):', 380, y + 15, { width: 90, align: 'right' }) + .text('40.38 €', 480, y + 15, { width: 65, align: 'right' }); + +doc.font('Helvetica-Bold').fontSize(11).fillColor('#1F4E79') + .text('Grand Total:', 380, y + 32, { width: 90, align: 'right' }) + .text('252.88 €', 480, y + 32, { width: 65, align: 'right' }); +``` + +--- + +# 5. Embedding Images & Logos + +`doc.image()` accepts VFS relative paths (e.g. `'images/logo.png'`), virtual database images, or raw Buffers: + +```javascript +// Place company logo in top-right corner +try { + doc.image('images/logo.png', 425, 45, { width: 120 }); +} catch { + // Graceful fallback if image does not exist + doc.font('Helvetica-Bold').fontSize(14).fillColor('#1F4E79').text('QuixPOS', 450, 50); +} +``` + +--- + +# 6. Multi-page Documents & Page Numbers + +To add page numbers across all pages, use `{ bufferPages: true }`: + +```javascript +const doc = new PDFDocument({ size: 'A4', margin: 50, bufferPages: true }); + +// Page 1 content... +doc.text('First Page Content'); + +// Add Page 2 +doc.addPage(); +doc.text('Second Page Content'); + +// Add footer with page numbering to every page before closing +const range = doc.bufferedPageRange(); +for (let i = 0; i < range.count; i++) { + doc.switchToPage(i); + doc.font('Helvetica').fontSize(8).fillColor('#9CA3AF') + .text( + `Page ${i + 1} of ${range.count} • Generated by QuixPOS Assistant`, + 50, + 800, + { align: 'center', width: 495 } + ); +} + +await doc.toFile('multipage-report.pdf'); +``` + +--- + +# 7. Complete End-to-End Invoice Example + +```javascript +module.exports = (async () => { + const doc = new PDFDocument({ size: 'A4', margin: 50, bufferPages: true }); + + // Header + doc.font('Helvetica-Bold').fontSize(22).fillColor('#1F4E79').text('INVOICE', 50, 50); + doc.font('Helvetica').fontSize(9).fillColor('#6B7280') + .text('Invoice #: INV-2026-089', 50, 80) + .text(`Date: ${new Date().toISOString().slice(0, 10)}`, 50, 95); + + // Client box + doc.roundedRect(50, 125, 495, 55, 4).fillColor('#F9FAFB').fill(); + doc.font('Helvetica-Bold').fontSize(9).fillColor('#1F4E79').text('Customer:', 65, 135); + doc.font('Helvetica').fontSize(9).fillColor('#374151') + .text('Sample Store GmbH • Friedrichstrasse 45, 10117 Berlin', 65, 150); + + // Items table + let y = 205; + doc.rect(50, y, 495, 20).fillColor('#1F4E79').fill(); + doc.font('Helvetica-Bold').fontSize(9).fillColor('#FFFFFF') + .text('Description', 60, y + 5) + .text('Qty', 350, y + 5, { width: 50, align: 'right' }) + .text('Unit Price', 410, y + 5, { width: 60, align: 'right' }) + .text('Total', 480, y + 5, { width: 55, align: 'right' }); + + y += 24; + const items = [ + { desc: 'Robusta Espresso Blend 1kg', qty: 4, price: 15.00 }, + { desc: 'Single Origin Ethiopia Yirgacheffe 500g', qty: 2, price: 12.50 } + ]; + + let total = 0; + for (const item of items) { + const lineTotal = item.qty * item.price; + total += lineTotal; + doc.font('Helvetica').fontSize(9).fillColor('#1F2937') + .text(item.desc, 60, y) + .text(String(item.qty), 350, y, { width: 50, align: 'right' }) + .text(`${item.price.toFixed(2)} €`, 410, y, { width: 60, align: 'right' }) + .text(`${lineTotal.toFixed(2)} €`, 480, y, { width: 55, align: 'right' }); + y += 20; + } + + doc.moveTo(50, y + 5).lineTo(545, y + 5).strokeColor('#E5E7EB').stroke(); + y += 15; + doc.font('Helvetica-Bold').fontSize(11).fillColor('#1F4E79') + .text('Total (Gross):', 380, y, { width: 90, align: 'right' }) + .text(`${total.toFixed(2)} €`, 480, y, { width: 55, align: 'right' }); + + // Output to VFS + const filename = 'invoices/invoice-2026-089.pdf'; + await doc.toFile(filename); + + return { success: true, file: filename, total }; +})(); +``` + +--- + +# 8. Presenting Output to User + +When you generate a PDF in `javascript_execute`, **always include a clickable Markdown link in your response summary**: +- `[Download Invoice PDF](invoices/invoice-2026-089.pdf)` +- `[Download Monthly Report](monthly-summary.pdf)` diff --git a/src/services/conversationManager.js b/src/services/conversationManager.js index 08bda0e..3194c26 100644 --- a/src/services/conversationManager.js +++ b/src/services/conversationManager.js @@ -113,7 +113,7 @@ async function buildSystemPrompt(vfsRoot, googleAuthId) { 'YOUR TOOLS:', '1. dashback_request: HTTP requests (GET, POST, PUT, PATCH, DELETE) against QuixPOS Article Management System.', '2. ask_user: Prompt user for clarification or decisions. Pauses execution until response.', - '3. javascript_execute: Sandboxed Node environment for computation, aggregation, and binary I/O. Assign module.exports. API: fetch("/path"). vfs.', + '3. javascript_execute: Sandboxed Node environment for computation, aggregation, and binary I/O. Assign module.exports. API: fetch("/path"). vfs, sharp, ExcelJS.', '4. find_files: List VFS files. Set skills=true for SKILL files.', '5. read_file: Read VFS text file (seek/limit windowed).', '6. patch: Create, overwrite, or replace text in VFS files (only text write path; creates SKILL files).', @@ -131,7 +131,7 @@ async function buildSystemPrompt(vfsRoot, googleAuthId) { '- Persistent per-company VFS. Paths are root-relative forward-slash (e.g. "labels/sheet1.png").', '- Can host an entire static website (HTML, CSS, JS, images, etc.).', '- Text: list with find_files, read with read_file, write with patch.', - '- User attachments are saved to VFS (e.g. "attachments/...") and tagged in user messages as `[ATTACHMENT name= path= type=]`. Inspect attachments with appropriate tools: `read_file` (text/csv/json), `pdf_read` (PDFs), or `analyze_image` (images).', + '- User attachments are saved to VFS (e.g. "attachments/...") and tagged in user messages as `[ATTACHMENT name= path= type=]`. Inspect attachments with appropriate tools: `read_file` (text/csv/json), `pdf_read` (PDFs), `analyze_image` (images), or `javascript_execute` with `ExcelJS` (Excel spreadsheets).', '- Markdown links: Internet links are "https://..." or simply "hostname.com" / "hostname.com/filename". A pure relative path with no dotted hostname is always a VFS relative path (e.g. `[download](report.csv)` or `![preview](images/photo.png)`).', '', 'SKILL FILES:', diff --git a/src/tools/javascript-worker.js b/src/tools/javascript-worker.js index cc01112..a61dd83 100644 --- a/src/tools/javascript-worker.js +++ b/src/tools/javascript-worker.js @@ -6,6 +6,8 @@ import sharp from 'sharp'; import path from 'path'; import fsModule from 'fs'; import { validateObjects, validateObject } from 'react-canvas-label-editor'; +import ExcelJS from 'exceljs'; +import PDFDocument from 'pdfkit'; import { createVfs } from './vfs.js'; const { code, root, vmTimeout, bufferLimit, network, dashbackUrl, dashbackToken, vfsDir, googleAuthId } = workerData; @@ -198,6 +200,218 @@ function bridgeSharp(vfs) { return wrapped; } + +// ExcelJS reads and writes files via Node's `fs` when using `.readFile()` / `.writeFile()` / streaming. +// Injecting the raw library would allow escaping VFS or reading/writing in the server process CWD. +// We wrap `Workbook`, `stream.xlsx.WorkbookWriter`, and `stream.xlsx.WorkbookReader` so that file +// paths are resolved through the VFS containment rules, parent directories are created on write, +// and errors are properly sanitized without leaking host server paths. +function bridgeExcelJS(vfs) { + const sanitizePathError = (err, diskPath, virtualPath) => { + if (!err) return err; + if (typeof err.message === 'string' && diskPath) { + err.message = err.message.split(diskPath).join(virtualPath); + } + if (typeof err.stack === 'string' && diskPath) { + err.stack = err.stack.split(diskPath).join(virtualPath); + } + return err; + }; + + class BridgedWorkbook extends ExcelJS.Workbook { + get xlsx() { + const x = super.xlsx; + if (!x.__vfsBridged) { + const origRead = x.readFile; + const origWrite = x.writeFile; + x.readFile = async function readFile(filename, options) { + if (typeof filename !== 'string') { + return origRead.call(this, filename, options); + } + const diskPath = vfs.resolveReadPath(filename); + if (!fsModule.existsSync(diskPath)) { + throw new Error(`File not found: ${filename}`); + } + try { + return await origRead.call(this, diskPath, options); + } catch (err) { + throw sanitizePathError(err, diskPath, filename); + } + }; + x.writeFile = async function writeFile(filename, options) { + if (typeof filename !== 'string') { + return origWrite.call(this, filename, options); + } + const diskPath = vfs.resolveWritePath(filename); + fsModule.mkdirSync(path.dirname(diskPath), { recursive: true }); + try { + return await origWrite.call(this, diskPath, options); + } catch (err) { + throw sanitizePathError(err, diskPath, filename); + } + }; + x.__vfsBridged = true; + } + return x; + } + + get csv() { + const c = super.csv; + if (!c.__vfsBridged) { + const origRead = c.readFile; + const origWrite = c.writeFile; + c.readFile = async function readFile(filename, options) { + if (typeof filename !== 'string') { + return origRead.call(this, filename, options); + } + const diskPath = vfs.resolveReadPath(filename); + if (!fsModule.existsSync(diskPath)) { + throw new Error(`File not found: ${filename}`); + } + try { + return await origRead.call(this, diskPath, options); + } catch (err) { + throw sanitizePathError(err, diskPath, filename); + } + }; + c.writeFile = async function writeFile(filename, options) { + if (typeof filename !== 'string') { + return origWrite.call(this, filename, options); + } + const diskPath = vfs.resolveWritePath(filename); + fsModule.mkdirSync(path.dirname(diskPath), { recursive: true }); + try { + return await origWrite.call(this, diskPath, options); + } catch (err) { + throw sanitizePathError(err, diskPath, filename); + } + }; + c.__vfsBridged = true; + } + return c; + } + + addImage(image) { + if (image && typeof image.filename === 'string') { + const diskPath = vfs.resolveReadPath(image.filename); + return super.addImage({ ...image, filename: diskPath }); + } + return super.addImage(image); + } + } + + class BridgedWorkbookWriter extends ExcelJS.stream.xlsx.WorkbookWriter { + constructor(options = {}) { + let opts = options; + if (options && typeof options.filename === 'string') { + const diskPath = vfs.resolveWritePath(options.filename); + fsModule.mkdirSync(path.dirname(diskPath), { recursive: true }); + opts = { ...options, filename: diskPath }; + } + super(opts); + } + + addImage(image) { + if (image && typeof image.filename === 'string') { + const diskPath = vfs.resolveReadPath(image.filename); + return super.addImage({ ...image, filename: diskPath }); + } + return super.addImage(image); + } + } + + class BridgedWorkbookReader extends ExcelJS.stream.xlsx.WorkbookReader { + constructor(input, options) { + let resolvedInput = input; + if (typeof input === 'string') { + const diskPath = vfs.resolveReadPath(input); + if (!fsModule.existsSync(diskPath)) { + throw new Error(`File not found: ${input}`); + } + resolvedInput = diskPath; + } + super(resolvedInput, options); + } + } + + return { + ...ExcelJS, + Workbook: BridgedWorkbook, + stream: { + ...ExcelJS.stream, + xlsx: { + ...ExcelJS.stream?.xlsx, + WorkbookWriter: BridgedWorkbookWriter, + WorkbookReader: BridgedWorkbookReader, + }, + }, + }; +} + +// PDFKit generates PDF documents. In Node, it emits binary chunks as a readable stream +// or writes directly to `fs.createWriteStream`. It also loads images and custom fonts via `fs.readFileSync`. +// We wrap `PDFDocument` so: +// 1. `doc.image(src, ...)` and `doc.font(src, ...)` resolve files via VFS (supporting disk paths, +// hashes, and relative paths) and pass raw Buffers so PDFKit avoids uncontained host disk reads. +// 2. Provide `await doc.toFile(filename)` to directly stream and finalize to a VFS file. +// 3. Provide `await doc.toBuffer()` to finalize and return the full PDF Buffer. +// 4. Piping via `doc.pipe(fs.createWriteStream('out.pdf'))` continues to work seamlessly with VFS. +function bridgePDFKit(vfs) { + const STANDARD_FONTS = new Set([ + 'Courier', 'Courier-Bold', 'Courier-Oblique', 'Courier-BoldOblique', + 'Helvetica', 'Helvetica-Bold', 'Helvetica-Oblique', 'Helvetica-BoldOblique', + 'Times-Roman', 'Times-Bold', 'Times-Italic', 'Times-BoldItalic', + 'Symbol', 'ZapfDingbats', + ]); + + class BridgedPDFDocument extends PDFDocument { + font(src, family, size) { + if (typeof src === 'string' && !STANDARD_FONTS.has(src)) { + try { + const buf = vfs.readFileSync(src); + return super.font(buf, family, size); + } catch { + return super.font(src, family, size); + } + } + return super.font(src, family, size); + } + + image(src, ...args) { + if (typeof src === 'string') { + const buf = vfs.readFileSync(src); + return super.image(buf, ...args); + } + return super.image(src, ...args); + } + + async toBuffer() { + return new Promise((resolve, reject) => { + const chunks = []; + this.on('data', (chunk) => chunks.push(chunk)); + this.on('end', () => resolve(Buffer.concat(chunks))); + this.on('error', reject); + this.end(); + }); + } + + async toFile(virtualPath) { + const diskPath = vfs.resolveWritePath(virtualPath); + fsModule.mkdirSync(path.dirname(diskPath), { recursive: true }); + const writeStream = fsModule.createWriteStream(diskPath); + return new Promise((resolve, reject) => { + writeStream.on('finish', resolve); + writeStream.on('error', reject); + this.on('error', reject); + this.pipe(writeStream); + this.end(); + }); + } + } + + return BridgedPDFDocument; +} + const NETWORK_GLOBALS = { fetch: createSandboxFetch(dashbackUrl, dashbackToken), // WebCrypto globals (bridged across the vm2 realm boundary — see bridgeCrypto above). @@ -329,6 +543,9 @@ void (async () => { try { const vfs = createVfs(vfsDir, { googleAuthId }); + const bridgedExcel = bridgeExcelJS(vfs); + const bridgedPDF = bridgePDFKit(vfs); + const vm = new NodeVM({ console: 'off', sandbox: { @@ -336,6 +553,11 @@ try { fs: vfs, vfs, sharp: bridgeSharp(vfs), + ExcelJS: bridgedExcel, + exceljs: bridgedExcel, + PDFDocument: bridgedPDF, + PDFKit: bridgedPDF, + pdfkit: bridgedPDF, validateLabelObjects: (objects) => validateObjects(JSON.parse(JSON.stringify(objects))), validateLabelObject: (object) => validateObject(JSON.parse(JSON.stringify(object))), ...NETWORK_GLOBALS, diff --git a/src/tools/javascript.js b/src/tools/javascript.js index 283e44f..61745d8 100644 --- a/src/tools/javascript.js +++ b/src/tools/javascript.js @@ -126,7 +126,7 @@ export function createJavascriptExecuteTool({ googleAuthId }) { 'REST API: `await fetch("/path")` (authenticated). ' + 'VFS: directly use global `fs.readFileSync`/`writeFileSync` for binary artifacts/buffers/images (`image/` is read-only; paths must be relative paths, root is not allowed). VFS can host a static website (HTML, CSS, JS, images, etc.). ' + 'Embed generated images/links in markdown: `![alt](relative/path)`. Internet links use `https://...` or `hostname.com`/`hostname.com/filename`; a pure relative file with no dotted hostname is always a VFS relative path. Use find_files/read_file/patch for text. ' + - 'All APIs are pre-injected globals: `fs`/`vfs` (virtual filesystem), `sharp` (image processing), WebCrypto `crypto` (`subtle`, `randomUUID`), and standard web globals. Do NOT use `require()` or `import` (disabled; all necessary APIs are already in global scope).', + 'All APIs are pre-injected globals: `fs`/`vfs` (virtual filesystem), `sharp` (image processing), `ExcelJS`/`exceljs` (spreadsheet processing with VFS support for readFile/writeFile/writeBuffer/load), `PDFDocument`/`PDFKit`/`pdfkit` (PDF generation with VFS support for toFile/toBuffer/pipe), WebCrypto `crypto` (`subtle`, `randomUUID`), and standard web globals. Do NOT use `require()` or `import` (disabled; all necessary APIs are already in global scope).', strict: false, parameters: { type: 'object', diff --git a/src/tools/read-file.js b/src/tools/read-file.js index 994c173..28666ec 100644 --- a/src/tools/read-file.js +++ b/src/tools/read-file.js @@ -124,7 +124,9 @@ export function createReadFileTool({ googleAuthId }) { `"${clean}" is a binary file (${raw.length} bytes${ext ? `, ${ext}` : ''}) and cannot be read as text. ` + (['.png', '.jpg', '.jpeg', '.avif', '.webp', '.gif', '.bmp'].includes(ext) ? 'Use analyze_image({ path: ' + JSON.stringify(clean) + ' }) to have a vision model inspect it.' - : 'Fetch it as bytes from javascript_execute with fs.readFileSync if you need its contents.') + : (['.xlsx', '.xls', '.xlsm'].includes(ext) + ? 'Use javascript_execute with ExcelJS to inspect or modify spreadsheet files.' + : 'Fetch it as bytes from javascript_execute with fs.readFileSync if you need its contents.')) ); } diff --git a/src/tools/vfs.js b/src/tools/vfs.js index c1676e0..037d206 100644 --- a/src/tools/vfs.js +++ b/src/tools/vfs.js @@ -32,6 +32,10 @@ export function getMimeType(filePath) { case '.log': return 'text/plain; charset=utf-8'; case '.csv': return 'text/csv; charset=utf-8'; case '.tsv': return 'text/tab-separated-values; charset=utf-8'; + case '.xlsx': return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; + case '.xls': return 'application/vnd.ms-excel'; + case '.xlsm': return 'application/vnd.ms-excel.sheet.macroEnabled.12'; + case '.xltx': return 'application/vnd.openxmlformats-officedocument.spreadsheetml.template'; case '.md': case '.skill': return 'text/markdown; charset=utf-8'; case '.xml': return 'application/xml; charset=utf-8'; @@ -68,7 +72,7 @@ export function extractFullHash(p) { export function isReadOnlyDatabasePath(filePath) { if (typeof filePath !== 'string' || !filePath.trim()) return false; - const clean = filePath.trim().replace(/^([a-zA-Z]:|\/)+/, '').replace(/^\.\/+/, ''); + const clean = filePath.trim().replace(/^vfs:\/+/i, '').replace(/^([a-zA-Z]:|\/)+/, '').replace(/^\.\/+/, ''); const lower = clean.toLowerCase(); if (lower === 'image' || lower.startsWith('image/')) return true; if (extractFullHash(clean)) return true; @@ -186,6 +190,23 @@ export async function prepareVfs({ googleAuthId }) { try { const files = await store.modelvfs.list(googleAuthId); + const existingNames = new Set(files.map((f) => f.name)); + const defaultSkills = getDefaultSkills(); + for (const skill of defaultSkills) { + if (!existingNames.has(skill.name)) { + try { + await store.modelvfs.write(googleAuthId, { + name: skill.name, + mime: 'text/markdown', + content: Buffer.from(skill.content, 'utf8'), + }); + files.push({ name: skill.name }); + existingNames.add(skill.name); + } catch (err) { + console.warn(`[vfs] failed to write default skill "${skill.name}" to modelvfs:`, err.message); + } + } + } for (const file of files) { const diskPath = path.resolve(vfsRoot, file.name); if (!diskPath.startsWith(vfsRoot)) continue; @@ -276,10 +297,10 @@ export function createVfs(vfsRoot, options = {}) { }; const safeResolve = (p) => { - if (typeof p !== 'string' || !p.trim()) { - throw new Error('Path must be a non-empty string'); + if (typeof p !== 'string' || !p.trim()) { + throw new Error('Path must be a non-empty string'); } - const cleanVirtual = p.trim().replace(/^([a-zA-Z]:|\/)+/, ''); + const cleanVirtual = p.trim().replace(/^vfs:\/+/i, '').replace(/^([a-zA-Z]:|\/)+/, ''); const resolved = path.resolve(vfsRoot, cleanVirtual); const relative = path.relative(vfsRoot, resolved); if (relative.startsWith('..') || path.isAbsolute(relative)) { @@ -302,20 +323,44 @@ export function createVfs(vfsRoot, options = {}) { const vfs = { readFileSync: (p, opt) => { - const hash = extractFullHash(p); - if (hash) { - const buf = syncFetchImage(hash); - if (buf) { - const encoding = typeof opt === 'string' ? opt : opt?.encoding; - return encoding ? buf.toString(encoding) : buf; + const hash = extractFullHash(p); + if (hash) { + const buf = syncFetchImage(hash); + if (buf) { + const encoding = typeof opt === 'string' ? opt : opt?.encoding; + return encoding ? buf.toString(encoding) : buf; } - } - const diskPath = safeResolve(p); - try { + } + const diskPath = safeResolve(p); + try { return fs.readFileSync(diskPath, opt); - } catch (err) { - throw sanitizeFsError(err, p); - } + } catch (err) { + throw sanitizeFsError(err, p); + } + }, + + readFile: (p, opt, cb) => { + const callback = typeof opt === 'function' ? opt : cb; + const options = typeof opt === 'function' ? undefined : opt; + try { + const data = vfs.readFileSync(p, options); + if (typeof callback === 'function') process.nextTick(callback, null, data); + } catch (err) { + if (typeof callback === 'function') process.nextTick(callback, err); + else throw err; + } + }, + + createReadStream: (p, opt) => { + const diskPath = safeResolve(p); + if (!fs.existsSync(diskPath)) { + const err = new Error(`ENOENT: no such file or directory, open '${String(p)}'`); + err.code = 'ENOENT'; + err.path = String(p); + err.syscall = 'open'; + throw sanitizeFsError(err, p); + } + return fs.createReadStream(diskPath, opt); }, writeFileSync: (p, data, opt) => { @@ -329,6 +374,25 @@ export function createVfs(vfsRoot, options = {}) { } }, + writeFile: (p, data, opt, cb) => { + const callback = typeof opt === 'function' ? opt : cb; + const options = typeof opt === 'function' ? undefined : opt; + try { + vfs.writeFileSync(p, data, options); + if (typeof callback === 'function') process.nextTick(callback, null); + } catch (err) { + if (typeof callback === 'function') process.nextTick(callback, err); + else throw err; + } + }, + + createWriteStream: (p, opt) => { + assertNotReadOnlyDatabasePath(p); + const diskPath = safeResolve(p); + fs.mkdirSync(path.dirname(diskPath), { recursive: true }); + return fs.createWriteStream(diskPath, opt); + }, + appendFileSync: (p, data, opt) => { assertNotReadOnlyDatabasePath(p); const diskPath = safeResolve(p); @@ -436,6 +500,18 @@ export function createVfs(vfsRoot, options = {}) { assertNotReadOnlyDatabasePath(p); return safeResolve(p); }, + + /** + * Resolve a virtual path to an absolute on-disk path for reading, applying containment checks. + */ + resolveReadPath: (p) => { + return safeResolve(p); + }, + + /** + * Sanitize on-disk error paths back to virtual paths for display in sandbox transcripts. + */ + sanitizeError: (err, virtualPath) => sanitizeFsError(err, virtualPath), }; return vfs;