From 08728ada4db683c4e7e01ea98d1b62254a5d324d Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Mon, 19 Jan 2026 21:05:51 +0000 Subject: [PATCH] feat(docker-images): add vLLM-based Nanonets-OCR2-3B image, Qwen3-VL Ollama image and refactor build/docs/tests to use new runtime/layout --- ...> Dockerfile_minicpm45v_ollama_gpu_VRAM9GB | 0 ...r => Dockerfile_nanonets_vllm_gpu_VRAM10GB | 21 +- ... => Dockerfile_qwen3vl_ollama_gpu_VRAM20GB | 0 build-images.sh | 44 +- changelog.md | 11 + package.json | 4 +- pnpm-lock.yaml | 883 ++++-------------- readme.hints.md | 284 ++---- readme.md | 170 +--- test/helpers/docker.ts | 10 +- test/test.bankstatements.nanonets.ts | 30 +- test/test.invoices.extraction.ts | 436 +++++++++ test/test.invoices.failed.ts | 695 ++++++++++++++ test/test.invoices.nanonets.ts | 30 +- 14 files changed, 1492 insertions(+), 1126 deletions(-) rename Dockerfile_minicpm45v_gpu => Dockerfile_minicpm45v_ollama_gpu_VRAM9GB (100%) rename Dockerfile_nanonets_ocr => Dockerfile_nanonets_vllm_gpu_VRAM10GB (52%) rename Dockerfile_qwen3vl => Dockerfile_qwen3vl_ollama_gpu_VRAM20GB (100%) create mode 100644 test/test.invoices.extraction.ts create mode 100644 test/test.invoices.failed.ts diff --git a/Dockerfile_minicpm45v_gpu b/Dockerfile_minicpm45v_ollama_gpu_VRAM9GB similarity index 100% rename from Dockerfile_minicpm45v_gpu rename to Dockerfile_minicpm45v_ollama_gpu_VRAM9GB diff --git a/Dockerfile_nanonets_ocr b/Dockerfile_nanonets_vllm_gpu_VRAM10GB similarity index 52% rename from Dockerfile_nanonets_ocr rename to Dockerfile_nanonets_vllm_gpu_VRAM10GB index 01c378e..9ce61a6 100644 --- a/Dockerfile_nanonets_ocr +++ b/Dockerfile_nanonets_vllm_gpu_VRAM10GB @@ -1,21 +1,22 @@ -# Nanonets-OCR-s Vision Language Model -# Based on Qwen2.5-VL-3B, fine-tuned for document OCR -# ~8-10GB VRAM, outputs structured markdown with semantic tags +# Nanonets-OCR2-3B Vision Language Model +# Based on Qwen2.5-VL-3B, fine-tuned for document OCR (Oct 2025 release) +# Improvements over OCR-s: better semantic tagging, LaTeX equations, flowcharts +# ~12-16GB VRAM with 30K context, outputs structured markdown with semantic tags # -# Build: docker build -f Dockerfile_nanonets_ocr -t nanonets-ocr . +# Build: docker build -f Dockerfile_nanonets_vllm_gpu_VRAM10GB -t nanonets-ocr . # Run: docker run --gpus all -p 8000:8000 -v ht-huggingface-cache:/root/.cache/huggingface nanonets-ocr FROM vllm/vllm-openai:latest LABEL maintainer="Task Venture Capital GmbH " -LABEL description="Nanonets-OCR-s - Document OCR optimized Vision Language Model" +LABEL description="Nanonets-OCR2-3B - Document OCR optimized Vision Language Model" LABEL org.opencontainers.image.source="https://code.foss.global/host.today/ht-docker-ai" # Environment configuration -ENV MODEL_NAME="nanonets/Nanonets-OCR-s" +ENV MODEL_NAME="nanonets/Nanonets-OCR2-3B" ENV HOST="0.0.0.0" ENV PORT="8000" -ENV MAX_MODEL_LEN="8192" +ENV MAX_MODEL_LEN="30000" ENV GPU_MEMORY_UTILIZATION="0.9" # Expose OpenAI-compatible API port @@ -25,9 +26,9 @@ EXPOSE 8000 HEALTHCHECK --interval=30s --timeout=10s --start-period=120s --retries=5 \ CMD curl -f http://localhost:8000/health || exit 1 -# Start vLLM server with Nanonets-OCR-s model -CMD ["--model", "nanonets/Nanonets-OCR-s", \ +# Start vLLM server with Nanonets-OCR2-3B model +CMD ["--model", "nanonets/Nanonets-OCR2-3B", \ "--trust-remote-code", \ - "--max-model-len", "8192", \ + "--max-model-len", "30000", \ "--host", "0.0.0.0", \ "--port", "8000"] diff --git a/Dockerfile_qwen3vl b/Dockerfile_qwen3vl_ollama_gpu_VRAM20GB similarity index 100% rename from Dockerfile_qwen3vl rename to Dockerfile_qwen3vl_ollama_gpu_VRAM20GB diff --git a/build-images.sh b/build-images.sh index 08e27a6..021f7ec 100755 --- a/build-images.sh +++ b/build-images.sh @@ -13,46 +13,38 @@ NC='\033[0m' # No Color echo -e "${BLUE}Building ht-docker-ai images...${NC}" -# Build GPU variant +# Build MiniCPM-V 4.5 GPU variant echo -e "${GREEN}Building MiniCPM-V 4.5 GPU variant...${NC}" docker build \ - -f Dockerfile_minicpm45v_gpu \ + -f Dockerfile_minicpm45v_ollama_gpu_VRAM9GB \ -t ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:minicpm45v \ -t ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:minicpm45v-gpu \ -t ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:latest \ . -# Build CPU variant -echo -e "${GREEN}Building MiniCPM-V 4.5 CPU variant...${NC}" +# Build Qwen3-VL GPU variant +echo -e "${GREEN}Building Qwen3-VL-30B-A3B GPU variant...${NC}" docker build \ - -f Dockerfile_minicpm45v_cpu \ - -t ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:minicpm45v-cpu \ + -f Dockerfile_qwen3vl_ollama_gpu_VRAM20GB \ + -t ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:qwen3vl \ . -# Build PaddleOCR-VL GPU variant -echo -e "${GREEN}Building PaddleOCR-VL GPU variant...${NC}" +# Build Nanonets-OCR GPU variant +echo -e "${GREEN}Building Nanonets-OCR-s GPU variant...${NC}" docker build \ - -f Dockerfile_paddleocr_vl_gpu \ - -t ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:paddleocr-vl \ - -t ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:paddleocr-vl-gpu \ - . - -# Build PaddleOCR-VL CPU variant -echo -e "${GREEN}Building PaddleOCR-VL CPU variant...${NC}" -docker build \ - -f Dockerfile_paddleocr_vl_cpu \ - -t ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:paddleocr-vl-cpu \ + -f Dockerfile_nanonets_vllm_gpu_VRAM10GB \ + -t ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:nanonets-ocr \ . echo -e "${GREEN}All images built successfully!${NC}" echo "" echo "Available images:" -echo " MiniCPM-V 4.5:" -echo " - ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:minicpm45v (GPU)" -echo " - ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:minicpm45v-cpu (CPU)" -echo " - ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:latest (GPU)" +echo " MiniCPM-V 4.5 (Ollama, ~9GB VRAM):" +echo " - ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:minicpm45v" +echo " - ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:latest" echo "" -echo " PaddleOCR-VL (Vision-Language Model):" -echo " - ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:paddleocr-vl (GPU/vLLM)" -echo " - ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:paddleocr-vl-gpu (GPU/vLLM)" -echo " - ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:paddleocr-vl-cpu (CPU)" +echo " Qwen3-VL-30B-A3B (Ollama, ~20GB VRAM):" +echo " - ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:qwen3vl" +echo "" +echo " Nanonets-OCR-s (vLLM, ~10GB VRAM):" +echo " - ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:nanonets-ocr" diff --git a/changelog.md b/changelog.md index 4d4471a..7e6996b 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,16 @@ # Changelog +## 2026-01-19 - 1.14.0 - feat(docker-images) +add vLLM-based Nanonets-OCR2-3B image, Qwen3-VL Ollama image and refactor build/docs/tests to use new runtime/layout + +- Add new Dockerfiles for Nanonets (Dockerfile_nanonets_vllm_gpu_VRAM10GB), Qwen3 (Dockerfile_qwen3vl_ollama_gpu_VRAM20GB) and a clarified MiniCPM Ollama variant (Dockerfile_minicpm45v_ollama_gpu_VRAM9GB); remove older, redundant Dockerfiles. +- Update build-images.sh to build the new image tags (minicpm45v, qwen3vl, nanonets-ocr) and adjust messaging/targets accordingly. +- Documentation overhaul: readme.md and readme.hints.md updated to reflect vLLM vs Ollama runtimes, corrected ports/VRAM estimates, volume recommendations, and API endpoint details. +- Tests updated to target the new model ID (nanonets/Nanonets-OCR2-3B), to process one page per batch, and to include a 10-minute AbortSignal timeout for OCR requests. +- Added focused extraction test suites (test/test.invoices.extraction.ts and test/test.invoices.failed.ts) for faster iteration and debugging of invoice extraction. +- Bump devDependencies: @git.zone/tsrun -> ^2.0.1 and @git.zone/tstest -> ^3.1.5. +- Misc: test helper references and docker compose/test port mapping fixed (nanonets uses 8000), and various README sections cleaned and reorganized. + ## 2026-01-18 - 1.13.2 - fix(tests) stabilize OCR extraction tests and manage GPU containers diff --git a/package.json b/package.json index c931d0b..2ab6876 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ "test": "tstest test/ --verbose" }, "devDependencies": { - "@git.zone/tsrun": "^1.3.3", - "@git.zone/tstest": "^1.0.90" + "@git.zone/tsrun": "^2.0.1", + "@git.zone/tstest": "^3.1.5" }, "repository": { "type": "git", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2b1eeac..19561b7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,11 +13,11 @@ importers: version: 25.0.9 devDependencies: '@git.zone/tsrun': - specifier: ^1.3.3 - version: 1.6.2 + specifier: ^2.0.1 + version: 2.0.1 '@git.zone/tstest': - specifier: ^1.0.90 - version: 1.11.5(socks@2.8.7)(typescript@5.9.3) + specifier: ^3.1.5 + version: 3.1.5(socks@2.8.7)(typescript@5.9.3) packages: @@ -64,12 +64,12 @@ packages: '@aws-crypto/util@5.2.0': resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} - '@aws-sdk/client-s3@3.970.0': - resolution: {integrity: sha512-mDC792KkFzLZG9PS1Fv9b18lEzmSNBjAdweLJ83D2CZu6ved9+Pr/Dr+FRs0kSxqY+sUUUuIBmvDYHXY8E8EzA==} + '@aws-sdk/client-s3@3.971.0': + resolution: {integrity: sha512-BBUne390fKa4C4QvZlUZ5gKcu+Uyid4IyQ20N4jl0vS7SK2xpfXlJcgKqPW5ts6kx6hWTQBk6sH5Lf12RvuJxg==} engines: {node: '>=20.0.0'} - '@aws-sdk/client-sso@3.970.0': - resolution: {integrity: sha512-ArmgnOsSCXN5VyIvZb4kSP5hpqlRRHolrMtKQ/0N8Hw4MTb7/IeYHSZzVPNzzkuX6gn5Aj8txoUnDPM8O7pc9g==} + '@aws-sdk/client-sso@3.971.0': + resolution: {integrity: sha512-Xx+w6DQqJxDdymYyIxyKJnRzPvVJ4e/Aw0czO7aC9L/iraaV7AG8QtRe93OGW6aoHSh72CIiinnpJJfLsQqP4g==} engines: {node: '>=20.0.0'} '@aws-sdk/core@3.970.0': @@ -88,28 +88,28 @@ packages: resolution: {integrity: sha512-CjDbWL7JxjLc9ZxQilMusWSw05yRvUJKRpz59IxDpWUnSMHC9JMMUUkOy5Izk8UAtzi6gupRWArp4NG4labt9Q==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-ini@3.970.0': - resolution: {integrity: sha512-L5R1hN1FY/xCmH65DOYMXl8zqCFiAq0bAq8tJZU32mGjIl1GzGeOkeDa9c461d81o7gsQeYzXyqFD3vXEbJ+kQ==} + '@aws-sdk/credential-provider-ini@3.971.0': + resolution: {integrity: sha512-c0TGJG4xyfTZz3SInXfGU8i5iOFRrLmy4Bo7lMyH+IpngohYMYGYl61omXqf2zdwMbDv+YJ9AviQTcCaEUKi8w==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-login@3.970.0': - resolution: {integrity: sha512-C+1dcLr+p2E+9hbHyvrQTZ46Kj4vC2RoP6N935GEukHQa637ZjXs8VlyHJ2xTvbvwwLZQNiu56Cx7o/OFOqw1A==} + '@aws-sdk/credential-provider-login@3.971.0': + resolution: {integrity: sha512-yhbzmDOsk0RXD3rTPhZra4AWVnVAC4nFWbTp+sUty1hrOPurUmhuz8bjpLqYTHGnlMbJp+UqkQONhS2+2LzW2g==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-node@3.970.0': - resolution: {integrity: sha512-nMM0eeVuiLtw1taLRQ+H/H5Qp11rva8ILrzAQXSvlbDeVmbc7d8EeW5Q2xnCJu+3U+2JNZ1uxqIL22pB2sLEMA==} + '@aws-sdk/credential-provider-node@3.971.0': + resolution: {integrity: sha512-epUJBAKivtJqalnEBRsYIULKYV063o/5mXNJshZfyvkAgNIzc27CmmKRXTN4zaNOZg8g/UprFp25BGsi19x3nQ==} engines: {node: '>=20.0.0'} '@aws-sdk/credential-provider-process@3.970.0': resolution: {integrity: sha512-0XeT8OaT9iMA62DFV9+m6mZfJhrD0WNKf4IvsIpj2Z7XbaYfz3CoDDvNoALf3rPY9NzyMHgDxOspmqdvXP00mw==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-sso@3.970.0': - resolution: {integrity: sha512-ROb+Aijw8nzkB14Nh2XRH861++SeTZykUzk427y8YtgTLxjAOjgDTchDUFW2Fx6GFWkSjqJ3sY7SZyb33IqyFw==} + '@aws-sdk/credential-provider-sso@3.971.0': + resolution: {integrity: sha512-dY0hMQ7dLVPQNJ8GyqXADxa9w5wNfmukgQniLxGVn+dMRx3YLViMp5ZpTSQpFhCWNF0oKQrYAI5cHhUJU1hETw==} engines: {node: '>=20.0.0'} - '@aws-sdk/credential-provider-web-identity@3.970.0': - resolution: {integrity: sha512-r7tnYJJg+B6QvnsRHSW5vDol+ks6n+5jBZdCFdGyK63hjcMRMqHx59zEH8O47UR1PFv5hS2Q3uGz6HXvVtP40Q==} + '@aws-sdk/credential-provider-web-identity@3.971.0': + resolution: {integrity: sha512-F1AwfNLr7H52T640LNON/h34YDiMuIqW/ZreGzhRR6vnFGaSPtNSKAKB2ssAMkLM8EVg8MjEAYD3NCUiEo+t/w==} engines: {node: '>=20.0.0'} '@aws-sdk/middleware-bucket-endpoint@3.969.0': @@ -120,8 +120,8 @@ packages: resolution: {integrity: sha512-qXygzSi8osok7tH9oeuS3HoKw6jRfbvg5Me/X5RlHOvSSqQz8c5O9f3MjUApaCUSwbAU92KrbZWasw2PKiaVHg==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-flexible-checksums@3.970.0': - resolution: {integrity: sha512-mlKLwX0jWa5EwIvMjJAvVFL/zLAxB/fNLOg4hQCNCUf1qi+XxD+brDopXNPWeA8bSCnpvWfZrQd5yNksG6Fzqg==} + '@aws-sdk/middleware-flexible-checksums@3.971.0': + resolution: {integrity: sha512-+hGUDUxeIw8s2kkjfeXym0XZxdh0cqkHkDpEanWYdS1gnWkIR+gf9u/DKbKqGHXILPaqHXhWpLTQTVlaB4sI7Q==} engines: {node: '>=20.0.0'} '@aws-sdk/middleware-host-header@3.969.0': @@ -144,16 +144,16 @@ packages: resolution: {integrity: sha512-v/Y5F1lbFFY7vMeG5yYxuhnn0CAshz6KMxkz1pDyPxejNE9HtA0w8R6OTBh/bVdIm44QpjhbI7qeLdOE/PLzXQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/middleware-ssec@3.969.0': - resolution: {integrity: sha512-9wUYtd5ye4exygKHyl02lPVHUoAFlxxXoqvlw7u2sycfkK6uHLlwdsPru3MkMwj47ZSZs+lkyP/sVKXVMhuaAg==} + '@aws-sdk/middleware-ssec@3.971.0': + resolution: {integrity: sha512-QGVhvRveYG64ZhnS/b971PxXM6N2NU79Fxck4EfQ7am8v1Br0ctoeDDAn9nXNblLGw87we9Z65F7hMxxiFHd3w==} engines: {node: '>=20.0.0'} '@aws-sdk/middleware-user-agent@3.970.0': resolution: {integrity: sha512-dnSJGGUGSFGEX2NzvjwSefH+hmZQ347AwbLhAsi0cdnISSge+pcGfOFrJt2XfBIypwFe27chQhlfuf/gWdzpZg==} engines: {node: '>=20.0.0'} - '@aws-sdk/nested-clients@3.970.0': - resolution: {integrity: sha512-RIl8s4DCa31MXtRFw23iU90OqEoWuwQxiZOZshzsPtjyrunhHFjyZJEqb+vuQcYd1o22SMaYa3lPJRp64OH35Q==} + '@aws-sdk/nested-clients@3.971.0': + resolution: {integrity: sha512-TWaILL8GyYlhGrxxnmbkazM4QsXatwQgoWUvo251FXmUOsiXDFDVX3hoGIfB3CaJhV2pJPfebHUNJtY6TjZ11g==} engines: {node: '>=20.0.0'} '@aws-sdk/region-config-resolver@3.969.0': @@ -164,8 +164,8 @@ packages: resolution: {integrity: sha512-z3syXfuK/x/IsKf/AeYmgc2NT7fcJ+3fHaGO+fkghkV9WEba3fPyOwtTBX4KpFMNb2t50zDGZwbzW1/5ighcUQ==} engines: {node: '>=20.0.0'} - '@aws-sdk/token-providers@3.970.0': - resolution: {integrity: sha512-YO8KgJecxHIFMhfoP880q51VXFL9V1ELywK5yzVEqzyrwqoG93IUmnTygBUylQrfkbH+QqS0FxEdgwpP3fcwoQ==} + '@aws-sdk/token-providers@3.971.0': + resolution: {integrity: sha512-4hKGWZbmuDdONMJV0HJ+9jwTDb0zLfKxcCLx2GEnBY31Gt9GeyIQ+DZ97Bb++0voawj6pnZToFikXTyrEq2x+w==} engines: {node: '>=20.0.0'} '@aws-sdk/types@3.969.0': @@ -187,8 +187,8 @@ packages: '@aws-sdk/util-user-agent-browser@3.969.0': resolution: {integrity: sha512-bpJGjuKmFr0rA6UKUCmN8D19HQFMLXMx5hKBXqBlPFdalMhxJSjcxzX9DbQh0Fn6bJtxCguFmRGOBdQqNOt49g==} - '@aws-sdk/util-user-agent-node@3.970.0': - resolution: {integrity: sha512-TNQpwIVD6SxMwkD+QKnaujKVyXy5ljN3O3jrI7nCHJ3GlJu5xJrd8yuBnanYCcrn3e2zwdfOh4d4zJAZvvIvVw==} + '@aws-sdk/util-user-agent-node@3.971.0': + resolution: {integrity: sha512-Eygjo9mFzQYjbGY3MYO6CsIhnTwAMd3WmuFalCykqEmj2r5zf0leWrhPaqvA5P68V5JdGfPYgj7vhNOd6CtRBQ==} engines: {node: '>=20.0.0'} peerDependencies: aws-crt: '>=1.0.0' @@ -219,19 +219,12 @@ packages: '@borewit/text-codec@0.2.1': resolution: {integrity: sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==} - '@cloudflare/workers-types@4.20260116.0': - resolution: {integrity: sha512-iMZIQDco7ARzzH+r8j90757kbPKEetKY3/6V5UurOzS6T1GJ+rsREw5i7vlBA4XjFV5UMaPtUD6HuUFSMLcxPQ==} - - '@colors/colors@1.6.0': - resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} - engines: {node: '>=0.1.90'} + '@cloudflare/workers-types@4.20260118.0': + resolution: {integrity: sha512-t+2Q421kAQqwBzMUDvgg2flp8zFVxOpiAyZPbyNcnPxMDHf0z3B7LqBIVQawwI6ntZinbk9f4oUmaA5bGeYwlg==} '@configvault.io/interfaces@1.0.17': resolution: {integrity: sha512-bEcCUR2VBDJsTin8HQh8Uw/mlYl2v8A3jMIaQ+MTB9Hrqd6CZL2dL7iJdWyFl/3EIX+LDxWFR+Oq7liIq7w+1Q==} - '@dabh/diagnostics@2.0.8': - resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==} - '@design.estate/dees-comms@1.0.30': resolution: {integrity: sha512-KchMlklJfKAjQiJiR0xmofXtQ27VgZtBIxcMwPE9d+h3jJRv+lPZxzBQVOM0eyM0uS44S5vJMZ11IeV4uDXSHg==} @@ -410,12 +403,12 @@ packages: resolution: {integrity: sha512-K4HzZmJ3wrR+sO5sZwcVcSEHxJiLj71bv7J6GRmGoex8sXj6T2QrpkeCshsnbMan8OCuwOKtzyVrBDXt36chHw==} hasBin: true - '@git.zone/tsrun@1.6.2': - resolution: {integrity: sha512-SOHbQqBg3/769/jPQcdpPCmugdEtIJINiG0O6aWx+su91GvGhheha5dAhccsCutJYErr+aJcBqBYuUYfhOfkFQ==} + '@git.zone/tsrun@2.0.1': + resolution: {integrity: sha512-NEcnsjvlC1o3Z6SS3VhKCf6Ev+Sh4EAinmggslrIR/ppMrvjDbXNFXoyr3PB+GLeSAR0JRZ1fGvVYjpEzjBdIg==} hasBin: true - '@git.zone/tstest@1.11.5': - resolution: {integrity: sha512-7YHFNGMjUd3WOFXi0DlUieQcdxzwYqxL7n2XDE7SOUd8XpMxVsGsY2SuwBKXlbT10By/H3thQTsy+Hjy9ahGWA==} + '@git.zone/tstest@3.1.5': + resolution: {integrity: sha512-gXDFaZczq3ulwJVB4RN9wjV5bbF5cdibjuVesncYvhQtEO6zf6AwLHeN07atIgy18iBJgpvsCEwLGsO5XrtiIg==} hasBin: true '@happy-dom/global-registrator@15.11.7': @@ -490,10 +483,6 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - '@koa/router@9.4.0': - resolution: {integrity: sha512-dOOXgzqaDoHu5qqMEPLKEgLz5CeIA7q8+1W62mCvFVCOqeC71UoTGJ4u1xUSOpIl2J1x2pqrNULkFteUeZW3/A==} - engines: {node: '>= 8.0.0'} - '@leichtgewicht/ip-codec@2.0.5': resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==} @@ -524,8 +513,8 @@ packages: '@module-federation/webpack-bundler-runtime@0.22.0': resolution: {integrity: sha512-aM8gCqXu+/4wBmJtVeMeeMN5guw3chf+2i6HajKtQv7SJfxV/f4IyNQJUeUQu9HfiAZHjqtMV5Lvq/Lvh8LdyA==} - '@mongodb-js/saslprep@1.4.4': - resolution: {integrity: sha512-p7X/ytJDIdwUfFL/CLOhKgdfJe1Fa8uw9seJYvdOmnP9JBWGWHW69HkOixXS6Wy9yvGf1MbhcS6lVmrhy4jm2g==} + '@mongodb-js/saslprep@1.4.5': + resolution: {integrity: sha512-k64Lbyb7ycCSXHSLzxVdb2xsKGPMvYZfCICXvDsI8Z65CeWQzTEKS4YmGbnqw+U9RBvLPTsB6UCmwkgsDTGWIw==} '@napi-rs/wasm-runtime@1.0.7': resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==} @@ -608,8 +597,8 @@ packages: '@push.rocks/smartbrowser@2.0.8': resolution: {integrity: sha512-0KWRZj3TuKo/sNwgPbiSE6WL+TMeR19t1JmXBZWh9n8iA2mpc4HhMrQAndEUdRCkx5ofSaHWojIRVFzGChj0Dg==} - '@push.rocks/smartbucket@3.3.10': - resolution: {integrity: sha512-0H2MioALspC8Aj0Q1FPCs2w4k2u9oJg7Q5yM8+1TZo7aRfrdxgM5HQ7z3apUaqC3ZEDewW6vSlttjHFHhMEC3A==} + '@push.rocks/smartbucket@4.3.0': + resolution: {integrity: sha512-4nstzEduCKou4R5ekKH6kUjDZXWfrtjA1hIQ4MJmTbtncmm2+4+ixjaFThS2nS8Aa+fHcBgOtKkBv8wTsgvK/Q==} '@push.rocks/smartbuffer@3.0.5': resolution: {integrity: sha512-pWYF08Mn8s/KF/9nHRk7pZPzuMjmYVQay2c5gGexdayxn1W4eCSYYhWH73vR2JBfGeGq/izbRNuUuEaIEeTIKA==} @@ -663,6 +652,9 @@ packages: '@push.rocks/smartfile@11.2.7': resolution: {integrity: sha512-8Yp7/sAgPpWJBHohV92ogHWKzRomI5MEbSG6b5W2n18tqwfAmjMed0rQvsvGrSBlnEWCKgoOrYIIZbLO61+J0Q==} + '@push.rocks/smartfile@13.1.2': + resolution: {integrity: sha512-DaEhwmnGEpX4coeeToaw4cZe3pNBhH7CY1iGr+d3pIXihozREvzzAR9/0i2r7bUXXL5+Lgy8YYIk5ZS+fwxMKA==} + '@push.rocks/smartfs@1.3.1': resolution: {integrity: sha512-ZSduVS8tM+/erbyCTvRRvc9gLWwbpqN5xdIIkMr+gub7fowSeJb7tR2rnGwySa63DyimU0q2KTp79VV9YqGLeg==} @@ -741,14 +733,17 @@ packages: '@push.rocks/smartrequest@4.4.2': resolution: {integrity: sha512-Om4y1Ce4YdSu8VoXREz2SgFz9pDxcFEm0+SC1YYa3RXd0AH2Mknaj/1XfvfMqojnK9L7N2z1fY4xX8tO1IwqFQ==} + '@push.rocks/smartrequest@5.0.1': + resolution: {integrity: sha512-gZQQF6HVt3LwTBxaPh6hHObd4VF76PUYQcs5pHD7f0VXaEewmrNAQSnccoinOY7fi45+0dOf04PJOXu9MibPzQ==} + '@push.rocks/smartrouter@1.3.3': resolution: {integrity: sha512-1+xZEnWlhzqLWAaJ1zFNhQ0zgbfCWQl1DBT72LygLxTs+P0K8AwJKgqo/IX6CT55kGCFnPAZIYSbVJlGsgrB0w==} '@push.rocks/smartrx@3.0.10': resolution: {integrity: sha512-USjIYcsSfzn14cwOsxgq/bBmWDTTzy3ouWAnW5NdMyRRzEbmeNrvmy6TRqNeDlJ2PsYNTt1rr/zGUqvIy72ITg==} - '@push.rocks/smarts3@2.2.7': - resolution: {integrity: sha512-9ZXGMlmUL2Wd+YJO0xOB8KyqPf4V++fWJvTq4s76bnqEuaCr9OLfq6czhban+i4cD3ZdIjehfuHqctzjuLw8Jw==} + '@push.rocks/smarts3@3.0.3': + resolution: {integrity: sha512-Y9nXMwurthJ9Z7yi0RwjhPFUC58aY8Mhia8kFo6Xj1tBM4LE8Oxg/ydejF7otHqQGr3QyqV5C4YrDEG17rUuzg==} '@push.rocks/smartshell@3.3.0': resolution: {integrity: sha512-m0w618H6YBs+vXGz1CgS4nPi5CUAnqRtckcS9/koGwfcIx1IpjqmiP47BoCTbdgcv0IPUxQVBG1IXTHPuZ8Z5g==} @@ -1008,8 +1003,8 @@ packages: resolution: {integrity: sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ==} engines: {node: '>=18.0.0'} - '@smithy/core@3.20.6': - resolution: {integrity: sha512-BpAffW1mIyRZongoKBbh3RgHG+JDHJek/8hjA/9LnPunM+ejorO6axkxCgwxCe4K//g/JdPeR9vROHDYr/hfnQ==} + '@smithy/core@3.20.7': + resolution: {integrity: sha512-aO7jmh3CtrmPsIJxUwYIzI5WVlMK8BMCPQ4D4nTzqTqBhbzvxHNzBMGcEg13yg/z9R2Qsz49NUFl0F0lVbTVFw==} engines: {node: '>=18.0.0'} '@smithy/credential-provider-imds@4.2.8': @@ -1072,12 +1067,12 @@ packages: resolution: {integrity: sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A==} engines: {node: '>=18.0.0'} - '@smithy/middleware-endpoint@4.4.7': - resolution: {integrity: sha512-SCmhUG1UwtnEhF5Sxd8qk7bJwkj1BpFzFlHkXqKCEmDPLrRjJyTGM0EhqT7XBtDaDJjCfjRJQodgZcKDR843qg==} + '@smithy/middleware-endpoint@4.4.8': + resolution: {integrity: sha512-TV44qwB/T0OMMzjIuI+JeS0ort3bvlPJ8XIH0MSlGADraXpZqmyND27ueuAL3E14optleADWqtd7dUgc2w+qhQ==} engines: {node: '>=18.0.0'} - '@smithy/middleware-retry@4.4.23': - resolution: {integrity: sha512-lLEmkQj7I7oKfvZ1wsnToGJouLOtfkMXDKRA1Hi6F+mMp5O1N8GcVWmVeNgTtgZtd0OTXDTI2vpVQmeutydGew==} + '@smithy/middleware-retry@4.4.24': + resolution: {integrity: sha512-yiUY1UvnbUFfP5izoKLtfxDSTRv724YRRwyiC/5HYY6vdsVDcDOXKSXmkJl/Hovcxt5r+8tZEUAdrOaCJwrl9Q==} engines: {node: '>=18.0.0'} '@smithy/middleware-serde@4.2.9': @@ -1124,8 +1119,8 @@ packages: resolution: {integrity: sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg==} engines: {node: '>=18.0.0'} - '@smithy/smithy-client@4.10.8': - resolution: {integrity: sha512-wcr3UEL26k7lLoyf9eVDZoD1nNY3Fa1gbNuOXvfxvVWLGkOVW+RYZgUUp/bXHryJfycIOQnBq9o1JAE00ax8HQ==} + '@smithy/smithy-client@4.10.9': + resolution: {integrity: sha512-Je0EvGXVJ0Vrrr2lsubq43JGRIluJ/hX17aN/W/A0WfE+JpoMdI8kwk2t9F0zTX9232sJDGcoH4zZre6m6f/sg==} engines: {node: '>=18.0.0'} '@smithy/types@4.12.0': @@ -1160,12 +1155,12 @@ packages: resolution: {integrity: sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==} engines: {node: '>=18.0.0'} - '@smithy/util-defaults-mode-browser@4.3.22': - resolution: {integrity: sha512-O2WXr6ZRqPnbyoepb7pKcLt1QL6uRfFzGYJ9sGb5hMJQi7v/4RjRmCQa9mNjA0YiXqsc5lBmLXqJPhjM1Vjv5A==} + '@smithy/util-defaults-mode-browser@4.3.23': + resolution: {integrity: sha512-mMg+r/qDfjfF/0psMbV4zd7F/i+rpyp7Hjh0Wry7eY15UnzTEId+xmQTGDU8IdZtDfbGQxuWNfgBZKBj+WuYbA==} engines: {node: '>=18.0.0'} - '@smithy/util-defaults-mode-node@4.2.25': - resolution: {integrity: sha512-7uMhppVNRbgNIpyUBVRfjGHxygP85wpXalRvn9DvUlCx4qgy1AB/uxOPSiDx/jFyrwD3/BypQhx1JK7f3yxrAw==} + '@smithy/util-defaults-mode-node@4.2.26': + resolution: {integrity: sha512-EQqe/WkbCinah0h1lMWh9ICl0Ob4lyl20/10WTB35SC9vDQfD8zWsOT+x2FIOXKAoZQ8z/y0EFMoodbcqWJY/w==} engines: {node: '>=18.0.0'} '@smithy/util-endpoints@3.2.8': @@ -1208,9 +1203,6 @@ packages: resolution: {integrity: sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==} engines: {node: '>=18.0.0'} - '@so-ric/colorspace@1.1.6': - resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==} - '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -1339,9 +1331,6 @@ packages: '@types/relateurl@0.2.33': resolution: {integrity: sha512-bTQCKsVbIdzLqZhLkF5fcJQreE4y1ro4DIyVrlDNSCJRRwHhB8Z+4zXXa8jN6eDvc2HbRsEYgbvrnGvi54EpSw==} - '@types/s3rver@3.7.4': - resolution: {integrity: sha512-CMCmdNszxS2FsIznWvBMVCl6fpvr5ueaFCaY0iSoH7Ud5maGcLghukpDvsXBnIcp92cv2HeVnVqI1p8yPcab9Q==} - '@types/send@1.2.1': resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} @@ -1354,9 +1343,6 @@ packages: '@types/through2@2.0.41': resolution: {integrity: sha512-ryQ0tidWkb1O1JuYvWKyMLYEtOWDqF5mHerJzKz/gQpoAaJq2l/dsMPBF0B5BNVT34rbARYJ5/tsZwLfUi2kwQ==} - '@types/triple-beam@1.3.5': - resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} - '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -1429,10 +1415,6 @@ packages: resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} - ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -1458,9 +1440,6 @@ packages: async-mutex@0.5.0: resolution: {integrity: sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==} - async@3.2.6: - resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} - asynckit@0.4.0: resolution: {integrity: sha1-x57Zf380y48robyXkLzDZkdLS3k=} @@ -1565,18 +1544,10 @@ packages: buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - busboy@0.3.1: - resolution: {integrity: sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==} - engines: {node: '>=4.5.0'} - bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} - cache-content-type@1.0.1: - resolution: {integrity: sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==} - engines: {node: '>= 6.0.0'} - call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1603,10 +1574,6 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - character-entities-html4@2.1.0: resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} @@ -1644,39 +1611,13 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} - co@4.6.0: - resolution: {integrity: sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=} - engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - - color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - color-convert@3.1.3: - resolution: {integrity: sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==} - engines: {node: '>=14.6'} - - color-name@1.1.3: - resolution: {integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=} - color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - color-name@2.1.0: - resolution: {integrity: sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==} - engines: {node: '>=12.20'} - - color-string@2.1.4: - resolution: {integrity: sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==} - engines: {node: '>=18'} - - color@5.0.3: - resolution: {integrity: sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==} - engines: {node: '>=18'} - combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -1687,20 +1628,12 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - commander@5.1.0: - resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} - engines: {node: '>= 6'} - commondir@1.0.1: resolution: {integrity: sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=} concat-map@0.0.1: resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} - content-disposition@0.5.4: - resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} - engines: {node: '>= 0.6'} - content-disposition@1.0.1: resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} engines: {node: '>=18'} @@ -1717,10 +1650,6 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} - cookies@0.9.1: - resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==} - engines: {node: '>= 0.8'} - cors@2.8.5: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} @@ -1777,9 +1706,6 @@ packages: decode-named-character-reference@1.2.0: resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} - deep-equal@1.0.1: - resolution: {integrity: sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=} - define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -1800,13 +1726,6 @@ packages: resolution: {integrity: sha1-3zrhmayt+31ECqrgsp4icrJOxhk=} engines: {node: '>=0.4.0'} - delegates@1.0.0: - resolution: {integrity: sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=} - - depd@1.1.2: - resolution: {integrity: sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=} - engines: {node: '>= 0.6'} - depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -1815,20 +1734,12 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - destroy@1.2.0: - resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} devtools-protocol@0.0.1534754: resolution: {integrity: sha512-26T91cV5dbOYnXdJi5qQHoTtUoNEqwkHcAyu/IKtjIAxiEqPMrDiRkDOPWVsGfNZGmlQVHQbZRSjD8sxagWVsQ==} - dicer@0.3.0: - resolution: {integrity: sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==} - engines: {node: '>=4.5.0'} - dns-packet@5.6.1: resolution: {integrity: sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==} engines: {node: '>=6'} @@ -1852,13 +1763,6 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - enabled@2.0.0: - resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} - - encodeurl@1.0.2: - resolution: {integrity: sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=} - engines: {node: '>= 0.8'} - encodeurl@2.0.0: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} @@ -1916,10 +1820,6 @@ packages: escape-html@1.0.3: resolution: {integrity: sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=} - escape-string-regexp@1.0.5: - resolution: {integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=} - engines: {node: '>=0.8.0'} - escape-string-regexp@5.0.0: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} @@ -1993,10 +1893,6 @@ packages: fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - fast-xml-parser@3.21.1: - resolution: {integrity: sha512-FTFVjYoBOZTJekiUsawGsSYV9QL0A+zDYCRj7y34IO6Jg+2IMYEtQa+bbictpdpV8dHxXywqU7C0gRDEOFtBFg==} - hasBin: true - fast-xml-parser@4.5.3: resolution: {integrity: sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==} hasBin: true @@ -2015,9 +1911,6 @@ packages: fd-slicer@1.1.0: resolution: {integrity: sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=} - fecha@4.2.3: - resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} - figures@6.1.0: resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} engines: {node: '>=18'} @@ -2038,9 +1931,6 @@ packages: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} - fn.name@1.1.0: - resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} - follow-redirects@1.15.11: resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} engines: {node: '>=4.0'} @@ -2070,10 +1960,6 @@ packages: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} - fresh@0.5.2: - resolution: {integrity: sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=} - engines: {node: '>= 0.6'} - fresh@2.0.0: resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} engines: {node: '>= 0.8'} @@ -2082,10 +1968,6 @@ packages: resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==} engines: {node: '>=14.14'} - fs-extra@8.1.0: - resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} - engines: {node: '>=6 <7 || >=8'} - fs.realpath@1.0.0: resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=} @@ -2097,10 +1979,6 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - generator-function@2.0.1: - resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} - engines: {node: '>= 0.4'} - get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -2147,10 +2025,6 @@ packages: resolution: {integrity: sha512-KyrFvnl+J9US63TEzwoiJOQzZBJY7KgBushJA8X61DMbNsH+2ONkDuLDnCnwUiPTF42tLoEmrPyoqbenVA5zrg==} engines: {node: '>=18.0.0'} - has-flag@3.0.0: - resolution: {integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0=} - engines: {node: '>=4'} - has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} @@ -2193,14 +2067,6 @@ packages: html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} - http-assert@1.5.0: - resolution: {integrity: sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==} - engines: {node: '>= 0.8'} - - http-errors@1.8.1: - resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} - engines: {node: '>= 0.6'} - http-errors@2.0.1: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} @@ -2216,9 +2082,6 @@ packages: humanize-ms@1.2.1: resolution: {integrity: sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=} - humanize-number@0.0.2: - resolution: {integrity: sha1-EcCvakcWQ2M1iFiASPF5lUFInBg=} - iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -2264,10 +2127,6 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - is-generator-function@1.1.2: - resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} - engines: {node: '>= 0.4'} - is-nan@1.3.2: resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} engines: {node: '>= 0.4'} @@ -2287,14 +2146,6 @@ packages: is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - is-regex@1.2.1: - resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} - engines: {node: '>= 0.4'} - - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - is-stream@4.0.1: resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} engines: {node: '>=18'} @@ -2342,38 +2193,13 @@ packages: json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - jsonfile@4.0.0: - resolution: {integrity: sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=} - jsonfile@6.2.0: resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} - keygrip@1.1.0: - resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} - engines: {node: '>= 0.6'} - kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} - koa-compose@4.1.0: - resolution: {integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==} - - koa-convert@2.0.0: - resolution: {integrity: sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==} - engines: {node: '>= 10'} - - koa-logger@3.2.1: - resolution: {integrity: sha512-MjlznhLLKy9+kG8nAXKJLM0/ClsQp/Or2vI3a5rbSQmgl8IJBQO0KI5FA70BvW+hqjtxjp49SpH2E7okS6NmHg==} - engines: {node: '>= 7.6.0'} - - koa@2.16.3: - resolution: {integrity: sha512-zPPuIt+ku1iCpFBRwseMcPYQ1cJL8l60rSmKeOuGfOXyE6YnTBmf2aEFNL2HQGrD0cPcLO/t+v9RTgC+fwEh/g==} - engines: {node: ^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4} - - kuler@2.0.0: - resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} - lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -2426,13 +2252,6 @@ packages: lodash.restparam@3.6.1: resolution: {integrity: sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=} - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - - logform@2.7.0: - resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==} - engines: {node: '>= 12.0.0'} - longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} @@ -2510,10 +2329,6 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} - media-typer@0.3.0: - resolution: {integrity: sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=} - engines: {node: '>= 0.6'} - media-typer@1.1.0: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} @@ -2525,10 +2340,6 @@ packages: resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} engines: {node: '>=18'} - methods@1.1.2: - resolution: {integrity: sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=} - engines: {node: '>= 0.6'} - micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -2764,12 +2575,6 @@ packages: once@1.4.0: resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} - one-time@1.0.0: - resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} - - only@0.0.2: - resolution: {integrity: sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=} - open@8.4.2: resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} engines: {node: '>=12'} @@ -2835,9 +2640,6 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} - passthrough-counter@1.0.0: - resolution: {integrity: sha1-GWfZ5m2lcrXAI8eH2xEqOHqxZvo=} - path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2854,9 +2656,6 @@ packages: resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} engines: {node: 20 || >=22} - path-to-regexp@6.3.0: - resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} - path-to-regexp@8.3.0: resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} @@ -3020,22 +2819,9 @@ packages: rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - s3rver@3.7.1: - resolution: {integrity: sha512-H9KIX6n8NqcfoE4ziFNbQASBQfjcNJgb+3wbT9L5iotEqfOncFO1c38cfJSFSo7xXTu1zM9HA6t2u9xKNlYRaA==} - engines: {node: '>=8.3.0'} - hasBin: true - safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safe-regex-test@1.1.0: - resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} - engines: {node: '>= 0.4'} - - safe-stable-stringify@2.5.0: - resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} - engines: {node: '>=10'} - safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -3138,21 +2924,10 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=} - stack-trace@0.0.10: - resolution: {integrity: sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=} - - statuses@1.5.0: - resolution: {integrity: sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=} - engines: {node: '>= 0.6'} - statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - streamsearch@0.1.2: - resolution: {integrity: sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=} - engines: {node: '>=0.8.0'} - streamx@2.23.0: resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==} @@ -3192,18 +2967,14 @@ packages: resolution: {integrity: sha512-FhwotcEqjr241ZbjFzjlIYg6c5/L/s4yBGWSMvJ9UoExiSqL+FnFA/CaeZx17WGaZMS/4SOZp8wH18jSS4R4lw==} engines: {node: '>=16'} - supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - sweet-scroll@4.0.0: resolution: {integrity: sha512-mR6fRsAQANtm3zpzhUE73KAOt2aT4ZsWzNSggiEsSqdO6Zh4gM7ioJG81EngrZEl0XAc3ZvzEfhxggOoEBc8jA==} symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - systeminformation@5.30.4: - resolution: {integrity: sha512-6Zi6NZRuEnK8Uv8R5s6+iz2NvamrxpYdpxhF7ANpzjlTfDRPQEJJh1cz2Car5KT+L1EWv6zGzECITKTinfL47g==} + systeminformation@5.30.5: + resolution: {integrity: sha512-DpWmpCckhwR3hG+6udb6/aQB7PpiqVnvSljrjbKxNSvTRsGsg7NVE3/vouoYf96xgwMxXFKcS4Ux+cnkFwYM7A==} engines: {node: '>=8.0.0'} os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android] hasBin: true @@ -3217,9 +2988,6 @@ packages: text-decoder@1.2.3: resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} - text-hex@1.0.0: - resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} - threads@1.7.0: resolution: {integrity: sha512-Mx5NBSHX3sQYR6iI9VYbgHKBLisyB+xROCBGjjWm1O9wb9vfLxdaGtmT/KCjUqMsSNW6nERzCW3T6H43LqjDZQ==} @@ -3252,10 +3020,6 @@ packages: trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} - triple-beam@1.4.1: - resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} - engines: {node: '>= 14.0.0'} - trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} @@ -3265,10 +3029,6 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsscmp@1.0.6: - resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} - engines: {node: '>=0.6.x'} - tsx@4.21.0: resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} engines: {node: '>=18.0.0'} @@ -3296,10 +3056,6 @@ packages: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} - type-is@1.6.18: - resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} - engines: {node: '>= 0.6'} - type-is@2.0.1: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} @@ -3345,10 +3101,6 @@ packages: unist-util-visit@5.0.0: resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} - universalify@0.1.2: - resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} - engines: {node: '>= 4.0.0'} - universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -3409,14 +3161,6 @@ packages: engines: {node: ^18.17.0 || >=20.5.0} hasBin: true - winston-transport@4.9.0: - resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==} - engines: {node: '>= 12.0.0'} - - winston@3.19.0: - resolution: {integrity: sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==} - engines: {node: '>= 12.0.0'} - wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} @@ -3500,10 +3244,6 @@ packages: resolution: {integrity: sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==} engines: {node: '>=12'} - ylru@1.4.0: - resolution: {integrity: sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==} - engines: {node: '>= 4.0.0'} - yoctocolors-cjs@2.1.3: resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} engines: {node: '>=18'} @@ -3537,7 +3277,7 @@ snapshots: '@api.global/typedrequest': 3.2.5 '@api.global/typedrequest-interfaces': 3.0.19 '@api.global/typedsocket': 3.1.1 - '@cloudflare/workers-types': 4.20260116.0 + '@cloudflare/workers-types': 4.20260118.0 '@design.estate/dees-comms': 1.0.30 '@push.rocks/lik': 6.2.2 '@push.rocks/smartchok': 1.2.0 @@ -3645,31 +3385,31 @@ snapshots: '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 - '@aws-sdk/client-s3@3.970.0': + '@aws-sdk/client-s3@3.971.0': dependencies: '@aws-crypto/sha1-browser': 5.2.0 '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 '@aws-sdk/core': 3.970.0 - '@aws-sdk/credential-provider-node': 3.970.0 + '@aws-sdk/credential-provider-node': 3.971.0 '@aws-sdk/middleware-bucket-endpoint': 3.969.0 '@aws-sdk/middleware-expect-continue': 3.969.0 - '@aws-sdk/middleware-flexible-checksums': 3.970.0 + '@aws-sdk/middleware-flexible-checksums': 3.971.0 '@aws-sdk/middleware-host-header': 3.969.0 '@aws-sdk/middleware-location-constraint': 3.969.0 '@aws-sdk/middleware-logger': 3.969.0 '@aws-sdk/middleware-recursion-detection': 3.969.0 '@aws-sdk/middleware-sdk-s3': 3.970.0 - '@aws-sdk/middleware-ssec': 3.969.0 + '@aws-sdk/middleware-ssec': 3.971.0 '@aws-sdk/middleware-user-agent': 3.970.0 '@aws-sdk/region-config-resolver': 3.969.0 '@aws-sdk/signature-v4-multi-region': 3.970.0 '@aws-sdk/types': 3.969.0 '@aws-sdk/util-endpoints': 3.970.0 '@aws-sdk/util-user-agent-browser': 3.969.0 - '@aws-sdk/util-user-agent-node': 3.970.0 + '@aws-sdk/util-user-agent-node': 3.971.0 '@smithy/config-resolver': 4.4.6 - '@smithy/core': 3.20.6 + '@smithy/core': 3.20.7 '@smithy/eventstream-serde-browser': 4.2.8 '@smithy/eventstream-serde-config-resolver': 4.3.8 '@smithy/eventstream-serde-node': 4.2.8 @@ -3680,21 +3420,21 @@ snapshots: '@smithy/invalid-dependency': 4.2.8 '@smithy/md5-js': 4.2.8 '@smithy/middleware-content-length': 4.2.8 - '@smithy/middleware-endpoint': 4.4.7 - '@smithy/middleware-retry': 4.4.23 + '@smithy/middleware-endpoint': 4.4.8 + '@smithy/middleware-retry': 4.4.24 '@smithy/middleware-serde': 4.2.9 '@smithy/middleware-stack': 4.2.8 '@smithy/node-config-provider': 4.3.8 '@smithy/node-http-handler': 4.4.8 '@smithy/protocol-http': 5.3.8 - '@smithy/smithy-client': 4.10.8 + '@smithy/smithy-client': 4.10.9 '@smithy/types': 4.12.0 '@smithy/url-parser': 4.2.8 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 '@smithy/util-body-length-node': 4.2.1 - '@smithy/util-defaults-mode-browser': 4.3.22 - '@smithy/util-defaults-mode-node': 4.2.25 + '@smithy/util-defaults-mode-browser': 4.3.23 + '@smithy/util-defaults-mode-node': 4.2.26 '@smithy/util-endpoints': 3.2.8 '@smithy/util-middleware': 4.2.8 '@smithy/util-retry': 4.2.8 @@ -3705,7 +3445,7 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sso@3.970.0': + '@aws-sdk/client-sso@3.971.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 @@ -3718,28 +3458,28 @@ snapshots: '@aws-sdk/types': 3.969.0 '@aws-sdk/util-endpoints': 3.970.0 '@aws-sdk/util-user-agent-browser': 3.969.0 - '@aws-sdk/util-user-agent-node': 3.970.0 + '@aws-sdk/util-user-agent-node': 3.971.0 '@smithy/config-resolver': 4.4.6 - '@smithy/core': 3.20.6 + '@smithy/core': 3.20.7 '@smithy/fetch-http-handler': 5.3.9 '@smithy/hash-node': 4.2.8 '@smithy/invalid-dependency': 4.2.8 '@smithy/middleware-content-length': 4.2.8 - '@smithy/middleware-endpoint': 4.4.7 - '@smithy/middleware-retry': 4.4.23 + '@smithy/middleware-endpoint': 4.4.8 + '@smithy/middleware-retry': 4.4.24 '@smithy/middleware-serde': 4.2.9 '@smithy/middleware-stack': 4.2.8 '@smithy/node-config-provider': 4.3.8 '@smithy/node-http-handler': 4.4.8 '@smithy/protocol-http': 5.3.8 - '@smithy/smithy-client': 4.10.8 + '@smithy/smithy-client': 4.10.9 '@smithy/types': 4.12.0 '@smithy/url-parser': 4.2.8 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 '@smithy/util-body-length-node': 4.2.1 - '@smithy/util-defaults-mode-browser': 4.3.22 - '@smithy/util-defaults-mode-node': 4.2.25 + '@smithy/util-defaults-mode-browser': 4.3.23 + '@smithy/util-defaults-mode-node': 4.2.26 '@smithy/util-endpoints': 3.2.8 '@smithy/util-middleware': 4.2.8 '@smithy/util-retry': 4.2.8 @@ -3752,12 +3492,12 @@ snapshots: dependencies: '@aws-sdk/types': 3.969.0 '@aws-sdk/xml-builder': 3.969.0 - '@smithy/core': 3.20.6 + '@smithy/core': 3.20.7 '@smithy/node-config-provider': 4.3.8 '@smithy/property-provider': 4.2.8 '@smithy/protocol-http': 5.3.8 '@smithy/signature-v4': 5.3.8 - '@smithy/smithy-client': 4.10.8 + '@smithy/smithy-client': 4.10.9 '@smithy/types': 4.12.0 '@smithy/util-base64': 4.3.0 '@smithy/util-middleware': 4.2.8 @@ -3785,21 +3525,21 @@ snapshots: '@smithy/node-http-handler': 4.4.8 '@smithy/property-provider': 4.2.8 '@smithy/protocol-http': 5.3.8 - '@smithy/smithy-client': 4.10.8 + '@smithy/smithy-client': 4.10.9 '@smithy/types': 4.12.0 '@smithy/util-stream': 4.5.10 tslib: 2.8.1 - '@aws-sdk/credential-provider-ini@3.970.0': + '@aws-sdk/credential-provider-ini@3.971.0': dependencies: '@aws-sdk/core': 3.970.0 '@aws-sdk/credential-provider-env': 3.970.0 '@aws-sdk/credential-provider-http': 3.970.0 - '@aws-sdk/credential-provider-login': 3.970.0 + '@aws-sdk/credential-provider-login': 3.971.0 '@aws-sdk/credential-provider-process': 3.970.0 - '@aws-sdk/credential-provider-sso': 3.970.0 - '@aws-sdk/credential-provider-web-identity': 3.970.0 - '@aws-sdk/nested-clients': 3.970.0 + '@aws-sdk/credential-provider-sso': 3.971.0 + '@aws-sdk/credential-provider-web-identity': 3.971.0 + '@aws-sdk/nested-clients': 3.971.0 '@aws-sdk/types': 3.969.0 '@smithy/credential-provider-imds': 4.2.8 '@smithy/property-provider': 4.2.8 @@ -3809,10 +3549,10 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-login@3.970.0': + '@aws-sdk/credential-provider-login@3.971.0': dependencies: '@aws-sdk/core': 3.970.0 - '@aws-sdk/nested-clients': 3.970.0 + '@aws-sdk/nested-clients': 3.971.0 '@aws-sdk/types': 3.969.0 '@smithy/property-provider': 4.2.8 '@smithy/protocol-http': 5.3.8 @@ -3822,14 +3562,14 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-node@3.970.0': + '@aws-sdk/credential-provider-node@3.971.0': dependencies: '@aws-sdk/credential-provider-env': 3.970.0 '@aws-sdk/credential-provider-http': 3.970.0 - '@aws-sdk/credential-provider-ini': 3.970.0 + '@aws-sdk/credential-provider-ini': 3.971.0 '@aws-sdk/credential-provider-process': 3.970.0 - '@aws-sdk/credential-provider-sso': 3.970.0 - '@aws-sdk/credential-provider-web-identity': 3.970.0 + '@aws-sdk/credential-provider-sso': 3.971.0 + '@aws-sdk/credential-provider-web-identity': 3.971.0 '@aws-sdk/types': 3.969.0 '@smithy/credential-provider-imds': 4.2.8 '@smithy/property-provider': 4.2.8 @@ -3848,11 +3588,11 @@ snapshots: '@smithy/types': 4.12.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-sso@3.970.0': + '@aws-sdk/credential-provider-sso@3.971.0': dependencies: - '@aws-sdk/client-sso': 3.970.0 + '@aws-sdk/client-sso': 3.971.0 '@aws-sdk/core': 3.970.0 - '@aws-sdk/token-providers': 3.970.0 + '@aws-sdk/token-providers': 3.971.0 '@aws-sdk/types': 3.969.0 '@smithy/property-provider': 4.2.8 '@smithy/shared-ini-file-loader': 4.4.3 @@ -3861,10 +3601,10 @@ snapshots: transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-web-identity@3.970.0': + '@aws-sdk/credential-provider-web-identity@3.971.0': dependencies: '@aws-sdk/core': 3.970.0 - '@aws-sdk/nested-clients': 3.970.0 + '@aws-sdk/nested-clients': 3.971.0 '@aws-sdk/types': 3.969.0 '@smithy/property-provider': 4.2.8 '@smithy/shared-ini-file-loader': 4.4.3 @@ -3890,7 +3630,7 @@ snapshots: '@smithy/types': 4.12.0 tslib: 2.8.1 - '@aws-sdk/middleware-flexible-checksums@3.970.0': + '@aws-sdk/middleware-flexible-checksums@3.971.0': dependencies: '@aws-crypto/crc32': 5.2.0 '@aws-crypto/crc32c': 5.2.0 @@ -3939,11 +3679,11 @@ snapshots: '@aws-sdk/core': 3.970.0 '@aws-sdk/types': 3.969.0 '@aws-sdk/util-arn-parser': 3.968.0 - '@smithy/core': 3.20.6 + '@smithy/core': 3.20.7 '@smithy/node-config-provider': 4.3.8 '@smithy/protocol-http': 5.3.8 '@smithy/signature-v4': 5.3.8 - '@smithy/smithy-client': 4.10.8 + '@smithy/smithy-client': 4.10.9 '@smithy/types': 4.12.0 '@smithy/util-config-provider': 4.2.0 '@smithy/util-middleware': 4.2.8 @@ -3951,7 +3691,7 @@ snapshots: '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@aws-sdk/middleware-ssec@3.969.0': + '@aws-sdk/middleware-ssec@3.971.0': dependencies: '@aws-sdk/types': 3.969.0 '@smithy/types': 4.12.0 @@ -3962,12 +3702,12 @@ snapshots: '@aws-sdk/core': 3.970.0 '@aws-sdk/types': 3.969.0 '@aws-sdk/util-endpoints': 3.970.0 - '@smithy/core': 3.20.6 + '@smithy/core': 3.20.7 '@smithy/protocol-http': 5.3.8 '@smithy/types': 4.12.0 tslib: 2.8.1 - '@aws-sdk/nested-clients@3.970.0': + '@aws-sdk/nested-clients@3.971.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 @@ -3980,28 +3720,28 @@ snapshots: '@aws-sdk/types': 3.969.0 '@aws-sdk/util-endpoints': 3.970.0 '@aws-sdk/util-user-agent-browser': 3.969.0 - '@aws-sdk/util-user-agent-node': 3.970.0 + '@aws-sdk/util-user-agent-node': 3.971.0 '@smithy/config-resolver': 4.4.6 - '@smithy/core': 3.20.6 + '@smithy/core': 3.20.7 '@smithy/fetch-http-handler': 5.3.9 '@smithy/hash-node': 4.2.8 '@smithy/invalid-dependency': 4.2.8 '@smithy/middleware-content-length': 4.2.8 - '@smithy/middleware-endpoint': 4.4.7 - '@smithy/middleware-retry': 4.4.23 + '@smithy/middleware-endpoint': 4.4.8 + '@smithy/middleware-retry': 4.4.24 '@smithy/middleware-serde': 4.2.9 '@smithy/middleware-stack': 4.2.8 '@smithy/node-config-provider': 4.3.8 '@smithy/node-http-handler': 4.4.8 '@smithy/protocol-http': 5.3.8 - '@smithy/smithy-client': 4.10.8 + '@smithy/smithy-client': 4.10.9 '@smithy/types': 4.12.0 '@smithy/url-parser': 4.2.8 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 '@smithy/util-body-length-node': 4.2.1 - '@smithy/util-defaults-mode-browser': 4.3.22 - '@smithy/util-defaults-mode-node': 4.2.25 + '@smithy/util-defaults-mode-browser': 4.3.23 + '@smithy/util-defaults-mode-node': 4.2.26 '@smithy/util-endpoints': 3.2.8 '@smithy/util-middleware': 4.2.8 '@smithy/util-retry': 4.2.8 @@ -4027,10 +3767,10 @@ snapshots: '@smithy/types': 4.12.0 tslib: 2.8.1 - '@aws-sdk/token-providers@3.970.0': + '@aws-sdk/token-providers@3.971.0': dependencies: '@aws-sdk/core': 3.970.0 - '@aws-sdk/nested-clients': 3.970.0 + '@aws-sdk/nested-clients': 3.971.0 '@aws-sdk/types': 3.969.0 '@smithy/property-provider': 4.2.8 '@smithy/shared-ini-file-loader': 4.4.3 @@ -4067,7 +3807,7 @@ snapshots: bowser: 2.13.1 tslib: 2.8.1 - '@aws-sdk/util-user-agent-node@3.970.0': + '@aws-sdk/util-user-agent-node@3.971.0': dependencies: '@aws-sdk/middleware-user-agent': 3.970.0 '@aws-sdk/types': 3.969.0 @@ -4095,20 +3835,12 @@ snapshots: '@borewit/text-codec@0.2.1': {} - '@cloudflare/workers-types@4.20260116.0': {} - - '@colors/colors@1.6.0': {} + '@cloudflare/workers-types@4.20260118.0': {} '@configvault.io/interfaces@1.0.17': dependencies: '@api.global/typedrequest-interfaces': 3.0.19 - '@dabh/diagnostics@2.0.8': - dependencies: - '@so-ric/colorspace': 1.1.6 - enabled: 2.0.0 - kuler: 2.0.0 - '@design.estate/dees-comms@1.0.30': dependencies: '@api.global/typedrequest': 3.2.5 @@ -4274,32 +4006,34 @@ snapshots: - supports-color - vue - '@git.zone/tsrun@1.6.2': + '@git.zone/tsrun@2.0.1': dependencies: - '@push.rocks/smartfile': 11.2.7 + '@push.rocks/smartfile': 13.1.2 '@push.rocks/smartshell': 3.3.0 tsx: 4.21.0 - '@git.zone/tstest@1.11.5(socks@2.8.7)(typescript@5.9.3)': + '@git.zone/tstest@3.1.5(socks@2.8.7)(typescript@5.9.3)': dependencies: '@api.global/typedserver': 3.0.80 '@git.zone/tsbundle': 2.8.1 - '@git.zone/tsrun': 1.6.2 + '@git.zone/tsrun': 2.0.1 '@push.rocks/consolecolor': 2.0.3 '@push.rocks/qenv': 6.1.3 '@push.rocks/smartbrowser': 2.0.8(typescript@5.9.3) + '@push.rocks/smartchok': 1.2.0 '@push.rocks/smartcrypto': 2.0.4 '@push.rocks/smartdelay': 3.0.5 - '@push.rocks/smartenv': 5.0.13 + '@push.rocks/smartenv': 6.0.0 '@push.rocks/smartexpect': 2.5.0 '@push.rocks/smartfile': 11.2.7 '@push.rocks/smartjson': 5.2.0 '@push.rocks/smartlog': 3.1.10 '@push.rocks/smartmongo': 2.0.14(socks@2.8.7) - '@push.rocks/smartpath': 5.1.0 + '@push.rocks/smartnetwork': 4.4.0 + '@push.rocks/smartpath': 6.0.0 '@push.rocks/smartpromise': 4.2.3 - '@push.rocks/smartrequest': 2.1.0 - '@push.rocks/smarts3': 2.2.7 + '@push.rocks/smartrequest': 5.0.1 + '@push.rocks/smarts3': 3.0.3 '@push.rocks/smartshell': 3.3.0 '@push.rocks/smarttime': 4.1.1 '@types/ws': 8.18.1 @@ -4442,16 +4176,6 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@koa/router@9.4.0': - dependencies: - debug: 4.4.3 - http-errors: 1.8.1 - koa-compose: 4.1.0 - methods: 1.1.2 - path-to-regexp: 6.3.0 - transitivePeerDependencies: - - supports-color - '@leichtgewicht/ip-codec@2.0.5': {} '@lit-labs/ssr-dom-shim@1.5.1': {} @@ -4487,7 +4211,7 @@ snapshots: '@module-federation/runtime': 0.22.0 '@module-federation/sdk': 0.22.0 - '@mongodb-js/saslprep@1.4.4': + '@mongodb-js/saslprep@1.4.5': dependencies: sparse-bitfield: 3.0.3 @@ -4705,9 +4429,9 @@ snapshots: - typescript - utf-8-validate - '@push.rocks/smartbucket@3.3.10': + '@push.rocks/smartbucket@4.3.0': dependencies: - '@aws-sdk/client-s3': 3.970.0 + '@aws-sdk/client-s3': 3.971.0 '@push.rocks/smartmime': 2.0.4 '@push.rocks/smartpath': 6.0.0 '@push.rocks/smartpromise': 4.2.3 @@ -4716,6 +4440,7 @@ snapshots: '@push.rocks/smartstring': 4.1.0 '@push.rocks/smartunique': 3.0.9 '@tsclass/tsclass': 9.3.0 + minimatch: 10.1.1 transitivePeerDependencies: - aws-crt @@ -4863,6 +4588,23 @@ snapshots: glob: 11.1.0 js-yaml: 4.1.1 + '@push.rocks/smartfile@13.1.2': + dependencies: + '@push.rocks/lik': 6.2.2 + '@push.rocks/smartdelay': 3.0.5 + '@push.rocks/smartfile-interfaces': 1.0.7 + '@push.rocks/smartfs': 1.3.1 + '@push.rocks/smarthash': 3.2.6 + '@push.rocks/smartjson': 5.2.0 + '@push.rocks/smartmime': 2.0.4 + '@push.rocks/smartpath': 6.0.0 + '@push.rocks/smartpromise': 4.2.3 + '@push.rocks/smartrequest': 4.4.2 + '@push.rocks/smartstream': 3.2.5 + '@types/js-yaml': 4.0.9 + glob: 11.1.0 + js-yaml: 4.1.1 + '@push.rocks/smartfs@1.3.1': dependencies: '@push.rocks/smartpath': 6.0.0 @@ -4978,7 +4720,7 @@ snapshots: '@push.rocks/smartpromise': 4.2.3 '@push.rocks/smartstring': 4.1.0 isopen: 1.3.0 - systeminformation: 5.30.4 + systeminformation: 5.30.5 transitivePeerDependencies: - supports-color @@ -5069,6 +4811,15 @@ snapshots: agentkeepalive: 4.6.0 form-data: 4.0.5 + '@push.rocks/smartrequest@5.0.1': + dependencies: + '@push.rocks/smartenv': 6.0.0 + '@push.rocks/smartpath': 6.0.0 + '@push.rocks/smartpromise': 4.2.3 + '@push.rocks/smarturl': 3.1.0 + agentkeepalive: 4.6.0 + form-data: 4.0.5 + '@push.rocks/smartrouter@1.3.3': dependencies: '@push.rocks/lik': 6.2.2 @@ -5080,17 +4831,15 @@ snapshots: '@push.rocks/smartpromise': 4.2.3 rxjs: 7.8.2 - '@push.rocks/smarts3@2.2.7': + '@push.rocks/smarts3@3.0.3': dependencies: - '@push.rocks/smartbucket': 3.3.10 - '@push.rocks/smartfile': 11.2.7 + '@push.rocks/smartbucket': 4.3.0 + '@push.rocks/smartfs': 1.3.1 '@push.rocks/smartpath': 6.0.0 + '@push.rocks/smartxml': 2.0.0 '@tsclass/tsclass': 9.3.0 - '@types/s3rver': 3.7.4 - s3rver: 3.7.1 transitivePeerDependencies: - aws-crt - - supports-color '@push.rocks/smartshell@3.3.0': dependencies: @@ -5415,7 +5164,7 @@ snapshots: '@smithy/util-middleware': 4.2.8 tslib: 2.8.1 - '@smithy/core@3.20.6': + '@smithy/core@3.20.7': dependencies: '@smithy/middleware-serde': 4.2.9 '@smithy/protocol-http': 5.3.8 @@ -5519,9 +5268,9 @@ snapshots: '@smithy/types': 4.12.0 tslib: 2.8.1 - '@smithy/middleware-endpoint@4.4.7': + '@smithy/middleware-endpoint@4.4.8': dependencies: - '@smithy/core': 3.20.6 + '@smithy/core': 3.20.7 '@smithy/middleware-serde': 4.2.9 '@smithy/node-config-provider': 4.3.8 '@smithy/shared-ini-file-loader': 4.4.3 @@ -5530,12 +5279,12 @@ snapshots: '@smithy/util-middleware': 4.2.8 tslib: 2.8.1 - '@smithy/middleware-retry@4.4.23': + '@smithy/middleware-retry@4.4.24': dependencies: '@smithy/node-config-provider': 4.3.8 '@smithy/protocol-http': 5.3.8 '@smithy/service-error-classification': 4.2.8 - '@smithy/smithy-client': 4.10.8 + '@smithy/smithy-client': 4.10.9 '@smithy/types': 4.12.0 '@smithy/util-middleware': 4.2.8 '@smithy/util-retry': 4.2.8 @@ -5609,10 +5358,10 @@ snapshots: '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@smithy/smithy-client@4.10.8': + '@smithy/smithy-client@4.10.9': dependencies: - '@smithy/core': 3.20.6 - '@smithy/middleware-endpoint': 4.4.7 + '@smithy/core': 3.20.7 + '@smithy/middleware-endpoint': 4.4.8 '@smithy/middleware-stack': 4.2.8 '@smithy/protocol-http': 5.3.8 '@smithy/types': 4.12.0 @@ -5657,20 +5406,20 @@ snapshots: dependencies: tslib: 2.8.1 - '@smithy/util-defaults-mode-browser@4.3.22': + '@smithy/util-defaults-mode-browser@4.3.23': dependencies: '@smithy/property-provider': 4.2.8 - '@smithy/smithy-client': 4.10.8 + '@smithy/smithy-client': 4.10.9 '@smithy/types': 4.12.0 tslib: 2.8.1 - '@smithy/util-defaults-mode-node@4.2.25': + '@smithy/util-defaults-mode-node@4.2.26': dependencies: '@smithy/config-resolver': 4.4.6 '@smithy/credential-provider-imds': 4.2.8 '@smithy/node-config-provider': 4.3.8 '@smithy/property-provider': 4.2.8 - '@smithy/smithy-client': 4.10.8 + '@smithy/smithy-client': 4.10.9 '@smithy/types': 4.12.0 tslib: 2.8.1 @@ -5730,11 +5479,6 @@ snapshots: dependencies: tslib: 2.8.1 - '@so-ric/colorspace@1.1.6': - dependencies: - color: 5.0.3 - text-hex: 1.0.0 - '@socket.io/component-emitter@3.1.2': {} '@tempfix/idb@8.0.3': {} @@ -5868,10 +5612,6 @@ snapshots: '@types/relateurl@0.2.33': {} - '@types/s3rver@3.7.4': - dependencies: - '@types/node': 25.0.9 - '@types/send@1.2.1': dependencies: '@types/node': 25.0.9 @@ -5887,8 +5627,6 @@ snapshots: dependencies: '@types/node': 25.0.9 - '@types/triple-beam@1.3.5': {} - '@types/trusted-types@2.0.7': {} '@types/turndown@5.0.6': {} @@ -5958,10 +5696,6 @@ snapshots: ansi-regex@6.2.2: {} - ansi-styles@3.2.1: - dependencies: - color-convert: 1.9.3 - ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -5988,8 +5722,6 @@ snapshots: dependencies: tslib: 2.8.1 - async@3.2.6: {} - asynckit@0.4.0: {} axios@1.13.2(debug@4.4.3): @@ -6096,17 +5828,8 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 - busboy@0.3.1: - dependencies: - dicer: 0.3.0 - bytes@3.1.2: {} - cache-content-type@1.0.1: - dependencies: - mime-types: 2.1.35 - ylru: 1.4.0 - call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -6135,12 +5858,6 @@ snapshots: ccount@2.0.1: {} - chalk@2.4.2: - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - character-entities-html4@2.1.0: {} character-entities-legacy@3.0.0: {} @@ -6173,35 +5890,12 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - co@4.6.0: {} - - color-convert@1.9.3: - dependencies: - color-name: 1.1.3 - color-convert@2.0.1: dependencies: color-name: 1.1.4 - color-convert@3.1.3: - dependencies: - color-name: 2.1.0 - - color-name@1.1.3: {} - color-name@1.1.4: {} - color-name@2.1.0: {} - - color-string@2.1.4: - dependencies: - color-name: 2.1.0 - - color@5.0.3: - dependencies: - color-convert: 3.1.3 - color-string: 2.1.4 - combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -6210,16 +5904,10 @@ snapshots: commander@2.20.3: {} - commander@5.1.0: {} - commondir@1.0.1: {} concat-map@0.0.1: {} - content-disposition@0.5.4: - dependencies: - safe-buffer: 5.2.1 - content-disposition@1.0.1: {} content-type@1.0.5: {} @@ -6228,11 +5916,6 @@ snapshots: cookie@0.7.2: {} - cookies@0.9.1: - dependencies: - depd: 2.0.0 - keygrip: 1.1.0 - cors@2.8.5: dependencies: object-assign: 4.1.1 @@ -6277,8 +5960,6 @@ snapshots: dependencies: character-entities: 2.0.2 - deep-equal@1.0.1: {} - define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 @@ -6301,26 +5982,16 @@ snapshots: delayed-stream@1.0.0: {} - delegates@1.0.0: {} - - depd@1.1.2: {} - depd@2.0.0: {} dequal@2.0.3: {} - destroy@1.2.0: {} - devlop@1.1.0: dependencies: dequal: 2.0.3 devtools-protocol@0.0.1534754: {} - dicer@0.3.0: - dependencies: - streamsearch: 0.1.2 - dns-packet@5.6.1: dependencies: '@leichtgewicht/ip-codec': 2.0.5 @@ -6349,10 +6020,6 @@ snapshots: emoji-regex@9.2.2: {} - enabled@2.0.0: {} - - encodeurl@1.0.2: {} - encodeurl@2.0.0: {} end-of-stream@1.4.5: @@ -6445,8 +6112,6 @@ snapshots: escape-html@1.0.3: {} - escape-string-regexp@1.0.5: {} - escape-string-regexp@5.0.0: {} escodegen@2.1.0: @@ -6540,10 +6205,6 @@ snapshots: fast-json-stable-stringify@2.1.0: {} - fast-xml-parser@3.21.1: - dependencies: - strnum: 1.1.2 - fast-xml-parser@4.5.3: dependencies: strnum: 1.1.2 @@ -6564,8 +6225,6 @@ snapshots: dependencies: pend: 1.2.0 - fecha@4.2.3: {} - figures@6.1.0: dependencies: is-unicode-supported: 2.1.0 @@ -6599,8 +6258,6 @@ snapshots: locate-path: 5.0.0 path-exists: 4.0.0 - fn.name@1.1.0: {} - follow-redirects@1.15.11(debug@4.4.3): optionalDependencies: debug: 4.4.3 @@ -6627,8 +6284,6 @@ snapshots: forwarded@0.2.0: {} - fresh@0.5.2: {} - fresh@2.0.0: {} fs-extra@11.3.3: @@ -6637,12 +6292,6 @@ snapshots: jsonfile: 6.2.0 universalify: 2.0.1 - fs-extra@8.1.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 4.0.0 - universalify: 0.1.2 - fs.realpath@1.0.0: {} fsevents@2.3.3: @@ -6650,8 +6299,6 @@ snapshots: function-bind@1.1.2: {} - generator-function@2.0.1: {} - get-caller-file@2.0.5: {} get-intrinsic@1.3.0: @@ -6721,8 +6368,6 @@ snapshots: webidl-conversions: 7.0.0 whatwg-mimetype: 3.0.0 - has-flag@3.0.0: {} - has-property-descriptors@1.0.2: dependencies: es-define-property: 1.0.1 @@ -6786,19 +6431,6 @@ snapshots: html-void-elements@3.0.0: {} - http-assert@1.5.0: - dependencies: - deep-equal: 1.0.1 - http-errors: 1.8.1 - - http-errors@1.8.1: - dependencies: - depd: 1.1.2 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 1.5.0 - toidentifier: 1.0.1 - http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -6825,8 +6457,6 @@ snapshots: dependencies: ms: 2.1.3 - humanize-number@0.0.2: {} - iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -6870,14 +6500,6 @@ snapshots: is-fullwidth-code-point@3.0.0: {} - is-generator-function@1.1.2: - dependencies: - call-bound: 1.0.4 - generator-function: 2.0.1 - get-proto: 1.0.1 - has-tostringtag: 1.0.2 - safe-regex-test: 1.1.0 - is-nan@1.3.2: dependencies: call-bind: 1.0.8 @@ -6891,15 +6513,6 @@ snapshots: is-promise@4.0.0: {} - is-regex@1.2.1: - dependencies: - call-bound: 1.0.4 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - - is-stream@2.0.1: {} - is-stream@4.0.1: {} is-unicode-supported@2.1.0: {} @@ -6935,66 +6548,14 @@ snapshots: json-parse-even-better-errors@2.3.1: {} - jsonfile@4.0.0: - optionalDependencies: - graceful-fs: 4.2.11 - jsonfile@6.2.0: dependencies: universalify: 2.0.1 optionalDependencies: graceful-fs: 4.2.11 - keygrip@1.1.0: - dependencies: - tsscmp: 1.0.6 - kind-of@6.0.3: {} - koa-compose@4.1.0: {} - - koa-convert@2.0.0: - dependencies: - co: 4.6.0 - koa-compose: 4.1.0 - - koa-logger@3.2.1: - dependencies: - bytes: 3.1.2 - chalk: 2.4.2 - humanize-number: 0.0.2 - passthrough-counter: 1.0.0 - - koa@2.16.3: - dependencies: - accepts: 1.3.8 - cache-content-type: 1.0.1 - content-disposition: 0.5.4 - content-type: 1.0.5 - cookies: 0.9.1 - debug: 4.4.3 - delegates: 1.0.0 - depd: 2.0.0 - destroy: 1.2.0 - encodeurl: 1.0.2 - escape-html: 1.0.3 - fresh: 0.5.2 - http-assert: 1.5.0 - http-errors: 1.8.1 - is-generator-function: 1.1.2 - koa-compose: 4.1.0 - koa-convert: 2.0.0 - on-finished: 2.4.1 - only: 0.0.2 - parseurl: 1.3.3 - statuses: 1.5.0 - type-is: 1.6.18 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - - kuler@2.0.0: {} - lines-and-columns@1.2.4: {} lit-element@4.2.2: @@ -7056,17 +6617,6 @@ snapshots: lodash.restparam@3.6.1: {} - lodash@4.17.21: {} - - logform@2.7.0: - dependencies: - '@colors/colors': 1.6.0 - '@types/triple-beam': 1.3.5 - fecha: 4.2.3 - ms: 2.1.3 - safe-stable-stringify: 2.5.0 - triple-beam: 1.4.1 - longest-streak@3.1.0: {} lower-case@1.1.4: {} @@ -7220,16 +6770,12 @@ snapshots: dependencies: '@types/mdast': 4.0.4 - media-typer@0.3.0: {} - media-typer@1.1.0: {} memory-pager@1.5.0: {} merge-descriptors@2.0.0: {} - methods@1.1.2: {} - micromark-core-commonmark@2.0.3: dependencies: decode-named-character-reference: 1.2.0 @@ -7511,7 +7057,7 @@ snapshots: mongodb@6.21.0(socks@2.8.7): dependencies: - '@mongodb-js/saslprep': 1.4.4 + '@mongodb-js/saslprep': 1.4.5 bson: 6.10.4 mongodb-connection-string-url: 3.0.2 optionalDependencies: @@ -7563,12 +7109,6 @@ snapshots: dependencies: wrappy: 1.0.2 - one-time@1.0.0: - dependencies: - fn.name: 1.1.0 - - only@0.0.2: {} - open@8.4.2: dependencies: define-lazy-prop: 2.0.0 @@ -7639,8 +7179,6 @@ snapshots: parseurl@1.3.3: {} - passthrough-counter@1.0.0: {} - path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -7652,8 +7190,6 @@ snapshots: lru-cache: 11.2.4 minipass: 7.1.2 - path-to-regexp@6.3.0: {} - path-to-regexp@8.3.0: {} pdf-lib@1.17.1: @@ -7878,32 +7414,8 @@ snapshots: dependencies: tslib: 2.8.1 - s3rver@3.7.1: - dependencies: - '@koa/router': 9.4.0 - busboy: 0.3.1 - commander: 5.1.0 - fast-xml-parser: 3.21.1 - fs-extra: 8.1.0 - he: 1.2.0 - koa: 2.16.3 - koa-logger: 3.2.1 - lodash: 4.17.21 - statuses: 2.0.2 - winston: 3.19.0 - transitivePeerDependencies: - - supports-color - safe-buffer@5.2.1: {} - safe-regex-test@1.1.0: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-regex: 1.2.1 - - safe-stable-stringify@2.5.0: {} - safer-buffer@2.1.2: {} semver@6.3.1: {} @@ -8059,14 +7571,8 @@ snapshots: sprintf-js@1.0.3: {} - stack-trace@0.0.10: {} - - statuses@1.5.0: {} - statuses@2.0.2: {} - streamsearch@0.1.2: {} - streamx@2.23.0: dependencies: events-universal: 1.0.1 @@ -8116,15 +7622,11 @@ snapshots: '@tokenizer/token': 0.3.0 peek-readable: 5.4.2 - supports-color@5.5.0: - dependencies: - has-flag: 3.0.0 - sweet-scroll@4.0.0: {} symbol-tree@3.2.4: {} - systeminformation@5.30.4: {} + systeminformation@5.30.5: {} tar-fs@3.1.1: dependencies: @@ -8153,8 +7655,6 @@ snapshots: transitivePeerDependencies: - react-native-b4a - text-hex@1.0.0: {} - threads@1.7.0: dependencies: callsites: 3.1.0 @@ -8194,16 +7694,12 @@ snapshots: trim-lines@3.0.1: {} - triple-beam@1.4.1: {} - trough@2.2.0: {} tslib@1.14.1: {} tslib@2.8.1: {} - tsscmp@1.0.6: {} - tsx@4.21.0: dependencies: esbuild: 0.27.2 @@ -8227,11 +7723,6 @@ snapshots: type-fest@4.41.0: {} - type-is@1.6.18: - dependencies: - media-typer: 0.3.0 - mime-types: 2.1.35 - type-is@2.0.1: dependencies: content-type: 1.0.5 @@ -8283,8 +7774,6 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 - universalify@0.1.2: {} - universalify@2.0.1: {} unload@2.4.1: {} @@ -8333,26 +7822,6 @@ snapshots: dependencies: isexe: 3.1.1 - winston-transport@4.9.0: - dependencies: - logform: 2.7.0 - readable-stream: 3.6.2 - triple-beam: 1.4.1 - - winston@3.19.0: - dependencies: - '@colors/colors': 1.6.0 - '@dabh/diagnostics': 2.0.8 - async: 3.2.6 - is-stream: 2.0.1 - logform: 2.7.0 - one-time: 1.0.0 - readable-stream: 3.6.2 - safe-stable-stringify: 2.5.0 - stack-trace: 0.0.10 - triple-beam: 1.4.1 - winston-transport: 4.9.0 - wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 @@ -8409,8 +7878,6 @@ snapshots: buffer-crc32: 0.2.13 pend: 1.2.0 - ylru@1.4.0: {} - yoctocolors-cjs@2.1.3: {} zod@3.25.76: {} diff --git a/readme.hints.md b/readme.hints.md index f673fb9..19fc3e4 100644 --- a/readme.hints.md +++ b/readme.hints.md @@ -2,12 +2,18 @@ ## Architecture -This project uses **Ollama** as the runtime framework for serving AI models. This provides: +This project uses **Ollama** and **vLLM** as runtime frameworks for serving AI models: +### Ollama-based Images (MiniCPM-V, Qwen3-VL) - Automatic model download and caching - Unified REST API (compatible with OpenAI format) - Built-in quantization support -- GPU/CPU auto-detection +- GPU auto-detection + +### vLLM-based Images (Nanonets-OCR) +- High-performance inference server +- OpenAI-compatible API +- Optimized for VLM workloads ## Model Details @@ -24,18 +30,24 @@ This project uses **Ollama** as the runtime framework for serving AI models. Thi |------|---------------| | Full precision (bf16) | 18GB | | int4 quantized | 9GB | -| GGUF (CPU) | 8GB RAM | ## Container Startup Flow +### Ollama-based containers 1. `docker-entrypoint.sh` starts Ollama server in background 2. Waits for server to be ready 3. Checks if model already exists in volume 4. Pulls model if not present 5. Keeps container running +### vLLM-based containers +1. vLLM server starts with model auto-download +2. Health check endpoint available at `/health` +3. OpenAI-compatible API at `/v1/chat/completions` + ## Volume Persistence +### Ollama volumes Mount `/root/.ollama` to persist downloaded models: ```bash @@ -44,9 +56,16 @@ Mount `/root/.ollama` to persist downloaded models: Without this volume, the model will be re-downloaded on each container start (~5GB download). +### vLLM/HuggingFace volumes +Mount `/root/.cache/huggingface` for model caching: + +```bash +-v hf-cache:/root/.cache/huggingface +``` + ## API Endpoints -All endpoints follow the Ollama API specification: +### Ollama API (MiniCPM-V, Qwen3-VL) | Endpoint | Method | Description | |----------|--------|-------------| @@ -56,192 +75,23 @@ All endpoints follow the Ollama API specification: | `/api/pull` | POST | Pull a model | | `/api/show` | POST | Show model info | -## GPU Detection +### vLLM API (Nanonets-OCR) -The GPU variant uses Ollama's automatic GPU detection. For CPU-only mode, we set: - -```dockerfile -ENV CUDA_VISIBLE_DEVICES="" -``` - -This forces Ollama to use CPU inference even if GPU is available. +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/health` | GET | Health check | +| `/v1/models` | GET | List available models | +| `/v1/chat/completions` | POST | OpenAI-compatible chat completions | ## Health Checks -Both variants include Docker health checks: +All containers include Docker health checks: ```dockerfile HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ CMD curl -f http://localhost:11434/api/tags || exit 1 ``` -CPU variant has longer `start-period` (120s) due to slower startup. - -## PaddleOCR-VL (Recommended) - -### Overview - -PaddleOCR-VL is a 0.9B parameter Vision-Language Model specifically optimized for document parsing. It replaces the older PP-Structure approach with native VLM understanding. - -**Key advantages over PP-Structure:** -- Native table understanding (no HTML parsing needed) -- 109 language support -- Better handling of complex multi-row tables -- Structured Markdown/JSON output - -### Docker Images - -| Tag | Description | -|-----|-------------| -| `paddleocr-vl` | GPU variant using vLLM (recommended) | -| `paddleocr-vl-cpu` | CPU variant using transformers | - -### API Endpoints (OpenAI-compatible) - -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/health` | GET | Health check with model info | -| `/v1/models` | GET | List available models | -| `/v1/chat/completions` | POST | OpenAI-compatible chat completions | -| `/ocr` | POST | Legacy OCR endpoint | - -### Request/Response Format - -**POST /v1/chat/completions (OpenAI-compatible)** -```json -{ - "model": "paddleocr-vl", - "messages": [ - { - "role": "user", - "content": [ - {"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}, - {"type": "text", "text": "Table Recognition:"} - ] - } - ], - "temperature": 0.0, - "max_tokens": 8192 -} -``` - -**Task Prompts:** -- `"OCR:"` - Text recognition -- `"Table Recognition:"` - Table extraction (returns markdown) -- `"Formula Recognition:"` - Formula extraction -- `"Chart Recognition:"` - Chart extraction - -**Response** -```json -{ - "id": "chatcmpl-...", - "object": "chat.completion", - "choices": [ - { - "index": 0, - "message": { - "role": "assistant", - "content": "| Date | Description | Amount |\n|---|---|---|\n| 2021-06-01 | GITLAB INC | -119.96 |" - }, - "finish_reason": "stop" - } - ] -} -``` - -### Environment Variables - -| Variable | Default | Description | -|----------|---------|-------------| -| `MODEL_NAME` | `PaddlePaddle/PaddleOCR-VL` | Model to load | -| `HOST` | `0.0.0.0` | Server host | -| `PORT` | `8000` | Server port | -| `MAX_BATCHED_TOKENS` | `16384` | vLLM max batch tokens | -| `GPU_MEMORY_UTILIZATION` | `0.9` | GPU memory usage (0-1) | - -### Performance - -- **GPU (vLLM)**: ~2-5 seconds per page -- **CPU**: ~30-60 seconds per page - ---- - -## Adding New Models - -To add a new model variant: - -1. Create `Dockerfile_` -2. Set `MODEL_NAME` environment variable -3. Update `build-images.sh` with new build target -4. Add documentation to `readme.md` - -## Troubleshooting - -### Model download hangs - -Check container logs: -```bash -docker logs -f -``` - -The model download is ~5GB and may take several minutes. - -### Out of memory - -- GPU: Use int4 quantized version or add more VRAM -- CPU: Increase container memory limit: `--memory=16g` - -### API not responding - -1. Check if container is healthy: `docker ps` -2. Check logs for errors: `docker logs ` -3. Verify port mapping: `curl localhost:11434/api/tags` - -## CI/CD Integration - -Build and push using npmci: - -```bash -npmci docker login -npmci docker build -npmci docker push code.foss.global -``` - -## Multi-Pass Extraction Strategy - -The bank statement extraction uses a dual-VLM consensus approach: - -### Architecture: Dual-VLM Consensus - -| VLM | Model | Purpose | -|-----|-------|---------| -| **MiniCPM-V 4.5** | 8B params | Primary visual extraction | -| **PaddleOCR-VL** | 0.9B params | Table-specialized extraction | - -### Extraction Strategy - -1. **Pass 1**: MiniCPM-V visual extraction (images → JSON) -2. **Pass 2**: PaddleOCR-VL table recognition (images → markdown → JSON) -3. **Consensus**: If Pass 1 == Pass 2 → Done (fast path) -4. **Pass 3+**: MiniCPM-V visual if no consensus - -### Why Dual-VLM Works - -- **Different architectures**: Two independent models cross-check each other -- **Specialized strengths**: PaddleOCR-VL optimized for tables, MiniCPM-V for general vision -- **No structure loss**: Both VLMs see the original images directly -- **Fast consensus**: Most documents complete in 2 passes when VLMs agree - -### Comparison vs Old PP-Structure Approach - -| Approach | Bank Statement Result | Issue | -|----------|----------------------|-------| -| MiniCPM-V Visual | 28 transactions ✓ | - | -| PP-Structure HTML + Visual | 13 transactions ✗ | HTML merged rows incorrectly | -| PaddleOCR-VL Table | 28 transactions ✓ | Native table understanding | - -**Key insight**: PP-Structure's HTML output loses structure for complex tables. PaddleOCR-VL's native VLM approach maintains table integrity. - --- ## Nanonets-OCR-s @@ -254,7 +104,7 @@ Nanonets-OCR-s is a Qwen2.5-VL-3B model fine-tuned specifically for document OCR - Based on Qwen2.5-VL-3B (~4B parameters) - Fine-tuned for document OCR - Outputs markdown with semantic HTML tags -- ~8-10GB VRAM (fits comfortably in 16GB) +- ~10GB VRAM ### Docker Images @@ -305,7 +155,7 @@ Page numbers should be wrapped in brackets. Ex: 14. ### Performance - **GPU (vLLM)**: ~3-8 seconds per page -- **VRAM usage**: ~8-10GB +- **VRAM usage**: ~10GB ### Two-Stage Pipeline (Nanonets + Qwen3) @@ -332,6 +182,76 @@ docker start minicpm-test --- +## Multi-Pass Extraction Strategy + +The bank statement extraction uses a dual-VLM consensus approach: + +### Architecture: Dual-VLM Consensus + +| VLM | Model | Purpose | +|-----|-------|---------| +| **MiniCPM-V 4.5** | 8B params | Primary visual extraction | +| **Nanonets-OCR-s** | ~4B params | Document OCR with semantic output | + +### Extraction Strategy + +1. **Pass 1**: MiniCPM-V visual extraction (images → JSON) +2. **Pass 2**: Nanonets-OCR semantic extraction (images → markdown → JSON) +3. **Consensus**: If Pass 1 == Pass 2 → Done (fast path) +4. **Pass 3+**: MiniCPM-V visual if no consensus + +### Why Dual-VLM Works + +- **Different architectures**: Two independent models cross-check each other +- **Specialized strengths**: Nanonets-OCR-s optimized for document structure, MiniCPM-V for general vision +- **No structure loss**: Both VLMs see the original images directly +- **Fast consensus**: Most documents complete in 2 passes when VLMs agree + +--- + +## Adding New Models + +To add a new model variant: + +1. Create `Dockerfile____VRAM` +2. Set `MODEL_NAME` environment variable +3. Update `build-images.sh` with new build target +4. Add documentation to `readme.md` + +## Troubleshooting + +### Model download hangs + +Check container logs: +```bash +docker logs -f +``` + +The model download is ~5GB and may take several minutes. + +### Out of memory + +- GPU: Use a lighter model variant or upgrade VRAM +- Add more GPU memory: Consider multi-GPU setup + +### API not responding + +1. Check if container is healthy: `docker ps` +2. Check logs for errors: `docker logs ` +3. Verify port mapping: `curl localhost:11434/api/tags` + +## CI/CD Integration + +Build and push using npmci: + +```bash +npmci docker login +npmci docker build +npmci docker push code.foss.global +``` + +--- + ## Related Resources - [Ollama Documentation](https://ollama.ai/docs) diff --git a/readme.md b/readme.md index 6c59a7a..a24f3b2 100644 --- a/readme.md +++ b/readme.md @@ -1,8 +1,8 @@ # @host.today/ht-docker-ai 🚀 -Production-ready Docker images for state-of-the-art AI Vision-Language Models. Run powerful multimodal AI locally with GPU acceleration or CPU fallback—**no cloud API keys required**. +Production-ready Docker images for state-of-the-art AI Vision-Language Models. Run powerful multimodal AI locally with GPU acceleration—**no cloud API keys required**. -> 🔥 **Four VLMs, one registry.** From lightweight document OCR to GPT-4o-level vision understanding—pick the right tool for your task. +> 🔥 **Three VLMs, one registry.** From lightweight document OCR to GPT-4o-level vision understanding—pick the right tool for your task. ## Issue Reporting and Security @@ -12,12 +12,11 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community ## 🎯 What's Included -| Model | Parameters | Best For | API | Port | -|-------|-----------|----------|-----|------| -| **MiniCPM-V 4.5** | 8B | General vision understanding, multi-image analysis | Ollama-compatible | 11434 | -| **PaddleOCR-VL** | 0.9B | Document parsing, table extraction, structured OCR | OpenAI-compatible | 8000 | -| **Nanonets-OCR-s** | ~4B | Document OCR with semantic markdown output | OpenAI-compatible | 8000 | -| **Qwen3-VL-30B** | 30B (A3B) | Advanced visual agents, code generation from images | Ollama-compatible | 11434 | +| Model | Parameters | Best For | API | Port | VRAM | +|-------|-----------|----------|-----|------|------| +| **MiniCPM-V 4.5** | 8B | General vision understanding, multi-image analysis | Ollama-compatible | 11434 | ~9GB | +| **Nanonets-OCR-s** | ~4B | Document OCR with semantic markdown output | OpenAI-compatible | 8000 | ~10GB | +| **Qwen3-VL-30B** | 30B (A3B) | Advanced visual agents, code generation from images | Ollama-compatible | 11434 | ~20GB | --- @@ -27,14 +26,11 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community code.foss.global/host.today/ht-docker-ai: ``` -| Tag | Model | Hardware | Port | -|-----|-------|----------|------| -| `minicpm45v` / `latest` | MiniCPM-V 4.5 | NVIDIA GPU (9-18GB VRAM) | 11434 | -| `minicpm45v-cpu` | MiniCPM-V 4.5 | CPU only (8GB+ RAM) | 11434 | -| `paddleocr-vl` / `paddleocr-vl-gpu` | PaddleOCR-VL | NVIDIA GPU | 8000 | -| `paddleocr-vl-cpu` | PaddleOCR-VL | CPU only | 8000 | -| `nanonets-ocr` | Nanonets-OCR-s | NVIDIA GPU (8-10GB VRAM) | 8000 | -| `qwen3vl` | Qwen3-VL-30B-A3B | NVIDIA GPU (~20GB VRAM) | 11434 | +| Tag | Model | Runtime | Port | VRAM | +|-----|-------|---------|------|------| +| `minicpm45v` / `latest` | MiniCPM-V 4.5 | Ollama | 11434 | ~9GB | +| `nanonets-ocr` | Nanonets-OCR-s | vLLM | 8000 | ~10GB | +| `qwen3vl` | Qwen3-VL-30B-A3B | Ollama | 11434 | ~20GB | --- @@ -44,7 +40,6 @@ A GPT-4o level multimodal LLM from OpenBMB—handles image understanding, OCR, m ### Quick Start -**GPU (Recommended):** ```bash docker run -d \ --name minicpm \ @@ -54,15 +49,6 @@ docker run -d \ code.foss.global/host.today/ht-docker-ai:minicpm45v ``` -**CPU Only:** -```bash -docker run -d \ - --name minicpm \ - -p 11434:11434 \ - -v ollama-data:/root/.ollama \ - code.foss.global/host.today/ht-docker-ai:minicpm45v-cpu -``` - > 💡 **Pro tip:** Mount the volume to persist downloaded models (~5GB). Without it, models re-download on every container start. ### API Examples @@ -95,103 +81,10 @@ curl http://localhost:11434/api/chat -d '{ ### Hardware Requirements -| Variant | VRAM/RAM | Notes | -|---------|----------|-------| -| GPU (int4 quantized) | 9GB VRAM | Recommended for most use cases | -| GPU (full precision) | 18GB VRAM | Maximum quality | -| CPU (GGUF) | 8GB+ RAM | Slower but accessible | - ---- - -## 📄 PaddleOCR-VL - -A specialized **0.9B Vision-Language Model** optimized for document parsing. Native support for tables, formulas, charts, and text extraction in **109 languages**. - -### Quick Start - -**GPU:** -```bash -docker run -d \ - --name paddleocr \ - --gpus all \ - -p 8000:8000 \ - -v hf-cache:/root/.cache/huggingface \ - code.foss.global/host.today/ht-docker-ai:paddleocr-vl -``` - -**CPU:** -```bash -docker run -d \ - --name paddleocr \ - -p 8000:8000 \ - -v hf-cache:/root/.cache/huggingface \ - code.foss.global/host.today/ht-docker-ai:paddleocr-vl-cpu -``` - -### OpenAI-Compatible API - -PaddleOCR-VL exposes a fully OpenAI-compatible `/v1/chat/completions` endpoint: - -```bash -curl http://localhost:8000/v1/chat/completions \ - -H "Content-Type: application/json" \ - -d '{ - "model": "paddleocr-vl", - "messages": [{ - "role": "user", - "content": [ - {"type": "image_url", "image_url": {"url": "data:image/png;base64,"}}, - {"type": "text", "text": "Table Recognition:"} - ] - }], - "max_tokens": 8192 - }' -``` - -### Task Prompts - -| Prompt | Output | Use Case | -|--------|--------|----------| -| `OCR:` | Plain text | General text extraction | -| `Table Recognition:` | Markdown table | Invoices, bank statements, spreadsheets | -| `Formula Recognition:` | LaTeX | Math equations, scientific notation | -| `Chart Recognition:` | Description | Graphs and visualizations | - -### API Endpoints - -| Endpoint | Method | Description | -|----------|--------|-------------| -| `/health` | GET | Health check with model/device info | -| `/formats` | GET | Supported image formats and input methods | -| `/v1/models` | GET | List available models | -| `/v1/chat/completions` | POST | OpenAI-compatible chat completions | -| `/ocr` | POST | Legacy OCR endpoint | - -### Image Input Methods - -PaddleOCR-VL accepts images in multiple formats: - -```javascript -// Base64 data URL -"data:image/png;base64,iVBORw0KGgo..." - -// HTTP URL -"https://example.com/document.png" - -// Raw base64 -"iVBORw0KGgo..." -``` - -**Supported formats:** PNG, JPEG, WebP, BMP, GIF, TIFF - -**Optimal resolution:** 1080p–2K. Images are automatically scaled for best results. - -### Performance - -| Mode | Speed per Page | -|------|----------------| -| GPU (CUDA) | 2–5 seconds | -| CPU | 30–60 seconds | +| Mode | VRAM Required | +|------|---------------| +| int4 quantized | 9GB | +| Full precision (bf16) | 18GB | --- @@ -203,7 +96,7 @@ A **Qwen2.5-VL-3B** model fine-tuned specifically for document OCR. Outputs stru - 📝 **Semantic output:** Tables → HTML, equations → LaTeX, watermarks/page numbers → tagged - 🌍 **Multilingual:** Inherits Qwen's broad language support -- ⚡ **Efficient:** ~8-10GB VRAM, runs great on consumer GPUs +- ⚡ **Efficient:** ~10GB VRAM, runs great on consumer GPUs - 🔌 **OpenAI-compatible:** Drop-in replacement for existing pipelines ### Quick Start @@ -253,7 +146,7 @@ Nanonets-OCR-s returns markdown with semantic tags: | Metric | Value | |--------|-------| | Speed | 3–8 seconds per page | -| VRAM | ~8-10GB | +| VRAM | ~10GB | --- @@ -329,27 +222,11 @@ services: capabilities: [gpu] restart: unless-stopped - # Document parsing / OCR (table specialist) - paddleocr: - image: code.foss.global/host.today/ht-docker-ai:paddleocr-vl - ports: - - "8000:8000" - volumes: - - hf-cache:/root/.cache/huggingface - deploy: - resources: - reservations: - devices: - - driver: nvidia - count: 1 - capabilities: [gpu] - restart: unless-stopped - # Document OCR with semantic output nanonets: image: code.foss.global/host.today/ht-docker-ai:nanonets-ocr ports: - - "8001:8000" + - "8000:8000" volumes: - hf-cache:/root/.cache/huggingface deploy: @@ -378,11 +255,11 @@ volumes: | `OLLAMA_HOST` | `0.0.0.0` | API bind address | | `OLLAMA_ORIGINS` | `*` | Allowed CORS origins | -### PaddleOCR-VL & Nanonets-OCR (vLLM-based) +### Nanonets-OCR (vLLM-based) | Variable | Default | Description | |----------|---------|-------------| -| `MODEL_NAME` | Model-specific | HuggingFace model ID | +| `MODEL_NAME` | `nanonets/Nanonets-OCR-s` | HuggingFace model ID | | `HOST` | `0.0.0.0` | API bind address | | `PORT` | `8000` | API port | | `MAX_MODEL_LEN` | `8192` | Maximum sequence length | @@ -397,7 +274,7 @@ volumes: For production document extraction, consider using multiple models together: 1. **Pass 1:** MiniCPM-V visual extraction (images → JSON) -2. **Pass 2:** PaddleOCR-VL table recognition (images → markdown → JSON) +2. **Pass 2:** Nanonets-OCR semantic extraction (images → markdown → JSON) 3. **Consensus:** If results match → Done (fast path) 4. **Pass 3+:** Additional visual passes if needed @@ -406,7 +283,7 @@ This dual-VLM approach catches extraction errors that single models miss. ### Why Multi-Model Works - **Different architectures:** Independent models cross-validate each other -- **Specialized strengths:** PaddleOCR-VL excels at tables; MiniCPM-V handles general vision +- **Specialized strengths:** Nanonets-OCR-s excels at document structure; MiniCPM-V handles general vision - **Native processing:** All VLMs see original images—no intermediate structure loss ### Model Selection Guide @@ -414,7 +291,6 @@ This dual-VLM approach catches extraction errors that single models miss. | Task | Recommended Model | |------|-------------------| | General image understanding | MiniCPM-V 4.5 | -| Table extraction from documents | PaddleOCR-VL | | Document OCR with structure preservation | Nanonets-OCR-s | | Complex visual reasoning / code generation | Qwen3-VL-30B | | Multi-image analysis | MiniCPM-V 4.5 | diff --git a/test/helpers/docker.ts b/test/helpers/docker.ts index dec42ef..ffb17ae 100644 --- a/test/helpers/docker.ts +++ b/test/helpers/docker.ts @@ -32,10 +32,10 @@ export const IMAGES = { healthTimeout: 120000, } as IImageConfig, - // Nanonets-OCR-s - Document OCR optimized VLM (Qwen2.5-VL-3B fine-tuned) + // Nanonets-OCR2-3B - Document OCR optimized VLM (Qwen2.5-VL-3B fine-tuned, Oct 2025) nanonetsOcr: { name: 'nanonets-ocr', - dockerfile: 'Dockerfile_nanonets_ocr', + dockerfile: 'Dockerfile_nanonets_vllm_gpu_VRAM10GB', buildContext: '.', containerName: 'nanonets-test', ports: ['8000:8000'], @@ -340,12 +340,12 @@ export async function ensureQwen3Vl(): Promise { } /** - * Ensure Nanonets-OCR-s service is running (via vLLM) - * Document OCR optimized VLM based on Qwen2.5-VL-3B + * Ensure Nanonets-OCR2-3B service is running (via vLLM) + * Document OCR optimized VLM based on Qwen2.5-VL-3B (Oct 2025 release) */ export async function ensureNanonetsOcr(): Promise { if (!isGpuAvailable()) { - console.log('[Docker] WARNING: Nanonets-OCR-s requires GPU, but none detected'); + console.log('[Docker] WARNING: Nanonets-OCR2-3B requires GPU, but none detected'); } return ensureService(IMAGES.nanonetsOcr); } diff --git a/test/test.bankstatements.nanonets.ts b/test/test.bankstatements.nanonets.ts index f4b4acc..9c8ef81 100644 --- a/test/test.bankstatements.nanonets.ts +++ b/test/test.bankstatements.nanonets.ts @@ -1,7 +1,7 @@ /** - * Bank statement extraction using Nanonets-OCR-s + GPT-OSS 20B (sequential two-stage pipeline) + * Bank statement extraction using Nanonets-OCR2-3B + GPT-OSS 20B (sequential two-stage pipeline) * - * Stage 1: Nanonets-OCR-s converts ALL document pages to markdown (stop after completion) + * Stage 1: Nanonets-OCR2-3B converts ALL document pages to markdown (stop after completion) * Stage 2: GPT-OSS 20B extracts structured JSON from saved markdown (after Nanonets stops) * * This approach avoids GPU contention by running services sequentially. @@ -14,7 +14,7 @@ import * as os from 'os'; import { ensureNanonetsOcr, ensureMiniCpm, removeContainer, isContainerRunning } from './helpers/docker.js'; const NANONETS_URL = 'http://localhost:8000/v1'; -const NANONETS_MODEL = 'nanonets/Nanonets-OCR-s'; +const NANONETS_MODEL = 'nanonets/Nanonets-OCR2-3B'; const OLLAMA_URL = 'http://localhost:11434'; const EXTRACTION_MODEL = 'gpt-oss:20b'; @@ -69,28 +69,11 @@ function estimateVisualTokens(width: number, height: number): number { } /** - * Batch images to fit within context window + * Process images one page at a time for reliability */ function batchImages(images: IImageData[]): IImageData[][] { - const batches: IImageData[][] = []; - let currentBatch: IImageData[] = []; - let currentTokens = 0; - - for (const img of images) { - const imgTokens = estimateVisualTokens(img.width, img.height); - - if (currentTokens + imgTokens > MAX_VISUAL_TOKENS && currentBatch.length > 0) { - batches.push(currentBatch); - currentBatch = [img]; - currentTokens = imgTokens; - } else { - currentBatch.push(img); - currentTokens += imgTokens; - } - } - if (currentBatch.length > 0) batches.push(currentBatch); - - return batches; + // One page per batch for reliable processing + return images.map(img => [img]); } /** @@ -171,6 +154,7 @@ async function convertBatchToMarkdown(batch: IImageData[]): Promise { max_tokens: 4096 * batch.length, // Scale output tokens with batch size temperature: 0.0, }), + signal: AbortSignal.timeout(600000), // 10 minute timeout for OCR }); const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); diff --git a/test/test.invoices.extraction.ts b/test/test.invoices.extraction.ts new file mode 100644 index 0000000..7f31558 --- /dev/null +++ b/test/test.invoices.extraction.ts @@ -0,0 +1,436 @@ +/** + * Invoice extraction tuning - uses pre-generated markdown files + * + * Skips OCR stage, only runs GPT-OSS extraction on existing .debug.md files. + * Use this to quickly iterate on extraction prompts and logic. + * + * Run with: tstest test/test.invoices.extraction.ts --verbose + */ +import { tap, expect } from '@git.zone/tstest/tapbundle'; +import * as fs from 'fs'; +import * as path from 'path'; +import { ensureMiniCpm } from './helpers/docker.js'; + +const OLLAMA_URL = 'http://localhost:11434'; +const EXTRACTION_MODEL = 'gpt-oss:20b'; + +// Test these specific invoices (must have .debug.md files) +const TEST_INVOICES = [ + 'consensus_2021-09', + 'hetzner_2022-04', + 'qonto_2021-08', + 'qonto_2021-09', +]; + +interface IInvoice { + invoice_number: string; + invoice_date: string; + vendor_name: string; + currency: string; + net_amount: number; + vat_amount: number; + total_amount: number; +} + +interface ITestCase { + name: string; + markdownPath: string; + jsonPath: string; +} + +// JSON extraction prompt for GPT-OSS 20B (sent AFTER the invoice text is provided) +const JSON_EXTRACTION_PROMPT = `Extract key fields from the invoice. Return ONLY valid JSON. + +WHERE TO FIND DATA: +- invoice_number, invoice_date, vendor_name: Look in the HEADER section at the TOP of PAGE 1 (near "Invoice no.", "Invoice date:", "Rechnungsnummer") +- net_amount, vat_amount, total_amount: Look in the SUMMARY section at the BOTTOM (look for "Total", "Amount due", "Gesamtbetrag") + +RULES: +1. invoice_number: Extract ONLY the value (e.g., "R0015632540"), NOT the label "Invoice no." +2. invoice_date: Convert to YYYY-MM-DD format (e.g., "14/04/2022" → "2022-04-14") +3. vendor_name: The company issuing the invoice +4. currency: EUR, USD, or GBP +5. net_amount: Total before tax +6. vat_amount: Tax amount +7. total_amount: Final total with tax + +JSON only: +{"invoice_number":"X","invoice_date":"YYYY-MM-DD","vendor_name":"X","currency":"EUR","net_amount":0,"vat_amount":0,"total_amount":0}`; + +/** + * Ensure GPT-OSS 20B model is available + */ +async function ensureExtractionModel(): Promise { + try { + const response = await fetch(`${OLLAMA_URL}/api/tags`); + if (response.ok) { + const data = await response.json(); + const models = data.models || []; + if (models.some((m: { name: string }) => m.name === EXTRACTION_MODEL)) { + console.log(` [Ollama] Model available: ${EXTRACTION_MODEL}`); + return true; + } + } + } catch { + return false; + } + + console.log(` [Ollama] Pulling ${EXTRACTION_MODEL}...`); + const pullResponse = await fetch(`${OLLAMA_URL}/api/pull`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name: EXTRACTION_MODEL, stream: false }), + }); + + return pullResponse.ok; +} + +/** + * Parse amount from string (handles European format) + */ +function parseAmount(s: string | number | undefined): number { + if (s === undefined || s === null) return 0; + if (typeof s === 'number') return s; + const match = s.match(/([\d.,]+)/); + if (!match) return 0; + const numStr = match[1]; + const normalized = numStr.includes(',') && numStr.indexOf(',') > numStr.lastIndexOf('.') + ? numStr.replace(/\./g, '').replace(',', '.') + : numStr.replace(/,/g, ''); + return parseFloat(normalized) || 0; +} + +/** + * Extract invoice number - minimal normalization + */ +function extractInvoiceNumber(s: string | undefined): string { + if (!s) return ''; + return s.replace(/\*\*/g, '').replace(/`/g, '').trim(); +} + +/** + * Extract date (YYYY-MM-DD) from response + */ +function extractDate(s: string | undefined): string { + if (!s) return ''; + let clean = s.replace(/\*\*/g, '').replace(/`/g, '').trim(); + const isoMatch = clean.match(/(\d{4}-\d{2}-\d{2})/); + if (isoMatch) return isoMatch[1]; + const dmyMatch = clean.match(/(\d{1,2})[\/.](\d{1,2})[\/.](\d{4})/); + if (dmyMatch) { + return `${dmyMatch[3]}-${dmyMatch[2].padStart(2, '0')}-${dmyMatch[1].padStart(2, '0')}`; + } + return clean.replace(/[^\d-]/g, '').trim(); +} + +/** + * Extract currency + */ +function extractCurrency(s: string | undefined): string { + if (!s) return 'EUR'; + const upper = s.toUpperCase(); + if (upper.includes('EUR') || upper.includes('€')) return 'EUR'; + if (upper.includes('USD') || upper.includes('$')) return 'USD'; + if (upper.includes('GBP') || upper.includes('£')) return 'GBP'; + return 'EUR'; +} + +/** + * Extract JSON from response + */ +function extractJsonFromResponse(response: string): Record | null { + let cleanResponse = response.replace(/[\s\S]*?<\/think>/g, '').trim(); + const codeBlockMatch = cleanResponse.match(/```(?:json)?\s*([\s\S]*?)```/); + const jsonStr = codeBlockMatch ? codeBlockMatch[1].trim() : cleanResponse; + + try { + return JSON.parse(jsonStr); + } catch { + const jsonMatch = jsonStr.match(/\{[\s\S]*\}/); + if (jsonMatch) { + try { + return JSON.parse(jsonMatch[0]); + } catch { + return null; + } + } + return null; + } +} + +/** + * Parse JSON response into IInvoice + */ +function parseJsonToInvoice(response: string): IInvoice | null { + const parsed = extractJsonFromResponse(response); + if (!parsed) return null; + + return { + invoice_number: extractInvoiceNumber(String(parsed.invoice_number || '')), + invoice_date: extractDate(String(parsed.invoice_date || '')), + vendor_name: String(parsed.vendor_name || '').replace(/\*\*/g, '').replace(/`/g, '').trim(), + currency: extractCurrency(String(parsed.currency || '')), + net_amount: parseAmount(parsed.net_amount as string | number), + vat_amount: parseAmount(parsed.vat_amount as string | number), + total_amount: parseAmount(parsed.total_amount as string | number), + }; +} + +/** + * Extract invoice from markdown using GPT-OSS 20B (streaming) + */ +async function extractInvoiceFromMarkdown(markdown: string, queryId: string): Promise { + const startTime = Date.now(); + + console.log(` [${queryId}] Invoice: ${markdown.length} chars, Prompt: ${JSON_EXTRACTION_PROMPT.length} chars`); + + const response = await fetch(`${OLLAMA_URL}/api/chat`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + model: EXTRACTION_MODEL, + messages: [ + { role: 'user', content: 'Hi there, how are you?' }, + { role: 'assistant', content: 'Good, how can I help you today?' }, + { role: 'user', content: `Here is an invoice document:\n\n${markdown}` }, + { role: 'assistant', content: 'I have read the invoice document you provided. I can see all the text content. What would you like me to do with it?' }, + { role: 'user', content: JSON_EXTRACTION_PROMPT }, + ], + stream: true, + }), + signal: AbortSignal.timeout(120000), // 2 min timeout + }); + + if (!response.ok) { + const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); + console.log(` [${queryId}] ERROR: ${response.status} (${elapsed}s)`); + throw new Error(`Ollama API error: ${response.status}`); + } + + // Stream the response + let content = ''; + let thinkingContent = ''; + let thinkingStarted = false; + let outputStarted = false; + const reader = response.body!.getReader(); + const decoder = new TextDecoder(); + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + const chunk = decoder.decode(value, { stream: true }); + + for (const line of chunk.split('\n').filter(l => l.trim())) { + try { + const json = JSON.parse(line); + + const thinking = json.message?.thinking || ''; + if (thinking) { + if (!thinkingStarted) { + process.stdout.write(` [${queryId}] THINKING: `); + thinkingStarted = true; + } + process.stdout.write(thinking); + thinkingContent += thinking; + } + + const token = json.message?.content || ''; + if (token) { + if (!outputStarted) { + if (thinkingStarted) process.stdout.write('\n'); + process.stdout.write(` [${queryId}] OUTPUT: `); + outputStarted = true; + } + process.stdout.write(token); + content += token; + } + } catch { + // Ignore parse errors for partial chunks + } + } + } + } finally { + if (thinkingStarted || outputStarted) process.stdout.write('\n'); + } + + const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); + console.log(` [${queryId}] Done: ${thinkingContent.length} thinking, ${content.length} output (${elapsed}s)`); + + return parseJsonToInvoice(content); +} + +/** + * Normalize date to YYYY-MM-DD + */ +function normalizeDate(dateStr: string | null): string { + if (!dateStr) return ''; + if (/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) return dateStr; + + const monthMap: Record = { + JAN: '01', FEB: '02', MAR: '03', APR: '04', MAY: '05', JUN: '06', + JUL: '07', AUG: '08', SEP: '09', OCT: '10', NOV: '11', DEC: '12', + }; + + let match = dateStr.match(/^(\d{1,2})-([A-Z]{3})-(\d{4})$/i); + if (match) { + return `${match[3]}-${monthMap[match[2].toUpperCase()] || '01'}-${match[1].padStart(2, '0')}`; + } + + match = dateStr.match(/^(\d{1,2})[\/.](\d{1,2})[\/.](\d{4})$/); + if (match) { + return `${match[3]}-${match[2].padStart(2, '0')}-${match[1].padStart(2, '0')}`; + } + + return dateStr; +} + +/** + * Normalize invoice number for comparison (remove spaces, lowercase) + */ +function normalizeInvoiceNumber(s: string): string { + return s.replace(/\s+/g, '').toLowerCase(); +} + +/** + * Compare extracted invoice against expected + */ +function compareInvoice( + extracted: IInvoice, + expected: IInvoice +): { match: boolean; errors: string[] } { + const errors: string[] = []; + + // Invoice number - normalize spaces for comparison + const extNum = normalizeInvoiceNumber(extracted.invoice_number || ''); + const expNum = normalizeInvoiceNumber(expected.invoice_number || ''); + if (extNum !== expNum) { + errors.push(`invoice_number: expected "${expected.invoice_number}", got "${extracted.invoice_number}"`); + } + + if (normalizeDate(extracted.invoice_date) !== normalizeDate(expected.invoice_date)) { + errors.push(`invoice_date: expected "${expected.invoice_date}", got "${extracted.invoice_date}"`); + } + + if (Math.abs(extracted.total_amount - expected.total_amount) > 0.02) { + errors.push(`total_amount: expected ${expected.total_amount}, got ${extracted.total_amount}`); + } + + if (extracted.currency?.toUpperCase() !== expected.currency?.toUpperCase()) { + errors.push(`currency: expected "${expected.currency}", got "${extracted.currency}"`); + } + + return { match: errors.length === 0, errors }; +} + +/** + * Find test cases with existing debug markdown + */ +function findTestCases(): ITestCase[] { + const invoicesDir = path.join(process.cwd(), '.nogit/invoices'); + if (!fs.existsSync(invoicesDir)) return []; + + const testCases: ITestCase[] = []; + + for (const invoiceName of TEST_INVOICES) { + const markdownPath = path.join(invoicesDir, `${invoiceName}.debug.md`); + const jsonPath = path.join(invoicesDir, `${invoiceName}.json`); + + if (fs.existsSync(markdownPath) && fs.existsSync(jsonPath)) { + testCases.push({ + name: invoiceName, + markdownPath, + jsonPath, + }); + } else { + if (!fs.existsSync(markdownPath)) { + console.warn(`Warning: Missing markdown: ${markdownPath}`); + } + if (!fs.existsSync(jsonPath)) { + console.warn(`Warning: Missing JSON: ${jsonPath}`); + } + } + } + + return testCases; +} + +// ============ TESTS ============ + +const testCases = findTestCases(); +console.log(`\n========================================`); +console.log(` EXTRACTION TUNING TEST`); +console.log(` (Skips OCR, uses existing .debug.md)`); +console.log(`========================================`); +console.log(` Testing ${testCases.length} invoices:`); +for (const tc of testCases) { + console.log(` - ${tc.name}`); +} +console.log(`========================================\n`); + +tap.test('Setup Ollama + GPT-OSS 20B', async () => { + const ollamaOk = await ensureMiniCpm(); + expect(ollamaOk).toBeTrue(); + + const extractionOk = await ensureExtractionModel(); + expect(extractionOk).toBeTrue(); +}); + +let passedCount = 0; +let failedCount = 0; + +for (const tc of testCases) { + tap.test(`Extract ${tc.name}`, async () => { + const expected: IInvoice = JSON.parse(fs.readFileSync(tc.jsonPath, 'utf-8')); + const markdown = fs.readFileSync(tc.markdownPath, 'utf-8'); + + console.log(`\n ========================================`); + console.log(` === ${tc.name} ===`); + console.log(` ========================================`); + console.log(` EXPECTED: ${expected.invoice_number} | ${expected.invoice_date} | ${expected.total_amount} ${expected.currency}`); + console.log(` Markdown: ${markdown.length} chars`); + + const startTime = Date.now(); + + const extracted = await extractInvoiceFromMarkdown(markdown, tc.name); + + if (!extracted) { + failedCount++; + console.log(`\n Result: ✗ FAILED TO PARSE (${((Date.now() - startTime) / 1000).toFixed(1)}s)`); + return; + } + + const elapsedMs = Date.now() - startTime; + + console.log(` EXTRACTED: ${extracted.invoice_number} | ${extracted.invoice_date} | ${extracted.total_amount} ${extracted.currency}`); + + const result = compareInvoice(extracted, expected); + + if (result.match) { + passedCount++; + console.log(`\n Result: ✓ MATCH (${(elapsedMs / 1000).toFixed(1)}s)`); + } else { + failedCount++; + console.log(`\n Result: ✗ MISMATCH (${(elapsedMs / 1000).toFixed(1)}s)`); + console.log(` ERRORS:`); + result.errors.forEach(e => console.log(` - ${e}`)); + } + }); +} + +tap.test('Summary', async () => { + const totalInvoices = testCases.length; + const accuracy = totalInvoices > 0 ? (passedCount / totalInvoices) * 100 : 0; + + console.log(`\n========================================`); + console.log(` Extraction Tuning Summary`); + console.log(`========================================`); + console.log(` Model: ${EXTRACTION_MODEL}`); + console.log(` Passed: ${passedCount}/${totalInvoices}`); + console.log(` Failed: ${failedCount}/${totalInvoices}`); + console.log(` Accuracy: ${accuracy.toFixed(1)}%`); + console.log(`========================================\n`); +}); + +export default tap.start(); diff --git a/test/test.invoices.failed.ts b/test/test.invoices.failed.ts new file mode 100644 index 0000000..39f82de --- /dev/null +++ b/test/test.invoices.failed.ts @@ -0,0 +1,695 @@ +/** + * Focused test for failed invoice extractions + * + * Tests only the 4 invoices that failed in the main test: + * - consensus_2021-09: invoice_number "2021/1384" → "20211384" (slash stripped) + * - hetzner_2022-04: model hallucinated after 281s thinking + * - qonto_2021-08: invoice_number "08-21-INVOICE-410870" → "4108705" (prefix stripped) + * - qonto_2021-09: invoice_number "09-21-INVOICE-4303642" → "4303642" (prefix stripped) + * + * Run with: tstest test/test.invoices.failed.ts --verbose + */ +import { tap, expect } from '@git.zone/tstest/tapbundle'; +import * as fs from 'fs'; +import * as path from 'path'; +import { execSync } from 'child_process'; +import * as os from 'os'; +import { ensureNanonetsOcr, ensureMiniCpm, isContainerRunning } from './helpers/docker.js'; + +const NANONETS_URL = 'http://localhost:8000/v1'; +const NANONETS_MODEL = 'nanonets/Nanonets-OCR2-3B'; + +const OLLAMA_URL = 'http://localhost:11434'; +const EXTRACTION_MODEL = 'gpt-oss:20b'; + +// Temp directory for storing markdown between stages +const TEMP_MD_DIR = path.join(os.tmpdir(), 'nanonets-invoices-failed-debug'); + +// Only test these specific invoices that failed +const FAILED_INVOICES = [ + 'consensus_2021-09', + 'hetzner_2022-04', + 'qonto_2021-08', + 'qonto_2021-09', +]; + +interface IInvoice { + invoice_number: string; + invoice_date: string; + vendor_name: string; + currency: string; + net_amount: number; + vat_amount: number; + total_amount: number; +} + +interface IImageData { + base64: string; + width: number; + height: number; + pageNum: number; +} + +interface ITestCase { + name: string; + pdfPath: string; + jsonPath: string; + markdownPath?: string; +} + +// Nanonets-specific prompt for document OCR to markdown +const NANONETS_OCR_PROMPT = `Extract the text from the above document as if you were reading it naturally. +Return the tables in html format. +Return the equations in LaTeX representation. +If there is an image in the document and image caption is not present, add a small description inside tag. +Watermarks should be wrapped in brackets. Ex: OFFICIAL COPY. +Page numbers should be wrapped in brackets. Ex: 14.`; + +// JSON extraction prompt for GPT-OSS 20B +const JSON_EXTRACTION_PROMPT = `You are an invoice data extractor. Below is an invoice document converted to text/markdown. Extract the key invoice fields as JSON. + +IMPORTANT RULES: +1. invoice_number: The unique invoice/document number (NOT VAT ID, NOT customer ID). PRESERVE ALL CHARACTERS including slashes, dashes, and prefixes. +2. invoice_date: Format as YYYY-MM-DD +3. vendor_name: The company that issued the invoice +4. currency: EUR, USD, or GBP +5. net_amount: Amount before tax +6. vat_amount: Tax/VAT amount +7. total_amount: Final total (gross amount) + +Return ONLY this JSON format, no explanation: +{ + "invoice_number": "INV-2024-001", + "invoice_date": "2024-01-15", + "vendor_name": "Company Name", + "currency": "EUR", + "net_amount": 100.00, + "vat_amount": 19.00, + "total_amount": 119.00 +} + +INVOICE TEXT: +`; + +const PATCH_SIZE = 14; + +/** + * Estimate visual tokens for an image based on dimensions + */ +function estimateVisualTokens(width: number, height: number): number { + return Math.ceil((width * height) / (PATCH_SIZE * PATCH_SIZE)); +} + +/** + * Process images one page at a time for reliability + */ +function batchImages(images: IImageData[]): IImageData[][] { + return images.map(img => [img]); +} + +/** + * Convert PDF to JPEG images using ImageMagick with dimension tracking + */ +function convertPdfToImages(pdfPath: string): IImageData[] { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pdf-convert-')); + const outputPattern = path.join(tempDir, 'page-%d.jpg'); + + try { + execSync( + `convert -density 150 -quality 90 "${pdfPath}" -background white -alpha remove "${outputPattern}"`, + { stdio: 'pipe' } + ); + + const files = fs.readdirSync(tempDir).filter((f: string) => f.endsWith('.jpg')).sort(); + const images: IImageData[] = []; + + for (let i = 0; i < files.length; i++) { + const file = files[i]; + const imagePath = path.join(tempDir, file); + const imageData = fs.readFileSync(imagePath); + + const dimensions = execSync(`identify -format "%w %h" "${imagePath}"`, { encoding: 'utf-8' }).trim(); + const [width, height] = dimensions.split(' ').map(Number); + + images.push({ + base64: imageData.toString('base64'), + width, + height, + pageNum: i + 1, + }); + } + + return images; + } finally { + fs.rmSync(tempDir, { recursive: true, force: true }); + } +} + +/** + * Convert a batch of pages to markdown using Nanonets-OCR-s + */ +async function convertBatchToMarkdown(batch: IImageData[]): Promise { + const startTime = Date.now(); + const pageNums = batch.map(img => img.pageNum).join(', '); + + const content: Array<{ type: string; image_url?: { url: string }; text?: string }> = []; + + for (const img of batch) { + content.push({ + type: 'image_url', + image_url: { url: `data:image/jpeg;base64,${img.base64}` }, + }); + } + + const promptText = batch.length > 1 + ? `${NANONETS_OCR_PROMPT}\n\nPlease clearly separate each page's content with "--- PAGE N ---" markers, where N is the page number starting from ${batch[0].pageNum}.` + : NANONETS_OCR_PROMPT; + + content.push({ type: 'text', text: promptText }); + + const response = await fetch(`${NANONETS_URL}/chat/completions`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer dummy', + }, + body: JSON.stringify({ + model: NANONETS_MODEL, + messages: [{ + role: 'user', + content, + }], + max_tokens: 4096 * batch.length, + temperature: 0.0, + }), + signal: AbortSignal.timeout(600000), + }); + + const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Nanonets API error: ${response.status} - ${errorText}`); + } + + const data = await response.json(); + let responseContent = (data.choices?.[0]?.message?.content || '').trim(); + + if (batch.length === 1 && !responseContent.includes('--- PAGE')) { + responseContent = `--- PAGE ${batch[0].pageNum} ---\n${responseContent}`; + } + + console.log(` Pages [${pageNums}]: ${responseContent.length} chars (${elapsed}s)`); + return responseContent; +} + +/** + * Convert all pages of a document to markdown using smart batching + */ +async function convertDocumentToMarkdown(images: IImageData[], docName: string): Promise { + const batches = batchImages(images); + console.log(` [${docName}] Processing ${images.length} page(s) in ${batches.length} batch(es)...`); + + const markdownParts: string[] = []; + + for (let i = 0; i < batches.length; i++) { + const batch = batches[i]; + const batchTokens = batch.reduce((sum, img) => sum + estimateVisualTokens(img.width, img.height), 0); + console.log(` Batch ${i + 1}: ${batch.length} page(s), ~${batchTokens} tokens`); + const markdown = await convertBatchToMarkdown(batch); + markdownParts.push(markdown); + } + + const fullMarkdown = markdownParts.join('\n\n'); + console.log(` [${docName}] Complete: ${fullMarkdown.length} chars total`); + return fullMarkdown; +} + +/** + * Stop Nanonets container + */ +function stopNanonets(): void { + console.log(' [Docker] Stopping Nanonets container...'); + try { + execSync('docker stop nanonets-test 2>/dev/null || true', { stdio: 'pipe' }); + execSync('sleep 5', { stdio: 'pipe' }); + console.log(' [Docker] Nanonets stopped'); + } catch { + console.log(' [Docker] Nanonets was not running'); + } +} + +/** + * Ensure GPT-OSS 20B model is available + */ +async function ensureExtractionModel(): Promise { + try { + const response = await fetch(`${OLLAMA_URL}/api/tags`); + if (response.ok) { + const data = await response.json(); + const models = data.models || []; + if (models.some((m: { name: string }) => m.name === EXTRACTION_MODEL)) { + console.log(` [Ollama] Model available: ${EXTRACTION_MODEL}`); + return true; + } + } + } catch { + return false; + } + + console.log(` [Ollama] Pulling ${EXTRACTION_MODEL}...`); + const pullResponse = await fetch(`${OLLAMA_URL}/api/pull`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name: EXTRACTION_MODEL, stream: false }), + }); + + return pullResponse.ok; +} + +/** + * Parse amount from string (handles European format) + */ +function parseAmount(s: string | number | undefined): number { + if (s === undefined || s === null) return 0; + if (typeof s === 'number') return s; + const match = s.match(/([\d.,]+)/); + if (!match) return 0; + const numStr = match[1]; + const normalized = numStr.includes(',') && numStr.indexOf(',') > numStr.lastIndexOf('.') + ? numStr.replace(/\./g, '').replace(',', '.') + : numStr.replace(/,/g, ''); + return parseFloat(normalized) || 0; +} + +/** + * Extract invoice number - MINIMAL normalization for debugging + */ +function extractInvoiceNumber(s: string | undefined): string { + if (!s) return ''; + // Only remove markdown formatting, preserve everything else + return s.replace(/\*\*/g, '').replace(/`/g, '').trim(); +} + +/** + * Extract date (YYYY-MM-DD) from response + */ +function extractDate(s: string | undefined): string { + if (!s) return ''; + let clean = s.replace(/\*\*/g, '').replace(/`/g, '').trim(); + const isoMatch = clean.match(/(\d{4}-\d{2}-\d{2})/); + if (isoMatch) return isoMatch[1]; + const dmyMatch = clean.match(/(\d{1,2})[\/.](\d{1,2})[\/.](\d{4})/); + if (dmyMatch) { + return `${dmyMatch[3]}-${dmyMatch[2].padStart(2, '0')}-${dmyMatch[1].padStart(2, '0')}`; + } + return clean.replace(/[^\d-]/g, '').trim(); +} + +/** + * Extract currency + */ +function extractCurrency(s: string | undefined): string { + if (!s) return 'EUR'; + const upper = s.toUpperCase(); + if (upper.includes('EUR') || upper.includes('€')) return 'EUR'; + if (upper.includes('USD') || upper.includes('$')) return 'USD'; + if (upper.includes('GBP') || upper.includes('£')) return 'GBP'; + return 'EUR'; +} + +/** + * Extract JSON from response + */ +function extractJsonFromResponse(response: string): Record | null { + let cleanResponse = response.replace(/[\s\S]*?<\/think>/g, '').trim(); + const codeBlockMatch = cleanResponse.match(/```(?:json)?\s*([\s\S]*?)```/); + const jsonStr = codeBlockMatch ? codeBlockMatch[1].trim() : cleanResponse; + + try { + return JSON.parse(jsonStr); + } catch { + const jsonMatch = jsonStr.match(/\{[\s\S]*\}/); + if (jsonMatch) { + try { + return JSON.parse(jsonMatch[0]); + } catch { + return null; + } + } + return null; + } +} + +/** + * Parse JSON response into IInvoice + */ +function parseJsonToInvoice(response: string): IInvoice | null { + const parsed = extractJsonFromResponse(response); + if (!parsed) return null; + + return { + invoice_number: extractInvoiceNumber(String(parsed.invoice_number || '')), + invoice_date: extractDate(String(parsed.invoice_date || '')), + vendor_name: String(parsed.vendor_name || '').replace(/\*\*/g, '').replace(/`/g, '').trim(), + currency: extractCurrency(String(parsed.currency || '')), + net_amount: parseAmount(parsed.net_amount as string | number), + vat_amount: parseAmount(parsed.vat_amount as string | number), + total_amount: parseAmount(parsed.total_amount as string | number), + }; +} + +/** + * Extract invoice from markdown using GPT-OSS 20B (streaming) + */ +async function extractInvoiceFromMarkdown(markdown: string, queryId: string): Promise { + const startTime = Date.now(); + const fullPrompt = JSON_EXTRACTION_PROMPT + markdown; + + // Log exact prompt + console.log(`\n [${queryId}] ===== PROMPT =====`); + console.log(fullPrompt); + console.log(` [${queryId}] ===== END PROMPT (${fullPrompt.length} chars) =====\n`); + + const response = await fetch(`${OLLAMA_URL}/api/chat`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + model: EXTRACTION_MODEL, + messages: [ + { role: 'user', content: 'Hi there, how are you?' }, + { role: 'assistant', content: 'Good, how can I help you today?' }, + { role: 'user', content: fullPrompt }, + ], + stream: true, + }), + signal: AbortSignal.timeout(600000), + }); + + if (!response.ok) { + const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); + console.log(` [${queryId}] ERROR: ${response.status} (${elapsed}s)`); + throw new Error(`Ollama API error: ${response.status}`); + } + + // Stream the response + let content = ''; + let thinkingContent = ''; + let thinkingStarted = false; + let outputStarted = false; + const reader = response.body!.getReader(); + const decoder = new TextDecoder(); + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + const chunk = decoder.decode(value, { stream: true }); + + for (const line of chunk.split('\n').filter(l => l.trim())) { + try { + const json = JSON.parse(line); + + const thinking = json.message?.thinking || ''; + if (thinking) { + if (!thinkingStarted) { + process.stdout.write(` [${queryId}] THINKING: `); + thinkingStarted = true; + } + process.stdout.write(thinking); + thinkingContent += thinking; + } + + const token = json.message?.content || ''; + if (token) { + if (!outputStarted) { + if (thinkingStarted) process.stdout.write('\n'); + process.stdout.write(` [${queryId}] OUTPUT: `); + outputStarted = true; + } + process.stdout.write(token); + content += token; + } + } catch { + // Ignore parse errors for partial chunks + } + } + } + } finally { + if (thinkingStarted || outputStarted) process.stdout.write('\n'); + } + + const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); + console.log(` [${queryId}] Done: ${thinkingContent.length} thinking chars, ${content.length} output chars (${elapsed}s)`); + + // Log raw response for debugging + console.log(` [${queryId}] RAW RESPONSE: ${content}`); + + return parseJsonToInvoice(content); +} + +/** + * Extract invoice (single pass) + */ +async function extractInvoice(markdown: string, docName: string): Promise { + console.log(` [${docName}] Extracting...`); + const invoice = await extractInvoiceFromMarkdown(markdown, docName); + if (!invoice) { + return { + invoice_number: '', + invoice_date: '', + vendor_name: '', + currency: 'EUR', + net_amount: 0, + vat_amount: 0, + total_amount: 0, + }; + } + console.log(` [${docName}] Extracted: ${JSON.stringify(invoice, null, 2)}`); + return invoice; +} + +/** + * Normalize date to YYYY-MM-DD + */ +function normalizeDate(dateStr: string | null): string { + if (!dateStr) return ''; + if (/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) return dateStr; + + const monthMap: Record = { + JAN: '01', FEB: '02', MAR: '03', APR: '04', MAY: '05', JUN: '06', + JUL: '07', AUG: '08', SEP: '09', OCT: '10', NOV: '11', DEC: '12', + }; + + let match = dateStr.match(/^(\d{1,2})-([A-Z]{3})-(\d{4})$/i); + if (match) { + return `${match[3]}-${monthMap[match[2].toUpperCase()] || '01'}-${match[1].padStart(2, '0')}`; + } + + match = dateStr.match(/^(\d{1,2})[\/.](\d{1,2})[\/.](\d{4})$/); + if (match) { + return `${match[3]}-${match[2].padStart(2, '0')}-${match[1].padStart(2, '0')}`; + } + + return dateStr; +} + +/** + * Compare extracted invoice against expected - detailed output + */ +function compareInvoice( + extracted: IInvoice, + expected: IInvoice +): { match: boolean; errors: string[] } { + const errors: string[] = []; + + // Invoice number comparison - exact match after whitespace normalization + const extNum = extracted.invoice_number?.trim() || ''; + const expNum = expected.invoice_number?.trim() || ''; + if (extNum.toLowerCase() !== expNum.toLowerCase()) { + errors.push(`invoice_number: expected "${expected.invoice_number}", got "${extracted.invoice_number}"`); + } + + if (normalizeDate(extracted.invoice_date) !== normalizeDate(expected.invoice_date)) { + errors.push(`invoice_date: expected "${expected.invoice_date}", got "${extracted.invoice_date}"`); + } + + if (Math.abs(extracted.total_amount - expected.total_amount) > 0.02) { + errors.push(`total_amount: expected ${expected.total_amount}, got ${extracted.total_amount}`); + } + + if (extracted.currency?.toUpperCase() !== expected.currency?.toUpperCase()) { + errors.push(`currency: expected "${expected.currency}", got "${extracted.currency}"`); + } + + return { match: errors.length === 0, errors }; +} + +/** + * Find test cases for failed invoices only + */ +function findTestCases(): ITestCase[] { + const testDir = path.join(process.cwd(), '.nogit/invoices'); + if (!fs.existsSync(testDir)) return []; + + const files = fs.readdirSync(testDir); + const testCases: ITestCase[] = []; + + for (const invoiceName of FAILED_INVOICES) { + const pdfFile = `${invoiceName}.pdf`; + const jsonFile = `${invoiceName}.json`; + + if (files.includes(pdfFile) && files.includes(jsonFile)) { + testCases.push({ + name: invoiceName, + pdfPath: path.join(testDir, pdfFile), + jsonPath: path.join(testDir, jsonFile), + }); + } else { + console.warn(`Warning: Missing files for ${invoiceName}`); + } + } + + return testCases; +} + +// ============ TESTS ============ + +const testCases = findTestCases(); +console.log(`\n========================================`); +console.log(` FAILED INVOICES DEBUG TEST`); +console.log(`========================================`); +console.log(` Testing ${testCases.length} failed invoices:`); +for (const tc of testCases) { + console.log(` - ${tc.name}`); +} +console.log(`========================================\n`); + +// Ensure temp directory exists +if (!fs.existsSync(TEMP_MD_DIR)) { + fs.mkdirSync(TEMP_MD_DIR, { recursive: true }); +} + +// -------- STAGE 1: OCR with Nanonets -------- + +tap.test('Stage 1: Setup Nanonets', async () => { + console.log('\n========== STAGE 1: Nanonets OCR ==========\n'); + const ok = await ensureNanonetsOcr(); + expect(ok).toBeTrue(); +}); + +tap.test('Stage 1: Convert failed invoices to markdown', async () => { + console.log('\n Converting failed invoice PDFs to markdown with Nanonets-OCR-s...\n'); + + for (const tc of testCases) { + console.log(`\n === ${tc.name} ===`); + + const images = convertPdfToImages(tc.pdfPath); + console.log(` Pages: ${images.length}`); + + const markdown = await convertDocumentToMarkdown(images, tc.name); + + const mdPath = path.join(TEMP_MD_DIR, `${tc.name}.md`); + fs.writeFileSync(mdPath, markdown); + tc.markdownPath = mdPath; + console.log(` Saved: ${mdPath}`); + + // Also save to .nogit for inspection + const debugMdPath = path.join(process.cwd(), '.nogit/invoices', `${tc.name}.debug.md`); + fs.writeFileSync(debugMdPath, markdown); + console.log(` Debug copy: ${debugMdPath}`); + } + + console.log('\n Stage 1 complete: All failed invoices converted to markdown\n'); +}); + +tap.test('Stage 1: Stop Nanonets', async () => { + stopNanonets(); + await new Promise(resolve => setTimeout(resolve, 3000)); + expect(isContainerRunning('nanonets-test')).toBeFalse(); +}); + +// -------- STAGE 2: Extraction with GPT-OSS 20B -------- + +tap.test('Stage 2: Setup Ollama + GPT-OSS 20B', async () => { + console.log('\n========== STAGE 2: GPT-OSS 20B Extraction ==========\n'); + + const ollamaOk = await ensureMiniCpm(); + expect(ollamaOk).toBeTrue(); + + const extractionOk = await ensureExtractionModel(); + expect(extractionOk).toBeTrue(); +}); + +let passedCount = 0; +let failedCount = 0; + +for (const tc of testCases) { + tap.test(`Stage 2: Extract ${tc.name}`, async () => { + const expected: IInvoice = JSON.parse(fs.readFileSync(tc.jsonPath, 'utf-8')); + console.log(`\n ========================================`); + console.log(` === ${tc.name} ===`); + console.log(` ========================================`); + console.log(` EXPECTED:`); + console.log(` invoice_number: "${expected.invoice_number}"`); + console.log(` invoice_date: "${expected.invoice_date}"`); + console.log(` vendor_name: "${expected.vendor_name}"`); + console.log(` total_amount: ${expected.total_amount} ${expected.currency}`); + + const startTime = Date.now(); + + const mdPath = path.join(TEMP_MD_DIR, `${tc.name}.md`); + if (!fs.existsSync(mdPath)) { + throw new Error(`Markdown not found: ${mdPath}. Run Stage 1 first.`); + } + const markdown = fs.readFileSync(mdPath, 'utf-8'); + console.log(` Markdown: ${markdown.length} chars`); + + const extracted = await extractInvoice(markdown, tc.name); + + const elapsedMs = Date.now() - startTime; + + console.log(`\n EXTRACTED:`); + console.log(` invoice_number: "${extracted.invoice_number}"`); + console.log(` invoice_date: "${extracted.invoice_date}"`); + console.log(` vendor_name: "${extracted.vendor_name}"`); + console.log(` total_amount: ${extracted.total_amount} ${extracted.currency}`); + + const result = compareInvoice(extracted, expected); + + if (result.match) { + passedCount++; + console.log(`\n Result: ✓ MATCH (${(elapsedMs / 1000).toFixed(1)}s)`); + } else { + failedCount++; + console.log(`\n Result: ✗ MISMATCH (${(elapsedMs / 1000).toFixed(1)}s)`); + console.log(` ERRORS:`); + result.errors.forEach(e => console.log(` - ${e}`)); + } + + // Don't fail the test - we're debugging + // expect(result.match).toBeTrue(); + }); +} + +tap.test('Summary', async () => { + const totalInvoices = testCases.length; + const accuracy = totalInvoices > 0 ? (passedCount / totalInvoices) * 100 : 0; + + console.log(`\n========================================`); + console.log(` Failed Invoices Debug Summary`); + console.log(`========================================`); + console.log(` Passed: ${passedCount}/${totalInvoices}`); + console.log(` Failed: ${failedCount}/${totalInvoices}`); + console.log(` Accuracy: ${accuracy.toFixed(1)}%`); + console.log(`========================================`); + console.log(` Markdown files saved to: ${TEMP_MD_DIR}`); + console.log(` Debug copies in: .nogit/invoices/*.debug.md`); + console.log(`========================================\n`); + + // Don't cleanup temp files for debugging + console.log(` Keeping temp files for debugging.\n`); +}); + +export default tap.start(); diff --git a/test/test.invoices.nanonets.ts b/test/test.invoices.nanonets.ts index f9f7fdd..f3cf306 100644 --- a/test/test.invoices.nanonets.ts +++ b/test/test.invoices.nanonets.ts @@ -1,7 +1,7 @@ /** - * Invoice extraction using Nanonets-OCR-s + GPT-OSS 20B (sequential two-stage pipeline) + * Invoice extraction using Nanonets-OCR2-3B + GPT-OSS 20B (sequential two-stage pipeline) * - * Stage 1: Nanonets-OCR-s converts ALL document pages to markdown (stop after completion) + * Stage 1: Nanonets-OCR2-3B converts ALL document pages to markdown (stop after completion) * Stage 2: GPT-OSS 20B extracts structured JSON from saved markdown (after Nanonets stops) * * This approach avoids GPU contention by running services sequentially. @@ -14,7 +14,7 @@ import * as os from 'os'; import { ensureNanonetsOcr, ensureMiniCpm, isContainerRunning } from './helpers/docker.js'; const NANONETS_URL = 'http://localhost:8000/v1'; -const NANONETS_MODEL = 'nanonets/Nanonets-OCR-s'; +const NANONETS_MODEL = 'nanonets/Nanonets-OCR2-3B'; const OLLAMA_URL = 'http://localhost:11434'; const EXTRACTION_MODEL = 'gpt-oss:20b'; @@ -92,28 +92,11 @@ function estimateVisualTokens(width: number, height: number): number { } /** - * Batch images to fit within context window + * Process images one page at a time for reliability */ function batchImages(images: IImageData[]): IImageData[][] { - const batches: IImageData[][] = []; - let currentBatch: IImageData[] = []; - let currentTokens = 0; - - for (const img of images) { - const imgTokens = estimateVisualTokens(img.width, img.height); - - if (currentTokens + imgTokens > MAX_VISUAL_TOKENS && currentBatch.length > 0) { - batches.push(currentBatch); - currentBatch = [img]; - currentTokens = imgTokens; - } else { - currentBatch.push(img); - currentTokens += imgTokens; - } - } - if (currentBatch.length > 0) batches.push(currentBatch); - - return batches; + // One page per batch for reliable processing + return images.map(img => [img]); } /** @@ -194,6 +177,7 @@ async function convertBatchToMarkdown(batch: IImageData[]): Promise { max_tokens: 4096 * batch.length, // Scale output tokens with batch size temperature: 0.0, }), + signal: AbortSignal.timeout(600000), // 10 minute timeout for OCR }); const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);