diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..bc74af5
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,20 @@
+# Local simulation; no credentials and no real charges.
+PAYMENT_MODE=demo
+PORT=4242
+PUBLIC_BASE_URL=http://localhost:4242
+DATABASE_PATH=./data/lucci-pay.sqlite
+
+# Native payment-sheet testing also requires OpenAI beta access.
+# PAYMENT_MODE=test
+# CHATGPT_CHECKOUT_ENABLED=true
+# STRIPE_SECRET_KEY=sk_test_...
+# MERCHANTS_FILE=./config/merchants.json
+# AUTH_ISSUER=https://your-identity-provider.example
+# AUTH_JWKS_URL=https://your-identity-provider.example/.well-known/jwks.json
+# AUTH_AUDIENCE=https://pay.your-domain.example/mcp
+# PUBLIC_BASE_URL=https://pay.your-domain.example
+
+# Live mode additionally requires approved commerce access.
+# PAYMENT_MODE=live
+# LIVE_PAYMENTS_APPROVED=true
+# STRIPE_SECRET_KEY=sk_live_...
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..35be733
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,17 @@
+name: Validate Lucci Pay
+on:
+ push:
+ pull_request:
+permissions:
+ contents: read
+jobs:
+ check:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '24'
+ cache: npm
+ - run: npm ci
+ - run: npm run check
diff --git a/.gitignore b/.gitignore
index da9badb..9ec2a4b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,40 +1,9 @@
-# Dependency directories
node_modules/
-.npm
-
-# Build artifacts
-out/
dist/
-*.vsix
-
-# VS Code specific
-.vscode-test/
-.vscode/
-*.vsix
-
-# Logs
-logs
-*.log
-npm-debug.log*
-
-# Environment
+data/
.env
-.env.local
-.env.development.local
-.env.test.local
-.env.production.local
-
-# Operating System Files
-.DS_Store
-Thumbs.db
-
-# Test images that contain personal data
-test-image/*.jpg
-test-image/*.jpeg
-test-image/*.png
-test-image/*.gif
-test-image/*.webp
-test-image/*.tiff
-
-# Exiftool temp files
-*_original
\ No newline at end of file
+.env.*
+!.env.example
+config/merchants.json
+*.log
+coverage/
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..226a39e
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,4 @@
+node_modules
+dist
+data
+package-lock.json
diff --git a/.prettierrc.json b/.prettierrc.json
new file mode 100644
index 0000000..4926aa8
--- /dev/null
+++ b/.prettierrc.json
@@ -0,0 +1 @@
+{ "singleQuote": true, "trailingComma": "all", "printWidth": 100 }
diff --git a/.vscode-test.mjs b/.vscode-test.mjs
deleted file mode 100644
index cfe906b..0000000
--- a/.vscode-test.mjs
+++ /dev/null
@@ -1,5 +0,0 @@
-import { defineConfig } from '@vscode/test-cli';
-
-export default defineConfig({
- files: 'test/**/*.test.js',
-});
diff --git a/.vscodeignore b/.vscodeignore
deleted file mode 100644
index d756798..0000000
--- a/.vscodeignore
+++ /dev/null
@@ -1,21 +0,0 @@
-.vscode/**
-.vscode-test/**
-test/**
-.gitignore
-.yarnrc
-**/jsconfig.json
-**/*.map
-**/eslint.config.mjs
-**/.vscode-test.*
-
-# Development & documentation files not needed in the package
-PUBLISHING.md
-TESTING.md
-test-image/**
-.git/**
-CHANGELOG.md
-
-# Additional build artifacts to exclude
-**/*.vsix
-
-images/output.gif
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
deleted file mode 100644
index 136f296..0000000
--- a/CHANGELOG.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# Change Log
-
-All notable changes to the "metascraper" extension will be documented in this file.
-
-Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
-
-## [Unreleased]
-
-- Initial release
\ No newline at end of file
diff --git a/PUBLISHING.md b/PUBLISHING.md
deleted file mode 100644
index 142ba09..0000000
--- a/PUBLISHING.md
+++ /dev/null
@@ -1,215 +0,0 @@
-# Publishing Guide for Meta-Scraper
-
-
-
-[](https://marketplace.visualstudio.com/items?itemName=natalie-a-1.metascraper)
-[](LICENSE)
-
-
-
-This guide provides detailed instructions for packaging and publishing the Meta-Scraper extension to the Visual Studio Code Marketplace.
-
----
-
-## š Table of Contents
-
-- [Prerequisites](#-prerequisites)
-- [Packaging the Extension](#-packaging-the-extension)
-- [Testing Before Publishing](#-testing-before-publishing)
-- [Publishing to VS Code Marketplace](#-publishing-to-vs-code-marketplace)
-- [Managing Published Extensions](#-managing-published-extensions)
-- [Automating Publishing](#-automating-publishing)
-- [Resources](#-resources)
-
----
-
-## š§ Prerequisites
-
-Before you can publish the extension, you'll need to set up a few things:
-
-
-
-
- Install the VS Code Extension Manager globally:
- npm install -g @vscode/vsce
-
- Create a Microsoft account if you don't have one
- Create a Personal Access Token (PAT):
-
- Go to Azure DevOps
- Sign in with your Microsoft account
- Select your profile icon ā Personal access tokens
- Create a new token with "Marketplace (publish)" scope
- ā ļø Save this token securely - you'll need it for publishing
-
-
-
-
-
-
----
-
-## š¦ Packaging the Extension
-
-Creating a `.vsix` package file is the first step for distribution:
-
-```bash
-# Navigate to your extension directory if needed
-cd meta-scraper
-
-# Create the package
-vsce package
-```
-
-This will generate a file named `metascraper-0.0.1.vsix` (or similar with your version number) in the current directory.
-
----
-
-## š§Ŗ Testing Before Publishing
-
-Always test your packaged extension before publishing:
-
-
-
-
- In VS Code, open the Extensions view (Ctrl+Shift+X)
- Click the "..." menu in the top-right corner
- Select "Install from VSIX..."
- Choose your newly created .vsix file
- Verify all functionality works as expected
- Check that the extension icon displays correctly
- Ensure the README is formatted properly
-
-
-
-
-See the [TESTING.md](TESTING.md) file for detailed testing procedures.
-
----
-
-## š Publishing to VS Code Marketplace
-
-When you're ready to publish:
-
-### Step 1: Create a Publisher Account
-
-
-
-
- Go to the VS Code Marketplace Publishers page
- Sign in with your Microsoft account
- Click "New Publisher"
- Fill in the details:
-
- Publisher ID: "natalie-a-1" (must match package.json)
- Display Name: Your preferred display name
- Description: Brief bio or description
-
-
-
-
-
-
-### Step 2: Login and Publish
-
-```bash
-# Login with your publisher name and PAT
-vsce login natalie-a-1
-# You'll be prompted for your Personal Access Token
-
-# Publish the extension
-vsce publish
-```
-
-If successful, your extension will be available on the marketplace within a few minutes!
-
-### For Future Updates
-
-When you make changes and want to publish a new version:
-
-```bash
-# Increment version (choose one)
-vsce publish patch # 0.0.1 -> 0.0.2 (bug fixes)
-vsce publish minor # 0.0.1 -> 0.1.0 (new features)
-vsce publish major # 0.0.1 -> 1.0.0 (breaking changes)
-```
-
----
-
-## š Managing Published Extensions
-
-### Updating Extension Details
-
-You can update your extension's marketplace page information:
-
-1. Update your `README.md` with new information
-2. Change the `package.json` for metadata updates
-3. Re-publish using the steps above
-
-### Unpublishing an Extension
-
-If needed, you can remove your extension from the marketplace:
-
-```bash
-vsce unpublish natalie-a-1.metascraper
-```
-
-ā ļø **Warning**: Unpublishing an extension can be disruptive to users. Consider updating instead of removing.
-
----
-
-## āļø Automating Publishing
-
-For more efficient workflows, consider setting up GitHub Actions to automatically publish your extension when you create a new release:
-
-
-Click to view GitHub Actions workflow file example
-
-```yaml
-name: Publish Extension
-
-on:
- release:
- types: [created]
-
-jobs:
- publish:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-node@v1
- with:
- node-version: 16
- - run: npm ci
- - name: Publish to Visual Studio Marketplace
- uses: HaaLeo/publish-vscode-extension@v1
- with:
- pat: ${{ secrets.VS_MARKETPLACE_TOKEN }}
- registryUrl: https://marketplace.visualstudio.com
-```
-
-
-
----
-
-## š Resources
-
-- [VS Code Publishing Extensions Guide](https://code.visualstudio.com/api/working-with-extensions/publishing-extension)
-- [vsce Command Line Tool Documentation](https://github.com/microsoft/vscode-vsce)
-- [VS Code Marketplace](https://marketplace.visualstudio.com/)
-- [Extension Guidelines](https://code.visualstudio.com/api/references/extension-guidelines)
-
----
-
-
-
-
-
-
\ No newline at end of file
diff --git a/README.md b/README.md
index cf8ef06..77f5c6c 100644
--- a/README.md
+++ b/README.md
@@ -1,39 +1,98 @@
-# Meta-Scraper: Image Metadata Cleaner
+# Lucci Pay
-[](https://marketplace.visualstudio.com/items?itemName=NatalieHill.metascraper)
-[](LICENSE)
+Payment requests from participating apps, reviewed and paid inside ChatGPT.
-Meta-Scraper is a Visual Studio Code extension for inspecting and removing image metadata, helping you avoid leaking sensitive information when sharing images. It currently has **250+ installs** on the Visual Studio Code Marketplace.
+This is a new project replacing the Meta-Scraper VS Code extension. The UI takes its cues from Wallet: a compact sheet, an identifiable merchant, an exact total, one primary action, and a clear receipt. The real checkout uses ChatGPTās native payment sheet with Apple Pay requested as a supported method. Payment details and authentication belong to the host.
-## Demo
+**Status: working local demo and implementation for a gated native-checkout pilot. Not a launched payment service.**
-
+## What is possible today
-## Features
+| Capability | Status |
+| ----------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
+| Merchant backend creates a payment request | Implemented with authentication, user binding and idempotency |
+| Review request and receipt in ChatGPT | Implemented with MCP Apps |
+| Native ChatGPT payment sheet | `window.openai.requestCheckout` integration; requires OpenAIās private-beta enablement |
+| Apple Pay | Declared in the native sheetās supported methods; actual availability depends on OpenAI, PSP, account and device |
+| Stripe payment processing | Scoped shared payment token adapter implemented; requires PSP credentials and end-to-end partner testing |
+| Arbitrary plugin subscriptions, digital goods, credits, or service fees | Not enabled; current general commerce approval covers physical goods |
+| Universal access to other apps or automatic charges | Not available; requesting apps must integrate, and the buyer explicitly approves checkout |
-- View metadata embedded in an image
-- Remove metadata in a single action
-- Supports `.jpg`, `.jpeg`, `.png`, `.gif`, `.tiff`, `.webp`
-- Fast processing with real-time feedback
-- All processing is done locally; no data leaves your machine
+OpenAIās checkout reference lists Apple Pay in the native payment-provider schema. It does **not** establish that every ChatGPT installation can open Apple Pay. Its UI guide describes private-beta access, while the submission guidelines restrict embedded third-party checkout. This project therefore has **no external checkout fallback, embedded Stripe card form, ApplePaySession workaround, or simulated biometric prompt**. When native checkout is unavailable, the action is disabled.
-
-
Right-click any image file in VS Code to access Meta-Scraper actions.
-
+The initial merchant contract supports **pre-priced physical-goods pickup orders**, with USD, CAD, GBP and EUR totals in minor units. Shipping/address changes, inventory reservation, subscriptions, refunds, payouts and an unrestricted payment network are outside this first implementation. See [platform limits](docs/platform-limits.md).
-## Why Cleaning Image Metadata Matters
+## Run the demo
-Modern image files can contain hidden metadata that exposes personal or sensitive details:
+Node.js 24 is required for the built-in SQLite store.
-| Metadata Type | Privacy Risk |
-|--------------------|------------------------------------------------------------|
-| GPS location | Reveals the exact place where the photo was taken |
-| Device information | Exposes camera/phone model and unique identifiers |
-| Creation time | Shows precisely when the photo was taken |
-| Software details | Reveals which tools were used to edit the image |
-| Author information | Can include your name, email, or other personal details |
+```sh
+npm ci
+npm run dev
+```
-Examples of potential issues:
+Open [the local preview](http://localhost:4242/preview). Try a successful demo payment, a decline, retry and cancellation. Every simulated payment is labeled **Demo** and moves no money.
-- Revealing your home or workplace location
-- Leaking details in screenshots or documents shared publicly
+```sh
+npm run demo:request # an example requesting-app backend; keep the server running
+npm run check # type checking, payment/API tests and production build
+npm start # run the compiled app after npm run build
+```
+
+The demo binds to loopback. It uses fictional shared data and a public demo key. Never use demo mode for real orders or customer information.
+
+## Connect to ChatGPT
+
+1. Run the server and expose `/mcp` through an HTTPS tunnel for development. Set `PUBLIC_BASE_URL` to the tunnel origin and restart so the host allowlist matches. A tunnel pointing at loopback can still reach the demo server.
+2. In ChatGPTās app settings, enable Developer Mode and add the HTTPS MCP endpoint, such as `https://your-dev-domain/mcp`. Current plugin documentation uses the newer **Plugins ā Connect and test** terminology; labels can vary by client.
+3. In demo mode, ask: āShow me the Lucci Pay demo.ā The `create_demo_payment_request` tool renders the sheet.
+4. For a participating appās request, enable both integrations and ask to review its returned request ID with Lucci Pay.
+5. Refresh the app connection after changing tool descriptors or UI resource metadata.
+
+This repository does not deploy a public endpoint or register a marketplace listing. ChatGPTās actual native payment sheet, iOS behavior, Apple Pay, 3DS and Stripe sandbox settlement still need to be exercised in an enabled account.
+
+## Native checkout setup
+
+Use `.env.example` and `config/merchants.example.json`. Real credentials belong in environment variables or a secret manager; `config/merchants.json`, local databases and `.env` files are ignored by git.
+
+- Obtain OpenAI approval for the intended marketplace and commerce category. `CHATGPT_CHECKOUT_ENABLED=true` records your deployment choice; it cannot grant beta access.
+- Configure an OAuth 2.1 identity provider, public discovery, PKCE, a supported client registration method, JWKS and the resource audience. This server implements the protected resource side, not an authorization server. See [authentication](docs/integration.md#identity-and-account-linking).
+- Register each merchant with its PSP-assigned native-checkout merchant identifier, optional Stripe Connect account, real policy URLs, and a hash of a strong merchant API key.
+- Confirm with Stripe that the token format, preview API version and connected-account routing match your OpenAI integration. The adapter uses Stripeās documented `shared_payment_granted_token` flow.
+- Set `PAYMENT_MODE=test` with a **test** key before exercising the real host. This mode accepts only scoped SPT tokens and never exposes demo tools.
+- Enable live mode only after the pilot is validated. It requires a live Stripe key, native checkout enabled and `LIVE_PAYMENTS_APPROVED=true`. These flags are deployment gates, not proof of commercial approval.
+
+Initial account connection may require the identity providerās sign-in flow. Once connected, the purchase flow itself stays in ChatGPT. The app does not read Apple Wallet contents or reuse a payment method from another app without an authorized provider token.
+
+## Architecture
+
+```text
+server/ Merchant API, OAuth verification, MCP tools, payment lifecycle, SQLite, Stripe
+web/ React payment sheet and standard MCP Apps bridge
+shared/ Request schemas and checkout types
+examples/ Small merchant-backend client and runnable request example
+tests/ Lifecycle, processor, auth and HTTP/MCP integration checks
+docs/ Integration contract, platform limits and validation notes
+```
+
+The primary app shape is `interactive-decoupled`: `show_payment_request` renders the widget; status and mutation tools update it without replacing the UI. Tool/resource wiring is adapted from OpenAIās **MCP App Basics** example at commit `18cc38e78a968712c357bacdc3c79fead5bfc6b4`. The widget uses the core `App` bridge; `callServerTool` wraps standard `tools/call`. The `window.openai` extension is used only for native checkout and optional dismissal.
+
+Prices are immutable once requested. The server calculates totals, binds the request to a payer and registered merchant, and persists each attempt before contacting Stripe. Retries reuse the same provider idempotency key. Unknown outcomes remain `processing`; only an authoritative matching payment result produces a receipt. Raw payment tokens and buyer details are not persisted or echoed into tool output.
+
+Use one server instance with a persistent SQLite volume for the pilot. Run behind HTTPS with the configured Host preserved. Load balancing, identity-based distributed rate limiting, operator reconciliation, storage backups and fulfillment integration need an operational design before public launch.
+
+## Documentation
+
+- [Participating-app integration](docs/integration.md)
+- [Platform limits and Apple Pay](docs/platform-limits.md)
+- [Validation and remaining host checks](docs/validation.md)
+- [Third-party notices](THIRD_PARTY_NOTICES.md)
+
+Primary references checked for this implementation:
+
+- [OpenAI checkout API](https://developers.openai.com/plugins/build/monetization)
+- [OpenAI MCP UI guide](https://developers.openai.com/plugins/build/chatgpt-ui)
+- [OpenAI MCP server](https://developers.openai.com/plugins/build/mcp-server), [tool design](https://developers.openai.com/plugins/plan/tools), [UI reference](https://developers.openai.com/plugins/reference)
+- [OpenAI plugin guidelines](https://developers.openai.com/plugins/app-guidelines) and [authentication](https://developers.openai.com/plugins/build/auth)
+- [Stripe shared payment tokens](https://docs.stripe.com/agentic-commerce/concepts/shared-payment-tokens?agent-seller=seller)
+- [Apple Pay on the Web](https://developer.apple.com/documentation/applepayontheweb)
diff --git a/TESTING.md b/TESTING.md
deleted file mode 100644
index 2b6f53b..0000000
--- a/TESTING.md
+++ /dev/null
@@ -1,170 +0,0 @@
-# Testing Guide for Meta-Scraper
-
-
-
-[](https://marketplace.visualstudio.com/items?itemName=natalie-a-1.metascraper)
-[](LICENSE)
-
-
-
-This comprehensive guide will help you effectively test the Meta-Scraper extension to ensure all features are working correctly.
-
----
-
-## š Table of Contents
-
-- [Testing Installation Methods](#-testing-installation-methods)
-- [Testing with Sample Images](#-testing-with-sample-images)
-- [Feature Testing Checklist](#-feature-testing-checklist)
-- [Troubleshooting](#-troubleshooting)
-- [Advanced Testing](#-advanced-testing)
-- [Reporting Issues](#-reporting-issues)
-
----
-
-## š Testing Installation Methods
-
-### Method 1: Install from VSIX package (Recommended for end-users)
-
-
-
-
- Open VS Code
- Press Ctrl+Shift+X (or Cmd+Shift+X on Mac) to open Extensions
- Click the "..." menu in the top-right corner
- Select "Install from VSIX..."
- Navigate to and select metascraper-0.0.1.vsix
- Restart VS Code after installation
-
-
-
-
-### Method 2: Run in Development Mode (For developers)
-
-
-
-
- Open the project in VS Code:
- code --new-window .
-
- Press F5 or go to Run > Start Debugging
- This launches an "Extension Development Host" window
- Test your extension in this development window
- Changes to code require restarting the debugging session
-
-
-
-
----
-
-## š¼ļø Testing with Sample Images
-
-### Option 1: Use Your Own Images
-
-
-
-
- Copy images into the test-image directory
- Right-click on an image and select "View Image Metadata"
- Check what metadata exists in the image
- Right-click again and select "Clean Image Metadata"
- View metadata again to verify cleaning was successful
-
-
-
-
-### Option 2: Create Test Images with Metadata
-
-We've included a script that adds artificial test metadata to any image, perfect for testing the cleaning functionality:
-
-```bash
-# First install dependencies
-npm install
-
-# Then run the script on any image
-node test-image/add-test-metadata.js test-image/your-image.jpg
-```
-
-The script adds:
-- GPS coordinates
-- Camera information
-- Creation timestamps
-- Copyright details
-- Author information
-
-
-
-
-
----
-
-## ā
Feature Testing Checklist
-
-Use this checklist to verify all extension features are working properly:
-
-| Feature | Test Steps | Expected Result | Status |
-|---------|------------|-----------------|--------|
-| **View Metadata** | Right-click image ā "View Image Metadata" | JSON metadata displayed in new document | ⬠|
-| **Clean Metadata** | Right-click image ā "Clean Image Metadata" | Progress notification ā Success message | ⬠|
-| **Verify Cleaning** | View metadata after cleaning | Most/all metadata fields removed | ⬠|
-| **Image Integrity** | Open image after cleaning | Image appears identical to original | ⬠|
-| **Error: Non-Image** | Right-click non-image file ā try commands | Proper error message shown | ⬠|
-| **Error: Read-Only** | Test with read-only image | Graceful error handling | ⬠|
-| **Large Images** | Test with high-resolution image | Successfully processes without timeout | ⬠|
-
----
-
-## š§ Troubleshooting
-
-If you encounter issues during testing:
-
-1. **Command not appearing in context menu**
- - Ensure the extension is properly installed
- - Verify you're right-clicking on a supported image format
- - Try reloading the VS Code window
-
-2. **Metadata not fully removed**
- - Some specialized file formats may retain certain metadata
- - Check if the file is locked by another application
- - Try saving the image to a new location first
-
-3. **Performance issues with large files**
- - Processing very large images may take longer
- - Ensure your system has sufficient resources available
-
----
-
-## š¬ Advanced Testing
-
-For more thorough testing, try these advanced scenarios:
-
-- Test with multiple image formats (JPG, PNG, GIF, TIFF, WebP)
-- Test with unusually large image files (20MB+)
-- Test with images containing extensive metadata
-- Test the extension while other resource-intensive operations are running
-- Test with images in read-only locations or with restricted permissions
-
----
-
-## š Reporting Issues
-
-If you encounter bugs or have feature suggestions:
-
-1. Check existing [issues on GitHub](https://github.com/natalie-a-1/Meta-Scraper/issues) to see if it's already reported
-2. Create a new issue with:
- - A clear descriptive title
- - Detailed steps to reproduce
- - Expected vs. actual behavior
- - VS Code version and OS information
- - Screenshots or logs if available
-
----
-
-
\ No newline at end of file
diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md
new file mode 100644
index 0000000..746fe31
--- /dev/null
+++ b/THIRD_PARTY_NOTICES.md
@@ -0,0 +1,31 @@
+# Third-party notices
+
+MCP App Basics server and widget connection patterns are adapted from
+[openai/openai-apps-sdk-examples](https://github.com/openai/openai-apps-sdk-examples)
+at commit `18cc38e78a968712c357bacdc3c79fead5bfc6b4`.
+
+Original files consulted: `mcp_app_basics_node/src/server.ts` and
+`src/call-server-tool/App.tsx`. Demo-specific content was removed; the payment
+service and UI are new. The current version uses the core MCP Apps `App` API.
+
+MIT License
+
+Copyright (c) 2025 OpenAI
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/config/merchants.example.json b/config/merchants.example.json
new file mode 100644
index 0000000..e86bf7e
--- /dev/null
+++ b/config/merchants.example.json
@@ -0,0 +1,15 @@
+[
+ {
+ "id": "your-merchant",
+ "name": "Your merchant name",
+ "domain": "your-merchant.example",
+ "api_key_sha256": "0000000000000000000000000000000000000000000000000000000000000000",
+ "provider_merchant_id": "profile_replace_with_psp_assigned_value",
+ "stripe_account": "acct_replace",
+ "category": "physical_goods",
+ "terms_url": "https://your-merchant.example/terms",
+ "privacy_url": "https://your-merchant.example/privacy",
+ "refund_url": "https://your-merchant.example/refunds",
+ "support_url": "https://your-merchant.example/support"
+ }
+]
diff --git a/docs/integration.md b/docs/integration.md
new file mode 100644
index 0000000..dc81520
--- /dev/null
+++ b/docs/integration.md
@@ -0,0 +1,107 @@
+# Participating-app integration
+
+A requesting app creates the order using its own trusted backend and gives ChatGPT the returned `request_id`. ChatGPT can show the request through Lucci Pay when the user has enabled that integration and asks to pay. MCP Apps does not give an arbitrary plugin permission to call another appās widget or access its credentials.
+
+## Request an order
+
+Authenticate with a **merchant backend key**, never a userās access token. The registered merchant determines the seller name, policy URLs, PSP merchant ID and Stripe account. Callers cannot override those fields.
+
+```http
+POST /v1/payment-requests
+Authorization: Bearer
+Content-Type: application/json
+Idempotency-Key: order-2026-123
+```
+
+```json
+{
+ "merchant_order_reference": "order-2026-123",
+ "payer_subject": "linked-payer-subject",
+ "currency": "USD",
+ "line_items": [
+ {
+ "id": "cup-1",
+ "name": "Everyday ceramic cup",
+ "quantity": 1,
+ "unit_amount": 2400,
+ "discount": 0,
+ "tax": 198
+ }
+ ],
+ "fulfillment": {
+ "type": "pickup",
+ "title": "Pick up in store",
+ "subtitle": "Your merchant location Ā· Ready in 2 hours"
+ },
+ "expires_in_seconds": 900
+}
+```
+
+`unit_amount` is per item. `discount` and `tax` are **whole-line** amounts, not per-unit amounts. Subtotal is `quantity Ć unit_amount ā discount`; total is subtotal plus tax. Supply tax and pickup terms after validating inventory and fulfillment in the requesting merchantās backend. Lucci Pay validates the arithmetic but is not a tax engine or inventory service. Maximum request size is 48 KB, 30 unique lines, 100 units per line and 10,000,000 minor units total. Expiry is 1ā30 minutes. Use only the four supported two-decimal currencies.
+
+```json
+{
+ "request_id": "pay_",
+ "mode": "test",
+ "status": "pending",
+ "expires_at": "",
+ "handoff": {
+ "tool": "show_payment_request",
+ "arguments": { "request_id": "pay_" }
+ }
+}
+```
+
+The handoff is data for the host to use; it is not an automatic cross-plugin invocation protocol. Return it as part of your appās MCP tool result after the user decides to review a purchase. Avoid sending the merchant API key or payer identity into chat. [merchant-client.ts](../examples/merchant-client.ts) is a small server-side integration example.
+
+Retry transient request creation failures with the **same** idempotency key and payload. Changing the amount, currency, payer or other request data under the same key returns `409 idempotency_conflict`. To replace an expired quote, issue a new order revision/key. An existing request is never silently repriced.
+
+## Identity and account linking
+
+The `payer_subject` must come from a trusted account-link mapping established by the merchant and your OAuth identity provider. It must exactly match the authenticated `sub` claim on the buyerās Lucci Pay access token. Do not accept a payer ID from arbitrary model text or let a merchant impersonate a buyer. Each read, cancel and completion verifies ownership. Merchant API keys cannot authenticate to user tools.
+
+This initial repository does not provide merchant self-service onboarding or a turnkey account-linking UI. Configure merchants and establish trusted identity mappings before a pilot. For a public multi-tenant service, introduce a pairwise linking API instead of sharing global user identifiers across merchants.
+
+The MCP server publishes RFC 9728 resource metadata at `/.well-known/oauth-protected-resource` and `/.well-known/oauth-protected-resource/mcp`. The external authorization server must support the current MCP OAuth flow, PKCE, discovery and a supported registration mechanism. Configure its audience as `PUBLIC_BASE_URL/mcp`. User JWTs require a valid RS256/ES256 signature, issuer, audience, subject, issuance time and expiry. Scopes:
+
+| Scope | Purpose |
+| --------------- | ---------------------------------- |
+| `payments:read` | Review owned requests and receipts |
+| `payments:pay` | Confirm or cancel an owned request |
+
+The SDK version used here exposes security metadata through `_meta.securitySchemes`. Read-only tools are model-visible; payment completion and cancellation are private/app-visible. **Visibility is not authorization:** OAuth, ownership and the scoped PSP token provide the server-side controls.
+
+## Native confirmation
+
+1. `show_payment_request` returns the immutable checkout session and review UI.
+2. The user presses Pay. The widget calls the feature-detected `window.openai.requestCheckout(session)` directly from that gesture.
+3. The host collects the payment method and confirmation, then calls `complete_checkout` with `checkout_session_id`, optional `buyer`, and `payment_data: { "token": "spt_...", "provider": "stripe" }`.
+4. The server verifies the user, request, mode, merchant, token limits and expiry, persists an attempt, and submits the token to Stripe on the registered merchant account. It uses `payment_method_data[shared_payment_granted_token]`, exact server totals and a stable idempotency key.
+5. The widget retrieves authoritative status after the host returns. A resolved browser promise alone never marks the order paid.
+
+The optional buyer shape currently accepts `first_name`, `last_name`, and `email`, and the token envelope deliberately rejects unknown payment fields. Confirm the actual partner payload and pickup-session schema during the native pilot; adapt versioned schemas if the enabled host/PSP contract differs. Never loosen this into a raw-card endpoint.
+
+No tokens or buyer details are stored in SQLite, logs or model-visible responses. Stripe API access uses the fixed provider origin, a timeout and API version `2026-04-22.preview`. Keep this version aligned with your granted-token integration.
+
+## Fulfill after verified payment
+
+```http
+GET /v1/payment-requests/pay_
+Authorization: Bearer
+```
+
+The response contains the merchant order reference, status, environment and receipt, with no payer identity or payment credentials. Fulfill exactly once only when `status` is `paid`, the environment matches your order, and the receipt amount/currency match your merchant record. Use the stable receipt ID as your fulfillment idempotency key.
+
+This version uses authenticated status retrieval instead of outbound webhooks. Retrieving a `processing` request with a known PaymentIntent refreshes its provider status. Do not treat a demo/test receipt as a live payment.
+
+| State | Meaning / permitted next action |
+| ----------------------- | --------------------------------------------------------------------------------------------- |
+| `pending` | Awaiting the user; pay, cancel or expire |
+| `processing` | Provider result uncertain or authentication pending; check status, never start another charge |
+| `paid` | Authoritative receipt; repeated completion returns the same receipt |
+| `failed` | Definite decline or invalid authorization; a new token may retry before expiry |
+| `cancelled` / `expired` | Closed; request a new quote |
+
+After a network timeout with no recorded provider ID, the native host can retry the original token and reuse the existing attempt/key for up to 23 hours. The server stores only the token hash. After the key retention window, operator reconciliation is required. Do not reset processing state or mint a new key to āfixā uncertainty. A `requires_3ds` response keeps the request locked; the host must resolve authentication, or the operator must reconcile it. No redirect fallback is implemented.
+
+Refunds and fulfillment support belong to the merchant. This app does not custody money, maintain a stored balance, or provide peer-to-peer transfers.
diff --git a/docs/platform-limits.md b/docs/platform-limits.md
new file mode 100644
index 0000000..4879132
--- /dev/null
+++ b/docs/platform-limits.md
@@ -0,0 +1,17 @@
+# Platform limits and Apple Pay
+
+Research checked September 8, 2026. Recheck before launch because the native checkout APIs and commercial eligibility are evolving.
+
+The closest supported implementation of the requested experience is **ChatGPTās native payment sheet**, with Apple Pay as a supported method. OpenAIās [checkout reference](https://developers.openai.com/plugins/build/monetization) documents `window.openai.requestCheckout`, the payment-provider schema and the `complete_checkout` callback. Apple Pay is explicitly present in the exampleās supported payment methods. This repository uses those fields; it does not fabricate an Apple Wallet sheet.
+
+Native checkout remains a **private beta for selected marketplaces**. Setting a configuration flag or adding Appleās JavaScript cannot unlock it. OpenAIās [UI guide](https://developers.openai.com/plugins/build/chatgpt-ui#use-the-chatgpt-payment-sheet) confirms this limitation. Current general commerce approval covers **physical goods**; digital app credits, subscriptions and service payments require separate explicit enablement. This is a platform constraint on the original universal app-payments concept, not an implemented digital-goods capability.
+
+The [submission guidelines](https://developers.openai.com/plugins/app-guidelines#checkout) prohibit embedding other third-party checkout solutions in plugin UI. The checkout and UI guides also describe using payment methods already saved with a merchant, while the general submission wording still calls external checkout the default. We did not use the saved-method flow to sidestep that ambiguity or the userās seamless setup goal. The chosen implementation is the explicitly documented native flow.
+
+Appleās [web payment APIs](https://developer.apple.com/documentation/applepayontheweb) require Appleās supported browser/merchant integration and do not establish compatibility with ChatGPTās controlled iframe. The requesting app cannot control ChatGPTās parent frame, permissions, native entitlements or Wallet. No direct `ApplePaySession`, embedded PSP frame, fake Apple Pay brand button, Face ID animation or double-click-side-button instruction is used. When an enabled host displays genuine Apple Pay, Apple and the host handle authentication.
+
+The request API enables **participating merchants** to integrate. There is no documented universal ability for this app to intercept payment requests from every installed plugin. Both integrations must be available to the host, and the requesting service must explicitly implement the handoff and trusted account mapping. OpenAI adds host-owned merchant/connector context to the native sheet; marketplace branding and per-seller settlement must be validated with OpenAI and the PSP rather than overwritten in client code.
+
+Stripeās old app-monetization documentation link now redirects to [MPP-based MCP monetization](https://docs.stripe.com/agentic-commerce/monetize-mcp). That is a different agent-wallet payment flow and does not demonstrate native Apple Pay in ChatGPT. This implementation instead uses the [documented seller shared-payment-token API](https://docs.stripe.com/agentic-commerce/concepts/shared-payment-tokens?agent-seller=seller) behind OpenAIās `complete_checkout` contract. The exact native host/Stripe token envelope still needs partner validation.
+
+The code can be reviewed and the local demo can be run now. Public launch depends on OpenAI/PSP access, merchant and identity setup, the enabled-host integration test, and normal merchant fulfillment operations. The PR does not claim those external prerequisites have been completed.
diff --git a/docs/validation.md b/docs/validation.md
new file mode 100644
index 0000000..00284da
--- /dev/null
+++ b/docs/validation.md
@@ -0,0 +1,32 @@
+# Validation
+
+Checked September 8, 2026 on Node.js 24.19.0.
+
+## Completed
+
+`npm run check` passes:
+
+- TypeScript checks for server, widget, examples and tests.
+- **22 tests**, all passing.
+- Self-contained React widget bundle and compiled MCP/HTTP server.
+
+The tests cover authoritative amount calculation, tampering and invalid amounts, duplicate request keys, payer/merchant isolation, account-routing changes, cancellation, expiry, retry after decline, unknown payment outcomes, persistent recovery after restart, the provider idempotency retention window, concurrent confirmations, required 3DS state, demo isolation, signed JWT validation, missing scopes, token constraints and receipt verification. A delayed UI result cannot overwrite a newer paid receipt because state versions are monotonic.
+
+A real MCP SDK client exercises initialization, discovery, resource retrieval, demo request creation, payment review, simulation, and receipt retrieval over a locally running HTTP server. A separate native-tool contract test verifies private tool metadata, scope checks, token-only inputs and output-schema conformance using a fake PSP. Stripe HTTP requests are tested through an injected transport; these tests never send a real payment.
+
+The compiled demo server also started successfully. `git diff --check` passed.
+
+## Not completed
+
+- **Visual browser review:** this environmentās Cloud Browser policy blocked the local preview and local-file navigation. No screenshot or mobile browser verification is claimed. Open `/preview` locally to review the layout. `node scripts/build-preview-harness.mjs` also creates an offline UI fixture after a build; that fixture uses simulated browser responses and is not an end-to-end backend test.
+- **ChatGPT host loop:** no enabled native-checkout account was available for validation. The MCP protocol and response schemas were tested locally, not in ChatGPTās actual payment sheet.
+- **Real PSP sandbox/live settlement:** no Stripe credentials or merchant account were supplied. The exact partner token/buyer envelope, pickup schema, Apple Pay eligibility, 3DS handling and Stripe account routing require validation with the enabled OpenAI/PSP integration.
+- **Production:** no hosting, merchant registration, OAuth authorization server, public policy pages, marketplace approval or launch was performed.
+
+Local validation reaches the working-server and MCP protocol levels. It does not establish native Apple Pay availability, payment-provider acceptance, visual readiness or public-launch readiness.
+
+## Before enabling a real pilot
+
+Run test-mode purchases inside the enabled ChatGPT app on the target iOS and web clients. Verify the genuine native sheet and Apple Pay availability; complete success, decline, cancellation and 3DS cases; repeat a confirmation during a timeout; verify matching Stripe and merchant receipts; then check keyboard navigation, narrow screens, dark mode and reduced motion. Confirm that an unsupported host disables checkout and that no purchase path opens an external site. Validate policy links separately.
+
+Do not enable live funds solely because automated tests pass. The remaining checks need the actual host, processor and registered merchant context.
diff --git a/eslint.config.mjs b/eslint.config.mjs
deleted file mode 100644
index db89148..0000000
--- a/eslint.config.mjs
+++ /dev/null
@@ -1,25 +0,0 @@
-import globals from "globals";
-
-export default [{
- files: ["**/*.js"],
- languageOptions: {
- globals: {
- ...globals.commonjs,
- ...globals.node,
- ...globals.mocha,
- },
-
- ecmaVersion: 2022,
- sourceType: "module",
- },
-
- rules: {
- "no-const-assign": "warn",
- "no-this-before-super": "warn",
- "no-undef": "warn",
- "no-unreachable": "warn",
- "no-unused-vars": "warn",
- "constructor-super": "warn",
- "valid-typeof": "warn",
- },
-}];
\ No newline at end of file
diff --git a/examples/merchant-client.ts b/examples/merchant-client.ts
new file mode 100644
index 0000000..3927cf9
--- /dev/null
+++ b/examples/merchant-client.ts
@@ -0,0 +1,54 @@
+import type { RequestInput } from '../shared/contracts.js';
+
+export interface MerchantClientOptions {
+ baseUrl: string;
+ apiKey: string;
+}
+export interface PaymentHandoff {
+ request_id: string;
+ status: string;
+ mode: string;
+ expires_at: string;
+ handoff: { tool: 'show_payment_request'; arguments: { request_id: string } };
+}
+/** Call from the requesting app's backend, using its authenticated order data. */
+export async function createPaymentRequest(
+ options: MerchantClientOptions,
+ order: RequestInput,
+): Promise {
+ const response = await fetch(`${options.baseUrl.replace(/\/$/, '')}/v1/payment-requests`, {
+ method: 'POST',
+ headers: {
+ Authorization: `Bearer ${options.apiKey}`,
+ 'Content-Type': 'application/json',
+ 'Idempotency-Key': order.merchant_order_reference,
+ },
+ body: JSON.stringify(order),
+ signal: AbortSignal.timeout(15_000),
+ redirect: 'error',
+ });
+ if (!response.ok)
+ throw new Error(
+ `Payment request failed (${response.status}). Retry the same order reference for transient failures.`,
+ );
+ return (await response.json()) as PaymentHandoff;
+}
+
+/** Fulfill only after this returns status=paid and mode matches your environment. */
+export async function getPaymentStatus(options: MerchantClientOptions, requestId: string) {
+ const response = await fetch(
+ `${options.baseUrl.replace(/\/$/, '')}/v1/payment-requests/${encodeURIComponent(requestId)}`,
+ {
+ headers: { Authorization: `Bearer ${options.apiKey}` },
+ signal: AbortSignal.timeout(15_000),
+ redirect: 'error',
+ },
+ );
+ if (!response.ok) throw new Error(`Status lookup failed (${response.status}).`);
+ return (await response.json()) as {
+ request_id: string;
+ status: string;
+ mode: string;
+ receipt?: unknown;
+ };
+}
diff --git a/examples/request-payment.ts b/examples/request-payment.ts
new file mode 100644
index 0000000..7ca6bb6
--- /dev/null
+++ b/examples/request-payment.ts
@@ -0,0 +1,31 @@
+import { randomUUID } from 'node:crypto';
+import { createPaymentRequest } from './merchant-client.js';
+
+const request = await createPaymentRequest(
+ {
+ baseUrl: process.env.LUCCI_PAY_URL ?? 'http://localhost:4242',
+ apiKey: process.env.LUCCI_MERCHANT_KEY ?? 'demo-merchant-key',
+ },
+ {
+ merchant_order_reference: `order-${randomUUID()}`,
+ payer_subject: process.env.LUCCI_PAYER_SUBJECT ?? 'demo-user',
+ currency: 'USD',
+ line_items: [
+ {
+ id: 'ceramic-cup',
+ name: 'Everyday ceramic cup',
+ quantity: 1,
+ unit_amount: 2400,
+ tax: 198,
+ discount: 0,
+ },
+ ],
+ fulfillment: {
+ type: 'pickup',
+ title: 'Pick up in store',
+ subtitle: 'Studio Supply Ā· Ready in 2 hours',
+ },
+ expires_in_seconds: 900,
+ },
+);
+console.log(JSON.stringify(request, null, 2));
diff --git a/extension.js b/extension.js
deleted file mode 100644
index a519963..0000000
--- a/extension.js
+++ /dev/null
@@ -1,164 +0,0 @@
-// The module 'vscode' contains the VS Code extensibility API
-// Import the module and reference it with the alias vscode in your code below
-const vscode = require('vscode');
-const { ExifTool } = require('exiftool-vendored');
-const path = require('path');
-const fs = require('fs');
-
-// This method is called when your extension is activated
-// Your extension is activated the very first time the command is executed
-
-/**
- * @param {vscode.ExtensionContext} context
- */
-function activate(context) {
-
- // Use the console to output diagnostic information (console.log) and errors (console.error)
- // This line of code will only be executed once when your extension is activated
- console.log('Metadata Scraper extension is now active');
-
- // Register the command to clean metadata from an image
- const cleanMetadataDisposable = vscode.commands.registerCommand('metascraper.cleanMetadata', async function (fileUri) {
- if (!fileUri) {
- // If command was triggered from command palette without a file
- vscode.window.showErrorMessage('Please right-click on an image file to clean its metadata.');
- return;
- }
-
- const filePath = fileUri.fsPath;
- const fileName = path.basename(filePath);
- const fileExt = path.extname(filePath).toLowerCase();
-
- // Check if the file is an image
- const supportedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.tiff', '.webp'];
- if (!supportedExtensions.includes(fileExt)) {
- vscode.window.showErrorMessage(`File "${fileName}" is not a supported image format.`);
- return;
- }
-
- try {
- // Show progress notification
- await vscode.window.withProgress({
- location: vscode.ProgressLocation.Notification,
- title: `Cleaning metadata from ${fileName}`,
- cancellable: false
- }, async (progress) => {
- // Initialize ExifTool
- const exiftool = new ExifTool();
-
- try {
- progress.report({ message: 'Reading metadata...' });
-
- // Read the current metadata (for informational purposes)
- const metadata = await exiftool.read(filePath);
- console.log('Original metadata:', metadata);
-
- progress.report({ message: 'Cleaning metadata...' });
-
- // Generate a temporary file path in the same directory
- const dirPath = path.dirname(filePath);
- const baseName = path.basename(filePath, fileExt);
- const tempFilePath = path.join(dirPath, `${baseName}-temp${fileExt}`);
-
- // Remove all metadata and write to the temp file
- await exiftool.write(
- filePath,
- { all: '' },
- ['-P', '-o', tempFilePath]
- );
-
- // Check if both files exist
- if (fs.existsSync(tempFilePath)) {
- // If original file exists, delete it first
- if (fs.existsSync(filePath)) {
- fs.unlinkSync(filePath);
- }
-
- // Then rename the temp file to the original name
- fs.renameSync(tempFilePath, filePath);
-
- vscode.window.showInformationMessage(`Successfully cleaned metadata from "${fileName}".`);
- } else {
- throw new Error('Failed to create cleaned image file');
- }
-
- // Close exiftool process
- await exiftool.end();
- } catch (err) {
- console.error('Error:', err);
- // Make sure to close exiftool process on error
- await exiftool.end();
- throw err;
- }
- });
- } catch (error) {
- vscode.window.showErrorMessage(`Failed to clean metadata: ${error.message}`);
- }
- });
-
- // Register the command to view metadata from an image
- const viewMetadataDisposable = vscode.commands.registerCommand('metascraper.viewMetadata', async function (fileUri) {
- if (!fileUri) {
- vscode.window.showErrorMessage('Please right-click on an image file to view its metadata.');
- return;
- }
-
- const filePath = fileUri.fsPath;
- const fileName = path.basename(filePath);
- const fileExt = path.extname(filePath).toLowerCase();
-
- // Check if the file is an image
- const supportedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.tiff', '.webp'];
- if (!supportedExtensions.includes(fileExt)) {
- vscode.window.showErrorMessage(`File "${fileName}" is not a supported image format.`);
- return;
- }
-
- try {
- // Show progress notification
- await vscode.window.withProgress({
- location: vscode.ProgressLocation.Notification,
- title: `Reading metadata from ${fileName}`,
- cancellable: false
- }, async (progress) => {
- // Initialize ExifTool
- const exiftool = new ExifTool();
-
- try {
- // Read the current metadata
- const metadata = await exiftool.read(filePath);
-
- // Create a virtual document to display metadata
- const metadataJson = JSON.stringify(metadata, null, 2);
- const metadataDoc = await vscode.workspace.openTextDocument({
- content: metadataJson,
- language: 'json'
- });
-
- // Show the document
- await vscode.window.showTextDocument(metadataDoc, { preview: true });
-
- // Close exiftool process
- await exiftool.end();
- } catch (err) {
- console.error('Error:', err);
- // Make sure to close exiftool process on error
- await exiftool.end();
- throw err;
- }
- });
- } catch (error) {
- vscode.window.showErrorMessage(`Failed to read metadata: ${error.message}`);
- }
- });
-
- context.subscriptions.push(cleanMetadataDisposable, viewMetadataDisposable);
-}
-
-// This method is called when your extension is deactivated
-function deactivate() {}
-
-module.exports = {
- activate,
- deactivate
-}
diff --git a/images/icon.png b/images/icon.png
deleted file mode 100644
index 798995a..0000000
Binary files a/images/icon.png and /dev/null differ
diff --git a/images/output.gif b/images/output.gif
deleted file mode 100644
index 1d9b9eb..0000000
Binary files a/images/output.gif and /dev/null differ
diff --git a/jsconfig.json b/jsconfig.json
deleted file mode 100644
index dbdd228..0000000
--- a/jsconfig.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "compilerOptions": {
- "module": "Node16",
- "target": "ES2022",
- "checkJs": false, /* Typecheck .js files. */
- "lib": [
- "ES2022"
- ]
- },
- "exclude": [
- "node_modules"
- ]
-}
diff --git a/package-lock.json b/package-lock.json
index fe77636..7afe4d7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,950 +1,866 @@
{
- "name": "metascraper",
- "version": "0.0.1",
+ "name": "lucci-pay",
+ "version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "metascraper",
- "version": "0.0.1",
+ "name": "lucci-pay",
+ "version": "0.1.0",
+ "license": "MIT",
"dependencies": {
- "exiftool-vendored": "^29.2.0"
+ "@modelcontextprotocol/ext-apps": "1.7.5",
+ "@modelcontextprotocol/sdk": "1.30.0",
+ "express": "^5.1.0",
+ "express-rate-limit": "^8.2.1",
+ "jose": "^6.1.3",
+ "react": "19.2.8",
+ "react-dom": "19.2.8",
+ "zod": "^4.3.6"
},
"devDependencies": {
- "@types/mocha": "^10.0.10",
- "@types/node": "20.x",
- "@types/vscode": "^1.98.0",
- "@vscode/test-cli": "^0.0.10",
- "@vscode/test-electron": "^2.4.1",
- "eslint": "^9.21.0"
+ "@types/express": "^5.0.6",
+ "@types/node": "^24.0.0",
+ "@types/react": "^19.2.0",
+ "@types/react-dom": "^19.2.0",
+ "esbuild": "^0.27.0",
+ "prettier": "3.9.6",
+ "tsx": "^4.21.0",
+ "typescript": "^5.9.3"
},
"engines": {
- "vscode": "^1.98.0"
+ "node": ">=24.0.0"
}
},
- "node_modules/@bcoe/v8-coverage": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
- "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@eslint-community/eslint-utils": {
- "version": "4.5.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz",
- "integrity": "sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==",
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
+ "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==",
+ "cpu": [
+ "ppc64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "eslint-visitor-keys": "^3.4.3"
- },
+ "optional": true,
+ "os": [
+ "aix"
+ ],
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
- },
- "peerDependencies": {
- "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ "node": ">=18"
}
},
- "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
- "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz",
+ "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
- "license": "Apache-2.0",
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
+ "node": ">=18"
}
},
- "node_modules/@eslint-community/regexpp": {
- "version": "4.12.1",
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
- "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz",
+ "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
"engines": {
- "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ "node": ">=18"
}
},
- "node_modules/@eslint/config-array": {
- "version": "0.19.2",
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz",
- "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==",
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz",
+ "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/object-schema": "^2.1.6",
- "debug": "^4.3.1",
- "minimatch": "^3.1.2"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">=18"
}
},
- "node_modules/@eslint/config-array/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz",
+ "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/@eslint/config-array/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
"engines": {
- "node": "*"
+ "node": ">=18"
}
},
- "node_modules/@eslint/config-helpers": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.1.0.tgz",
- "integrity": "sha512-kLrdPDJE1ckPo94kmPPf9Hfd0DU0Jw6oKYrhe+pwSC0iTUInmTa+w6fw8sGgcfkFJGNdWOUeOaDM4quW4a7OkA==",
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz",
+ "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "license": "Apache-2.0",
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">=18"
}
},
- "node_modules/@eslint/core": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz",
- "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==",
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@types/json-schema": "^7.0.15"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">=18"
}
},
- "node_modules/@eslint/eslintrc": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz",
- "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==",
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz",
+ "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "ajv": "^6.12.4",
- "debug": "^4.3.2",
- "espree": "^10.0.1",
- "globals": "^14.0.0",
- "ignore": "^5.2.0",
- "import-fresh": "^3.2.1",
- "js-yaml": "^4.1.0",
- "minimatch": "^3.1.2",
- "strip-json-comments": "^3.1.1"
- },
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
+ "node": ">=18"
}
},
- "node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz",
+ "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/@eslint/eslintrc/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": "*"
+ "node": ">=18"
}
},
- "node_modules/@eslint/js": {
- "version": "9.22.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.22.0.tgz",
- "integrity": "sha512-vLFajx9o8d1/oL2ZkpMYbkLv8nDB6yaIwFNt7nI4+I80U/z03SxmfOMsLbvWr3p7C+Wnoh//aOu2pQW8cS0HCQ==",
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz",
+ "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">=18"
}
},
- "node_modules/@eslint/object-schema": {
- "version": "2.1.6",
- "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz",
- "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==",
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz",
+ "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==",
+ "cpu": [
+ "ia32"
+ ],
"dev": true,
- "license": "Apache-2.0",
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">=18"
}
},
- "node_modules/@eslint/plugin-kit": {
- "version": "0.2.7",
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz",
- "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==",
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz",
+ "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==",
+ "cpu": [
+ "loong64"
+ ],
"dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@eslint/core": "^0.12.0",
- "levn": "^0.4.1"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">=18"
}
},
- "node_modules/@humanfs/core": {
- "version": "0.19.1",
- "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
- "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz",
+ "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==",
+ "cpu": [
+ "mips64el"
+ ],
"dev": true,
- "license": "Apache-2.0",
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=18.18.0"
+ "node": ">=18"
}
},
- "node_modules/@humanfs/node": {
- "version": "0.16.6",
- "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
- "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz",
+ "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==",
+ "cpu": [
+ "ppc64"
+ ],
"dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@humanfs/core": "^0.19.1",
- "@humanwhocodes/retry": "^0.3.0"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=18.18.0"
+ "node": ">=18"
}
},
- "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
- "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz",
+ "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==",
+ "cpu": [
+ "riscv64"
+ ],
"dev": true,
- "license": "Apache-2.0",
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=18.18"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
+ "node": ">=18"
}
},
- "node_modules/@humanwhocodes/module-importer": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
- "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz",
+ "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==",
+ "cpu": [
+ "s390x"
+ ],
"dev": true,
- "license": "Apache-2.0",
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=12.22"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
+ "node": ">=18"
}
},
- "node_modules/@humanwhocodes/retry": {
- "version": "0.4.2",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz",
- "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==",
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz",
+ "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "license": "Apache-2.0",
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=18.18"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/nzakas"
+ "node": ">=18"
}
},
- "node_modules/@isaacs/cliui": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
- "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
- "license": "ISC",
- "dependencies": {
- "string-width": "^5.1.2",
- "string-width-cjs": "npm:string-width@^4.2.0",
- "strip-ansi": "^7.0.1",
- "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
- "wrap-ansi": "^8.1.0",
- "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
"engines": {
- "node": ">=12"
+ "node": ">=18"
}
},
- "node_modules/@istanbuljs/schema": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
- "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz",
+ "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
"engines": {
- "node": ">=8"
+ "node": ">=18"
}
},
- "node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz",
+ "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
"engines": {
- "node": ">=6.0.0"
+ "node": ">=18"
}
},
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
- "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
- "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz",
+ "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/@photostructure/tz-lookup": {
- "version": "11.1.0",
- "resolved": "https://registry.npmjs.org/@photostructure/tz-lookup/-/tz-lookup-11.1.0.tgz",
- "integrity": "sha512-UywyhMwUdVU2aH5ls7EweTEyPpXbDkgC//Nnsm/lWfpae8WX3N33Yy0/aBmb/Pd9+qEtgcFMYTtN/Htb+cd0ZA==",
- "license": "CC0-1.0"
- },
- "node_modules/@pkgjs/parseargs": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
- "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz",
+ "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
"optional": true,
+ "os": [
+ "openharmony"
+ ],
"engines": {
- "node": ">=14"
+ "node": ">=18"
}
},
- "node_modules/@types/estree": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
- "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/istanbul-lib-coverage": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
- "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/json-schema": {
- "version": "7.0.15",
- "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
- "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/luxon": {
- "version": "3.4.2",
- "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz",
- "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==",
- "license": "MIT"
- },
- "node_modules/@types/mocha": {
- "version": "10.0.10",
- "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz",
- "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/node": {
- "version": "20.17.24",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.24.tgz",
- "integrity": "sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA==",
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz",
+ "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "undici-types": "~6.19.2"
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/@types/vscode": {
- "version": "1.98.0",
- "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.98.0.tgz",
- "integrity": "sha512-+KuiWhpbKBaG2egF+51KjbGWatTH5BbmWQjSLMDCssb4xF8FJnW4nGH4nuAdOOfMbpD0QlHtI+C3tPq+DoKElg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@vscode/test-cli": {
- "version": "0.0.10",
- "resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.10.tgz",
- "integrity": "sha512-B0mMH4ia+MOOtwNiLi79XhA+MLmUItIC8FckEuKrVAVriIuSWjt7vv4+bF8qVFiNFe4QRfzPaIZk39FZGWEwHA==",
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz",
+ "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@types/mocha": "^10.0.2",
- "c8": "^9.1.0",
- "chokidar": "^3.5.3",
- "enhanced-resolve": "^5.15.0",
- "glob": "^10.3.10",
- "minimatch": "^9.0.3",
- "mocha": "^10.2.0",
- "supports-color": "^9.4.0",
- "yargs": "^17.7.2"
- },
- "bin": {
- "vscode-test": "out/bin.mjs"
- },
+ "optional": true,
+ "os": [
+ "win32"
+ ],
"engines": {
"node": ">=18"
}
},
- "node_modules/@vscode/test-electron": {
- "version": "2.4.1",
- "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.4.1.tgz",
- "integrity": "sha512-Gc6EdaLANdktQ1t+zozoBVRynfIsMKMc94Svu1QreOBC8y76x4tvaK32TljrLi1LI2+PK58sDVbL7ALdqf3VRQ==",
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz",
+ "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==",
+ "cpu": [
+ "ia32"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "http-proxy-agent": "^7.0.2",
- "https-proxy-agent": "^7.0.5",
- "jszip": "^3.10.1",
- "ora": "^7.0.1",
- "semver": "^7.6.2"
- },
+ "optional": true,
+ "os": [
+ "win32"
+ ],
"engines": {
- "node": ">=16"
+ "node": ">=18"
}
},
- "node_modules/acorn": {
- "version": "8.14.1",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
- "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz",
+ "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "bin": {
- "acorn": "bin/acorn"
- },
+ "optional": true,
+ "os": [
+ "win32"
+ ],
"engines": {
- "node": ">=0.4.0"
+ "node": ">=18"
}
},
- "node_modules/acorn-jsx": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
- "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
- "dev": true,
+ "node_modules/@hono/node-server": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.1.tgz",
+ "integrity": "sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg==",
"license": "MIT",
+ "engines": {
+ "node": ">=20"
+ },
"peerDependencies": {
- "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ "hono": "^4"
}
},
- "node_modules/agent-base": {
- "version": "7.1.3",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
- "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
- "dev": true,
+ "node_modules/@modelcontextprotocol/ext-apps": {
+ "version": "1.7.5",
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/ext-apps/-/ext-apps-1.7.5.tgz",
+ "integrity": "sha512-TjPH2S2y5UEGKhmI6+XGFuqfqOV4ppe1x6DA3txnUaEWkgtA4G5vo14jGKFZmegdkZ1H4QMLyujLvoU1BEdnAg==",
"license": "MIT",
- "engines": {
- "node": ">= 14"
+ "workspaces": [
+ "examples/*"
+ ],
+ "dependencies": {
+ "@standard-schema/spec": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "peerDependencies": {
+ "@modelcontextprotocol/sdk": "^1.29.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "zod": "^3.25.0 || ^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
}
},
- "node_modules/ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
- "dev": true,
+ "node_modules/@modelcontextprotocol/sdk": {
+ "version": "1.30.0",
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz",
+ "integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==",
"license": "MIT",
"dependencies": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
+ "@hono/node-server": "^1.19.9 || ^2.0.5",
+ "ajv": "^8.17.1",
+ "ajv-formats": "^3.0.1",
+ "content-type": "^1.0.5",
+ "cors": "^2.8.5",
+ "cross-spawn": "^7.0.5",
+ "eventsource": "^3.0.2",
+ "eventsource-parser": "^3.0.0",
+ "express": "^5.2.1",
+ "express-rate-limit": "^8.2.1",
+ "hono": "^4.11.4",
+ "jose": "^6.1.3",
+ "json-schema-typed": "^8.0.2",
+ "pkce-challenge": "^5.0.0",
+ "raw-body": "^3.0.0",
+ "zod": "^3.25 || ^4.0",
+ "zod-to-json-schema": "^3.25.1"
},
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@cfworker/json-schema": "^4.1.1",
+ "zod": "^3.25 || ^4.0"
+ },
+ "peerDependenciesMeta": {
+ "@cfworker/json-schema": {
+ "optional": true
+ },
+ "zod": {
+ "optional": false
+ }
}
},
- "node_modules/ansi-colors": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
- "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "license": "MIT"
+ },
+ "node_modules/@types/body-parser": {
+ "version": "1.19.6",
+ "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
+ "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=6"
+ "dependencies": {
+ "@types/connect": "*",
+ "@types/node": "*"
}
},
- "node_modules/ansi-regex": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
- "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
+ "node_modules/@types/connect": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
+ "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ "dependencies": {
+ "@types/node": "*"
}
},
- "node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "node_modules/@types/express": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz",
+ "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^5.0.0",
+ "@types/serve-static": "^2"
}
},
- "node_modules/anymatch": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
- "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "node_modules/@types/express-serve-static-core": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.3.tgz",
+ "integrity": "sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==",
"dev": true,
- "license": "ISC",
+ "license": "MIT",
"dependencies": {
- "normalize-path": "^3.0.0",
- "picomatch": "^2.0.4"
- },
- "engines": {
- "node": ">= 8"
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*",
+ "@types/send": "*"
}
},
- "node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "node_modules/@types/http-errors": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
+ "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
"dev": true,
- "license": "Python-2.0"
+ "license": "MIT"
},
- "node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "node_modules/@types/node": {
+ "version": "24.13.3",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz",
+ "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==",
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.18.0"
+ }
},
- "node_modules/base64-js": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
- "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "node_modules/@types/qs": {
+ "version": "6.15.1",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz",
+ "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==",
"dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
"license": "MIT"
},
- "node_modules/batch-cluster": {
- "version": "13.0.0",
- "resolved": "https://registry.npmjs.org/batch-cluster/-/batch-cluster-13.0.0.tgz",
- "integrity": "sha512-EreW0Vi8TwovhYUHBXXRA5tthuU2ynGsZFlboyMJHCCUXYa2AjgwnE3ubBOJs2xJLcuXFJbi6c/8pH5+FVj8Og==",
- "license": "MIT",
- "engines": {
- "node": ">=14"
- }
+ "node_modules/@types/range-parser": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
+ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
+ "dev": true,
+ "license": "MIT"
},
- "node_modules/binary-extensions": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
- "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "node_modules/@types/react": {
+ "version": "19.2.18",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz",
+ "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "dependencies": {
+ "csstype": "^3.2.2"
}
},
- "node_modules/bl": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz",
- "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==",
+ "node_modules/@types/react-dom": {
+ "version": "19.2.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.7.tgz",
+ "integrity": "sha512-I8bPpDLcHBv1qiIiXDCy71Rt8eQDKJP0sMSWJphDdAcdqiJ1sGpZamavoEIRZmYzjia9LuEb2HlYdDpmoENpvQ==",
"dev": true,
"license": "MIT",
- "dependencies": {
- "buffer": "^6.0.3",
- "inherits": "^2.0.4",
- "readable-stream": "^3.4.0"
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
}
},
- "node_modules/bl/node_modules/readable-stream": {
- "version": "3.6.2",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
- "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "node_modules/@types/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
- },
- "engines": {
- "node": ">= 6"
+ "@types/node": "*"
}
},
- "node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "node_modules/@types/serve-static": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz",
+ "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "balanced-match": "^1.0.0"
+ "@types/http-errors": "*",
+ "@types/node": "*"
}
},
- "node_modules/braces": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
- "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
- "dev": true,
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
"license": "MIT",
"dependencies": {
- "fill-range": "^7.1.1"
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
},
"engines": {
- "node": ">=8"
+ "node": ">= 0.6"
}
},
- "node_modules/browser-stdout": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
- "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/buffer": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
- "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
+ "node_modules/ajv": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
+ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
"license": "MIT",
"dependencies": {
- "base64-js": "^1.3.1",
- "ieee754": "^1.2.1"
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
}
},
- "node_modules/c8": {
- "version": "9.1.0",
- "resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz",
- "integrity": "sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==",
- "dev": true,
- "license": "ISC",
+ "node_modules/ajv-formats": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
+ "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
+ "license": "MIT",
"dependencies": {
- "@bcoe/v8-coverage": "^0.2.3",
- "@istanbuljs/schema": "^0.1.3",
- "find-up": "^5.0.0",
- "foreground-child": "^3.1.1",
- "istanbul-lib-coverage": "^3.2.0",
- "istanbul-lib-report": "^3.0.1",
- "istanbul-reports": "^3.1.6",
- "test-exclude": "^6.0.0",
- "v8-to-istanbul": "^9.0.0",
- "yargs": "^17.7.2",
- "yargs-parser": "^21.1.1"
+ "ajv": "^8.0.0"
},
- "bin": {
- "c8": "bin/c8.js"
+ "peerDependencies": {
+ "ajv": "^8.0.0"
},
- "engines": {
- "node": ">=14.14.0"
- }
- },
- "node_modules/callsites": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
- "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
}
},
- "node_modules/camelcase": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
- "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
- "dev": true,
+ "node_modules/body-parser": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
+ "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
"license": "MIT",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^2.0.0",
+ "debug": "^4.4.3",
+ "http-errors": "^2.0.1",
+ "iconv-lite": "^0.7.2",
+ "on-finished": "^2.4.1",
+ "qs": "^6.15.2",
+ "raw-body": "^3.0.2",
+ "type-is": "^2.1.0"
+ },
"engines": {
- "node": ">=10"
+ "node": ">=18"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/chalk": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
- "dev": true,
+ "node_modules/body-parser/node_modules/content-type": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz",
+ "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==",
"license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
"engines": {
- "node": ">=10"
+ "node": ">=18"
},
"funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/chalk/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
"engines": {
- "node": ">=8"
+ "node": ">= 0.8"
}
},
- "node_modules/chokidar": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
- "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
- "dev": true,
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
- "anymatch": "~3.1.2",
- "braces": "~3.0.2",
- "glob-parent": "~5.1.2",
- "is-binary-path": "~2.1.0",
- "is-glob": "~4.0.1",
- "normalize-path": "~3.0.0",
- "readdirp": "~3.6.0"
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
},
"engines": {
- "node": ">= 8.10.0"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.2"
+ "node": ">= 0.4"
}
},
- "node_modules/cli-cursor": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
- "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==",
- "dev": true,
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"license": "MIT",
"dependencies": {
- "restore-cursor": "^4.0.0"
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
},
"engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/cli-spinners": {
- "version": "2.9.2",
- "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
- "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
- "dev": true,
+ "node_modules/content-disposition": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
+ "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
"license": "MIT",
"engines": {
- "node": ">=6"
+ "node": ">=18"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/cliui": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
- "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.1",
- "wrap-ansi": "^7.0.0"
- },
- "engines": {
- "node": ">=12"
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/cliui/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
"license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">= 0.6"
}
},
- "node_modules/cliui/node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/cliui/node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
"engines": {
- "node": ">=8"
+ "node": ">= 0.6"
}
},
- "node_modules/cliui/node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
+ "node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
"license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
"engines": {
- "node": ">=8"
+ "node": ">=6.6.0"
}
},
- "node_modules/cliui/node_modules/wrap-ansi": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dev": true,
+ "node_modules/cors": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
"license": "MIT",
"dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
+ "object-assign": "^4",
+ "vary": "^1"
},
"engines": {
- "node": ">=10"
+ "node": ">= 0.10"
},
"funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/core-util-is": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
- "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
@@ -955,11 +871,17 @@
"node": ">= 8"
}
},
- "node_modules/debug": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
- "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -973,499 +895,500 @@
}
}
},
- "node_modules/decamelize": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
- "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
- "dev": true,
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"license": "MIT",
"engines": {
- "node": ">=10"
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
},
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "engines": {
+ "node": ">= 0.4"
}
},
- "node_modules/deep-is": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
- "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
- "dev": true,
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT"
},
- "node_modules/diff": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
- "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
- "dev": true,
- "license": "BSD-3-Clause",
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
"engines": {
- "node": ">=0.3.1"
+ "node": ">= 0.8"
}
},
- "node_modules/eastasianwidth": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
- "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
- "dev": true,
- "license": "MIT"
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
},
- "node_modules/emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true,
- "license": "MIT"
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
},
- "node_modules/enhanced-resolve": {
- "version": "5.18.1",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
- "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==",
- "dev": true,
+ "node_modules/es-object-atoms": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
+ "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
"license": "MIT",
"dependencies": {
- "graceful-fs": "^4.2.4",
- "tapable": "^2.2.0"
+ "es-errors": "^1.3.0"
},
"engines": {
- "node": ">=10.13.0"
+ "node": ">= 0.4"
}
},
- "node_modules/escalade": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
- "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "node_modules/esbuild": {
+ "version": "0.27.7",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz",
+ "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==",
"dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.7",
+ "@esbuild/android-arm": "0.27.7",
+ "@esbuild/android-arm64": "0.27.7",
+ "@esbuild/android-x64": "0.27.7",
+ "@esbuild/darwin-arm64": "0.27.7",
+ "@esbuild/darwin-x64": "0.27.7",
+ "@esbuild/freebsd-arm64": "0.27.7",
+ "@esbuild/freebsd-x64": "0.27.7",
+ "@esbuild/linux-arm": "0.27.7",
+ "@esbuild/linux-arm64": "0.27.7",
+ "@esbuild/linux-ia32": "0.27.7",
+ "@esbuild/linux-loong64": "0.27.7",
+ "@esbuild/linux-mips64el": "0.27.7",
+ "@esbuild/linux-ppc64": "0.27.7",
+ "@esbuild/linux-riscv64": "0.27.7",
+ "@esbuild/linux-s390x": "0.27.7",
+ "@esbuild/linux-x64": "0.27.7",
+ "@esbuild/netbsd-arm64": "0.27.7",
+ "@esbuild/netbsd-x64": "0.27.7",
+ "@esbuild/openbsd-arm64": "0.27.7",
+ "@esbuild/openbsd-x64": "0.27.7",
+ "@esbuild/openharmony-arm64": "0.27.7",
+ "@esbuild/sunos-x64": "0.27.7",
+ "@esbuild/win32-arm64": "0.27.7",
+ "@esbuild/win32-ia32": "0.27.7",
+ "@esbuild/win32-x64": "0.27.7"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"license": "MIT",
"engines": {
- "node": ">=6"
+ "node": ">= 0.6"
}
},
- "node_modules/escape-string-regexp": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
- "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
- "dev": true,
+ "node_modules/eventsource": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
+ "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
"license": "MIT",
+ "dependencies": {
+ "eventsource-parser": "^3.0.1"
+ },
"engines": {
- "node": ">=10"
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/eventsource-parser": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz",
+ "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/express": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.1",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/eslint": {
- "version": "9.22.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.22.0.tgz",
- "integrity": "sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ==",
- "dev": true,
+ "node_modules/express-rate-limit": {
+ "version": "8.7.0",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.0.tgz",
+ "integrity": "sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==",
"license": "MIT",
"dependencies": {
- "@eslint-community/eslint-utils": "^4.2.0",
- "@eslint-community/regexpp": "^4.12.1",
- "@eslint/config-array": "^0.19.2",
- "@eslint/config-helpers": "^0.1.0",
- "@eslint/core": "^0.12.0",
- "@eslint/eslintrc": "^3.3.0",
- "@eslint/js": "9.22.0",
- "@eslint/plugin-kit": "^0.2.7",
- "@humanfs/node": "^0.16.6",
- "@humanwhocodes/module-importer": "^1.0.1",
- "@humanwhocodes/retry": "^0.4.2",
- "@types/estree": "^1.0.6",
- "@types/json-schema": "^7.0.15",
- "ajv": "^6.12.4",
- "chalk": "^4.0.0",
- "cross-spawn": "^7.0.6",
- "debug": "^4.3.2",
- "escape-string-regexp": "^4.0.0",
- "eslint-scope": "^8.3.0",
- "eslint-visitor-keys": "^4.2.0",
- "espree": "^10.3.0",
- "esquery": "^1.5.0",
- "esutils": "^2.0.2",
- "fast-deep-equal": "^3.1.3",
- "file-entry-cache": "^8.0.0",
- "find-up": "^5.0.0",
- "glob-parent": "^6.0.2",
- "ignore": "^5.2.0",
- "imurmurhash": "^0.1.4",
- "is-glob": "^4.0.0",
- "json-stable-stringify-without-jsonify": "^1.0.1",
- "lodash.merge": "^4.6.2",
- "minimatch": "^3.1.2",
- "natural-compare": "^1.4.0",
- "optionator": "^0.9.3"
- },
- "bin": {
- "eslint": "bin/eslint.js"
+ "debug": "^4.4.3",
+ "ip-address": "^10.2.0"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">= 16"
},
"funding": {
- "url": "https://eslint.org/donate"
+ "url": "https://github.com/sponsors/express-rate-limit"
},
"peerDependencies": {
- "jiti": "*"
- },
- "peerDependenciesMeta": {
- "jiti": {
- "optional": true
- }
+ "express": ">= 4.11"
}
},
- "node_modules/eslint-scope": {
- "version": "8.3.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz",
- "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==",
- "dev": true,
- "license": "BSD-2-Clause",
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "license": "MIT"
+ },
+ "node_modules/fast-uri": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz",
+ "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/finalhandler": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
+ "license": "MIT",
"dependencies": {
- "esrecurse": "^4.3.0",
- "estraverse": "^5.2.0"
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
},
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": ">= 18.0.0"
},
"funding": {
- "url": "https://opencollective.com/eslint"
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/eslint-visitor-keys": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
- "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
- "dev": true,
- "license": "Apache-2.0",
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
+ "node": ">= 0.6"
}
},
- "node_modules/eslint/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
"license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/eslint/node_modules/glob-parent": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.3"
- },
"engines": {
- "node": ">=10.13.0"
+ "node": ">= 0.8"
}
},
- "node_modules/eslint/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
"engines": {
- "node": "*"
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
- "node_modules/espree": {
- "version": "10.3.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
- "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "acorn": "^8.14.0",
- "acorn-jsx": "^5.3.2",
- "eslint-visitor-keys": "^4.2.0"
- },
- "engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
- },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
"funding": {
- "url": "https://opencollective.com/eslint"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/esquery": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
- "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
- "dev": true,
- "license": "BSD-3-Clause",
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
"dependencies": {
- "estraverse": "^5.1.0"
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
},
"engines": {
- "node": ">=0.10"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/esrecurse": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
- "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
- "dev": true,
- "license": "BSD-2-Clause",
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
"dependencies": {
- "estraverse": "^5.2.0"
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
},
"engines": {
- "node": ">=4.0"
+ "node": ">= 0.4"
}
},
- "node_modules/estraverse": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
- "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
- "dev": true,
- "license": "BSD-2-Clause",
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
"engines": {
- "node": ">=4.0"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/esutils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
- "dev": true,
- "license": "BSD-2-Clause",
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
"engines": {
- "node": ">=0.10.0"
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/exiftool-vendored": {
- "version": "29.2.0",
- "resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-29.2.0.tgz",
- "integrity": "sha512-bZnjVAV/0T++o9gPf8uIa4B6DsixnkLTNirD2xM6ciRXgonp28viFG/D3bZiXUrRRV9u3uUmZS/ipieQcEMzWA==",
+ "node_modules/hasown": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
"license": "MIT",
"dependencies": {
- "@photostructure/tz-lookup": "^11.1.0",
- "@types/luxon": "^3.4.2",
- "batch-cluster": "^13.0.0",
- "he": "^1.2.0",
- "luxon": "^3.5.0"
+ "function-bind": "^1.1.2"
},
- "optionalDependencies": {
- "exiftool-vendored.exe": "13.25.0",
- "exiftool-vendored.pl": "13.25.0"
+ "engines": {
+ "node": ">= 0.4"
}
},
- "node_modules/exiftool-vendored.exe": {
- "version": "13.25.0",
- "resolved": "https://registry.npmjs.org/exiftool-vendored.exe/-/exiftool-vendored.exe-13.25.0.tgz",
- "integrity": "sha512-TTc8XL07ApDQl/+mB+ocbFIU3vMhqtHyilF508cyZ0mMyp6S+5d7ETyoGhndylTCtZRkp6n3LPh3uPpUrYrrOA==",
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/exiftool-vendored.pl": {
- "version": "13.25.0",
- "resolved": "https://registry.npmjs.org/exiftool-vendored.pl/-/exiftool-vendored.pl-13.25.0.tgz",
- "integrity": "sha512-aTIKz/E+UO1fayrPUy/6OoBC+ryjL57rqew4uFQPa3Ed5dle0W8YHjkROvd24EkQgRX4wmmskPOAbtAW8fvQRQ==",
- "license": "MIT",
- "optional": true,
- "os": [
- "!win32"
- ]
- },
- "node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-json-stable-stringify": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-levenshtein": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/file-entry-cache": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
- "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
- "dev": true,
+ "node_modules/hono": {
+ "version": "4.13.7",
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.7.tgz",
+ "integrity": "sha512-c8/gF9ac8Y78/agExVocyLevgR+JlpNB444Py0FSX8pJoPdYUfUzRcXtYEYGwt6l19qIlVZPN5Mfsw9jFShmQQ==",
"license": "MIT",
- "dependencies": {
- "flat-cache": "^4.0.0"
- },
"engines": {
- "node": ">=16.0.0"
+ "node": ">=16.9.0"
}
},
- "node_modules/fill-range": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
- "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
- "dev": true,
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
"license": "MIT",
"dependencies": {
- "to-regex-range": "^5.0.1"
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
},
"engines": {
- "node": ">=8"
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/find-up": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
- "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
- "dev": true,
+ "node_modules/iconv-lite": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz",
+ "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==",
"license": "MIT",
"dependencies": {
- "locate-path": "^6.0.0",
- "path-exists": "^4.0.0"
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
- "node": ">=10"
+ "node": ">=0.10.0"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/flat": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
- "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
- "dev": true,
- "license": "BSD-3-Clause",
- "bin": {
- "flat": "cli.js"
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ip-address": {
+ "version": "10.7.0",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.0.tgz",
+ "integrity": "sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
}
},
- "node_modules/flat-cache": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
- "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
- "dev": true,
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
"license": "MIT",
- "dependencies": {
- "flatted": "^3.2.9",
- "keyv": "^4.5.4"
- },
"engines": {
- "node": ">=16"
+ "node": ">= 0.10"
}
},
- "node_modules/flatted": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
- "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
- "dev": true,
+ "node_modules/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
- "node_modules/foreground-child": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
- "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "cross-spawn": "^7.0.6",
- "signal-exit": "^4.0.1"
- },
- "engines": {
- "node": ">=14"
- },
+ "node_modules/jose": {
+ "version": "6.2.12",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz",
+ "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==",
+ "license": "MIT",
"funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "url": "https://github.com/sponsors/panva"
}
},
- "node_modules/fs.realpath": {
+ "node_modules/json-schema-traverse": {
"version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
- "dev": true,
- "license": "ISC"
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "license": "MIT"
},
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
- "hasInstallScript": true,
+ "node_modules/json-schema-typed": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz",
+ "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
"engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ "node": ">= 0.4"
}
},
- "node_modules/get-caller-file": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
- "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
- "dev": true,
- "license": "ISC",
+ "node_modules/media-typer": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz",
+ "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==",
+ "license": "MIT",
"engines": {
- "node": "6.* || 8.* || >= 10.*"
- }
- },
- "node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^3.1.2",
- "minimatch": "^9.0.4",
- "minipass": "^7.1.2",
- "package-json-from-dist": "^1.0.0",
- "path-scurry": "^1.11.1"
- },
- "bin": {
- "glob": "dist/esm/bin.mjs"
+ "node": ">= 0.8"
},
"funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/glob-parent": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
- "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/globals": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
- "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
- "dev": true,
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
"license": "MIT",
"engines": {
"node": ">=18"
@@ -1474,1704 +1397,1048 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/graceful-fs": {
- "version": "4.2.11",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
- "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
+ "node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
"license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">= 0.6"
}
},
- "node_modules/he": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
- "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
"license": "MIT",
- "bin": {
- "he": "bin/he"
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/html-escaper": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
- "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
- "dev": true,
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
- "node_modules/http-proxy-agent": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
- "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
- "dev": true,
+ "node_modules/negotiator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz",
+ "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==",
"license": "MIT",
"dependencies": {
- "agent-base": "^7.1.0",
- "debug": "^4.3.4"
+ "content-type": "^2.1.0"
},
"engines": {
- "node": ">= 14"
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/https-proxy-agent": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
- "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
- "dev": true,
+ "node_modules/negotiator/node_modules/content-type": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz",
+ "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==",
"license": "MIT",
- "dependencies": {
- "agent-base": "^7.1.2",
- "debug": "4"
- },
"engines": {
- "node": ">= 14"
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/ieee754": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
- "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "BSD-3-Clause"
- },
- "node_modules/ignore": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
- "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
- "dev": true,
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"license": "MIT",
"engines": {
- "node": ">= 4"
+ "node": ">=0.10.0"
}
},
- "node_modules/immediate": {
- "version": "3.0.6",
- "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
- "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/import-fresh": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
- "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
- "dev": true,
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"license": "MIT",
- "dependencies": {
- "parent-module": "^1.0.0",
- "resolve-from": "^4.0.0"
- },
"engines": {
- "node": ">=6"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/imurmurhash": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
- "dev": true,
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
"engines": {
- "node": ">=0.8.19"
+ "node": ">= 0.8"
}
},
- "node_modules/inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
- "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
- "dev": true,
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"dependencies": {
- "once": "^1.3.0",
"wrappy": "1"
}
},
- "node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/is-binary-path": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
- "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
- "dev": true,
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"license": "MIT",
- "dependencies": {
- "binary-extensions": "^2.0.0"
- },
"engines": {
- "node": ">=8"
+ "node": ">= 0.8"
}
},
- "node_modules/is-extglob": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
- "dev": true,
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"license": "MIT",
"engines": {
- "node": ">=0.10.0"
+ "node": ">=8"
}
},
- "node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true,
+ "node_modules/path-to-regexp": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
+ "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
"license": "MIT",
- "engines": {
- "node": ">=8"
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/is-glob": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
- "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
- "dev": true,
+ "node_modules/pkce-challenge": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz",
+ "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==",
"license": "MIT",
- "dependencies": {
- "is-extglob": "^2.1.1"
- },
"engines": {
- "node": ">=0.10.0"
+ "node": ">=16.20.0"
}
},
- "node_modules/is-interactive": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz",
- "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==",
+ "node_modules/prettier": {
+ "version": "3.9.6",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz",
+ "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==",
"dev": true,
"license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
"engines": {
- "node": ">=12"
+ "node": ">=14"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/prettier/prettier?sponsor=1"
}
},
- "node_modules/is-number": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
- "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
- "dev": true,
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
"license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
"engines": {
- "node": ">=0.12.0"
+ "node": ">= 0.10"
}
},
- "node_modules/is-plain-obj": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
- "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
- "dev": true,
- "license": "MIT",
+ "node_modules/qs": {
+ "version": "6.16.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz",
+ "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "es-define-property": "^1.0.1",
+ "side-channel": "^1.1.1"
+ },
"engines": {
- "node": ">=8"
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/is-unicode-supported": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
- "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
- "dev": true,
+ "node_modules/range-parser": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz",
+ "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==",
"license": "MIT",
"engines": {
- "node": ">=10"
+ "node": ">= 0.6"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/isarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/istanbul-lib-coverage": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
- "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=8"
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/istanbul-lib-report": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
- "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
- "dev": true,
- "license": "BSD-3-Clause",
+ "node_modules/raw-body": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
+ "license": "MIT",
"dependencies": {
- "istanbul-lib-coverage": "^3.0.0",
- "make-dir": "^4.0.0",
- "supports-color": "^7.1.0"
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.7.0",
+ "unpipe": "~1.0.0"
},
"engines": {
- "node": ">=10"
+ "node": ">= 0.10"
}
},
- "node_modules/istanbul-lib-report/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
+ "node_modules/react": {
+ "version": "19.2.8",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
+ "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
"license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
"engines": {
- "node": ">=8"
+ "node": ">=0.10.0"
}
},
- "node_modules/istanbul-reports": {
- "version": "3.1.7",
- "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
- "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==",
- "dev": true,
- "license": "BSD-3-Clause",
+ "node_modules/react-dom": {
+ "version": "19.2.8",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
+ "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
+ "license": "MIT",
"dependencies": {
- "html-escaper": "^2.0.0",
- "istanbul-lib-report": "^3.0.0"
+ "scheduler": "^0.27.0"
},
- "engines": {
- "node": ">=8"
+ "peerDependencies": {
+ "react": "^19.2.8"
}
},
- "node_modules/jackspeak": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
- "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "@isaacs/cliui": "^8.0.2"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- },
- "optionalDependencies": {
- "@pkgjs/parseargs": "^0.11.0"
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
}
},
- "node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dev": true,
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
"license": "MIT",
"dependencies": {
- "argparse": "^2.0.1"
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
},
- "bin": {
- "js-yaml": "bin/js-yaml.js"
+ "engines": {
+ "node": ">= 18"
}
},
- "node_modules/json-buffer": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
- "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/json-schema-traverse": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "dev": true,
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
- "node_modules/json-stable-stringify-without-jsonify": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
- "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
- "dev": true,
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
"license": "MIT"
},
- "node_modules/jszip": {
- "version": "3.10.1",
- "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
- "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
- "dev": true,
- "license": "(MIT OR GPL-3.0-or-later)",
+ "node_modules/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+ "license": "MIT",
"dependencies": {
- "lie": "~3.3.0",
- "pako": "~1.0.2",
- "readable-stream": "~2.3.6",
- "setimmediate": "^1.0.5"
+ "debug": "^4.4.3",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.1",
+ "mime-types": "^3.0.2",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/keyv": {
- "version": "4.5.4",
- "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
- "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
- "dev": true,
+ "node_modules/serve-static": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
"license": "MIT",
"dependencies": {
- "json-buffer": "3.0.1"
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/levn": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
- "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
- "dev": true,
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"license": "MIT",
"dependencies": {
- "prelude-ls": "^1.2.1",
- "type-check": "~0.4.0"
+ "shebang-regex": "^3.0.0"
},
"engines": {
- "node": ">= 0.8.0"
+ "node": ">=8"
}
},
- "node_modules/lie": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
- "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
- "dev": true,
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"license": "MIT",
- "dependencies": {
- "immediate": "~3.0.5"
+ "engines": {
+ "node": ">=8"
}
},
- "node_modules/locate-path": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
- "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
- "dev": true,
+ "node_modules/side-channel": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
+ "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
"license": "MIT",
"dependencies": {
- "p-locate": "^5.0.0"
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4",
+ "side-channel-list": "^1.0.1",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
},
"engines": {
- "node": ">=10"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/lodash.merge": {
- "version": "4.6.2",
- "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
- "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/log-symbols": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
- "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
- "dev": true,
+ "node_modules/side-channel-list": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
"license": "MIT",
"dependencies": {
- "chalk": "^4.1.0",
- "is-unicode-supported": "^0.1.0"
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4"
},
"engines": {
- "node": ">=10"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/lru-cache": {
- "version": "10.4.3",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
- "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/luxon": {
- "version": "3.5.0",
- "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz",
- "integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==",
- "license": "MIT",
- "engines": {
- "node": ">=12"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/make-dir": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
- "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
- "dev": true,
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
"license": "MIT",
"dependencies": {
- "semver": "^7.5.3"
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
},
"engines": {
- "node": ">=10"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/mimic-fn": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
- "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
- "dev": true,
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
"license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
- "dev": true,
- "license": "ISC",
"dependencies": {
- "brace-expansion": "^2.0.1"
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
},
"engines": {
- "node": ">=16 || 14 >=14.17"
+ "node": ">= 0.4"
},
"funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/minipass": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
- "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
- "dev": true,
- "license": "ISC",
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
"engines": {
- "node": ">=16 || 14 >=14.17"
+ "node": ">=0.6"
}
},
- "node_modules/mocha": {
- "version": "10.8.2",
- "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz",
- "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==",
+ "node_modules/tsx": {
+ "version": "4.23.13",
+ "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.13.tgz",
+ "integrity": "sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "ansi-colors": "^4.1.3",
- "browser-stdout": "^1.3.1",
- "chokidar": "^3.5.3",
- "debug": "^4.3.5",
- "diff": "^5.2.0",
- "escape-string-regexp": "^4.0.0",
- "find-up": "^5.0.0",
- "glob": "^8.1.0",
- "he": "^1.2.0",
- "js-yaml": "^4.1.0",
- "log-symbols": "^4.1.0",
- "minimatch": "^5.1.6",
- "ms": "^2.1.3",
- "serialize-javascript": "^6.0.2",
- "strip-json-comments": "^3.1.1",
- "supports-color": "^8.1.1",
- "workerpool": "^6.5.1",
- "yargs": "^16.2.0",
- "yargs-parser": "^20.2.9",
- "yargs-unparser": "^2.0.0"
+ "esbuild": "~0.28.0"
},
"bin": {
- "_mocha": "bin/_mocha",
- "mocha": "bin/mocha.js"
+ "tsx": "dist/cli.mjs"
},
"engines": {
- "node": ">= 14.0.0"
+ "node": ">=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
}
},
- "node_modules/mocha/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "node_modules/tsx/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz",
+ "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==",
+ "cpu": [
+ "ppc64"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
"engines": {
- "node": ">=8"
+ "node": ">=18"
}
},
- "node_modules/mocha/node_modules/cliui": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
- "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "node_modules/tsx/node_modules/@esbuild/android-arm": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz",
+ "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
- "license": "ISC",
- "dependencies": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.0",
- "wrap-ansi": "^7.0.0"
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
}
},
- "node_modules/mocha/node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/mocha/node_modules/glob": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
- "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
- "deprecated": "Glob versions prior to v9 are no longer supported",
+ "node_modules/tsx/node_modules/@esbuild/android-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz",
+ "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
- "license": "ISC",
- "dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^5.0.1",
- "once": "^1.3.0"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
"engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "node": ">=18"
}
},
- "node_modules/mocha/node_modules/minimatch": {
- "version": "5.1.6",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
- "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "node_modules/tsx/node_modules/@esbuild/android-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz",
+ "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
"engines": {
- "node": ">=10"
+ "node": ">=18"
}
},
- "node_modules/mocha/node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "node_modules/tsx/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz",
+ "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
"engines": {
- "node": ">=8"
+ "node": ">=18"
}
},
- "node_modules/mocha/node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "node_modules/tsx/node_modules/@esbuild/darwin-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz",
+ "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
"engines": {
- "node": ">=8"
+ "node": ">=18"
}
},
- "node_modules/mocha/node_modules/supports-color": {
- "version": "8.1.1",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
- "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
"engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
+ "node": ">=18"
}
},
- "node_modules/mocha/node_modules/wrap-ansi": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "node_modules/tsx/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz",
+ "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
"engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ "node": ">=18"
}
},
- "node_modules/mocha/node_modules/yargs": {
- "version": "16.2.0",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
- "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "node_modules/tsx/node_modules/@esbuild/linux-arm": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz",
+ "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "cliui": "^7.0.2",
- "escalade": "^3.1.1",
- "get-caller-file": "^2.0.5",
- "require-directory": "^2.1.1",
- "string-width": "^4.2.0",
- "y18n": "^5.0.5",
- "yargs-parser": "^20.2.2"
- },
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=10"
+ "node": ">=18"
}
},
- "node_modules/mocha/node_modules/yargs-parser": {
- "version": "20.2.9",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
- "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
+ "node_modules/tsx/node_modules/@esbuild/linux-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz",
+ "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
- "license": "ISC",
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=10"
+ "node": ">=18"
}
},
- "node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/natural-compare": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
- "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/normalize-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
- "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "wrappy": "1"
- }
- },
- "node_modules/onetime": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
- "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "mimic-fn": "^2.1.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/optionator": {
- "version": "0.9.4",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
- "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "deep-is": "^0.1.3",
- "fast-levenshtein": "^2.0.6",
- "levn": "^0.4.1",
- "prelude-ls": "^1.2.1",
- "type-check": "^0.4.0",
- "word-wrap": "^1.2.5"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/ora": {
- "version": "7.0.1",
- "resolved": "https://registry.npmjs.org/ora/-/ora-7.0.1.tgz",
- "integrity": "sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "chalk": "^5.3.0",
- "cli-cursor": "^4.0.0",
- "cli-spinners": "^2.9.0",
- "is-interactive": "^2.0.0",
- "is-unicode-supported": "^1.3.0",
- "log-symbols": "^5.1.0",
- "stdin-discarder": "^0.1.0",
- "string-width": "^6.1.0",
- "strip-ansi": "^7.1.0"
- },
- "engines": {
- "node": ">=16"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/ora/node_modules/chalk": {
- "version": "5.4.1",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
- "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^12.17.0 || ^14.13 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/ora/node_modules/emoji-regex": {
- "version": "10.4.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
- "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/ora/node_modules/is-unicode-supported": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz",
- "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/ora/node_modules/log-symbols": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz",
- "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "chalk": "^5.0.0",
- "is-unicode-supported": "^1.1.0"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/ora/node_modules/string-width": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-6.1.0.tgz",
- "integrity": "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eastasianwidth": "^0.2.0",
- "emoji-regex": "^10.2.1",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=16"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-limit": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "yocto-queue": "^0.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-locate": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
- "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^3.0.2"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/package-json-from-dist": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
- "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
- "dev": true,
- "license": "BlueOak-1.0.0"
- },
- "node_modules/pako": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
- "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
- "dev": true,
- "license": "(MIT AND Zlib)"
- },
- "node_modules/parent-module": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
- "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "callsites": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/path-exists": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
- "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-is-absolute": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/path-key": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
- "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-scurry": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
- "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "lru-cache": "^10.2.0",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
- },
- "engines": {
- "node": ">=16 || 14 >=14.18"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/picomatch": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/prelude-ls": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
- "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/process-nextick-args": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
- "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/punycode": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
- "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/randombytes": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
- "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "^5.1.0"
- }
- },
- "node_modules/readable-stream": {
- "version": "2.3.8",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
- "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~2.0.0",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.1.1",
- "util-deprecate": "~1.0.1"
- }
- },
- "node_modules/readdirp": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
- "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "picomatch": "^2.2.1"
- },
- "engines": {
- "node": ">=8.10.0"
- }
- },
- "node_modules/require-directory": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
- "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/resolve-from": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
- "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/restore-cursor": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz",
- "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "onetime": "^5.1.0",
- "signal-exit": "^3.0.2"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/restore-cursor/node_modules/signal-exit": {
- "version": "3.0.7",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
- "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/semver": {
- "version": "7.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
- "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
- "dev": true,
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/serialize-javascript": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
- "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "randombytes": "^2.1.0"
- }
- },
- "node_modules/setimmediate": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
- "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/shebang-command": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
- "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "shebang-regex": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/shebang-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
- "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/signal-exit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
- "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/stdin-discarder": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz",
- "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "bl": "^5.0.0"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/string_decoder": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
- "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "~5.1.0"
- }
- },
- "node_modules/string-width": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
- "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "eastasianwidth": "^0.2.0",
- "emoji-regex": "^9.2.2",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/string-width-cjs": {
- "name": "string-width",
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/string-width-cjs/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/string-width-cjs/node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/string-width-cjs/node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-ansi": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
- "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "node_modules/tsx/node_modules/@esbuild/linux-ia32": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz",
+ "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==",
+ "cpu": [
+ "ia32"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "ansi-regex": "^6.0.1"
- },
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ "node": ">=18"
}
},
- "node_modules/strip-ansi-cjs": {
- "name": "strip-ansi",
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "node_modules/tsx/node_modules/@esbuild/linux-loong64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz",
+ "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==",
+ "cpu": [
+ "loong64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=8"
+ "node": ">=18"
}
},
- "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "node_modules/tsx/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz",
+ "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==",
+ "cpu": [
+ "mips64el"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=8"
+ "node": ">=18"
}
},
- "node_modules/strip-json-comments": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
- "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "node_modules/tsx/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz",
+ "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==",
+ "cpu": [
+ "ppc64"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "node": ">=18"
}
},
- "node_modules/supports-color": {
- "version": "9.4.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz",
- "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==",
+ "node_modules/tsx/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz",
+ "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==",
+ "cpu": [
+ "riscv64"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/supports-color?sponsor=1"
+ "node": ">=18"
}
},
- "node_modules/tapable": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
- "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
+ "node_modules/tsx/node_modules/@esbuild/linux-s390x": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz",
+ "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==",
+ "cpu": [
+ "s390x"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": ">=6"
- }
- },
- "node_modules/test-exclude": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
- "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "@istanbuljs/schema": "^0.1.2",
- "glob": "^7.1.4",
- "minimatch": "^3.0.4"
- },
- "engines": {
- "node": ">=8"
+ "node": ">=18"
}
},
- "node_modules/test-exclude/node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "node_modules/tsx/node_modules/@esbuild/linux-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz",
+ "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/test-exclude/node_modules/glob": {
- "version": "7.2.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
- "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
- "deprecated": "Glob versions prior to v9 are no longer supported",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.1.1",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- },
+ "optional": true,
+ "os": [
+ "linux"
+ ],
"engines": {
- "node": "*"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
+ "node": ">=18"
}
},
- "node_modules/test-exclude/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
"engines": {
- "node": "*"
+ "node": ">=18"
}
},
- "node_modules/to-regex-range": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
- "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "node_modules/tsx/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz",
+ "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "is-number": "^7.0.0"
- },
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
"engines": {
- "node": ">=8.0"
+ "node": ">=18"
}
},
- "node_modules/type-check": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
- "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "prelude-ls": "^1.2.1"
- },
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
"engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/undici-types": {
- "version": "6.19.8",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
- "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/uri-js": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
- "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "punycode": "^2.1.0"
+ "node": ">=18"
}
},
- "node_modules/util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/v8-to-istanbul": {
- "version": "9.3.0",
- "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
- "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
+ "node_modules/tsx/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz",
+ "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "license": "ISC",
- "dependencies": {
- "@jridgewell/trace-mapping": "^0.3.12",
- "@types/istanbul-lib-coverage": "^2.0.1",
- "convert-source-map": "^2.0.0"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
"engines": {
- "node": ">=10.12.0"
+ "node": ">=18"
}
},
- "node_modules/which": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
- "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz",
+ "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
- "license": "ISC",
- "dependencies": {
- "isexe": "^2.0.0"
- },
- "bin": {
- "node-which": "bin/node-which"
- },
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
"engines": {
- "node": ">= 8"
+ "node": ">=18"
}
},
- "node_modules/word-wrap": {
- "version": "1.2.5",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
- "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "node_modules/tsx/node_modules/@esbuild/sunos-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz",
+ "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
"engines": {
- "node": ">=0.10.0"
+ "node": ">=18"
}
},
- "node_modules/workerpool": {
- "version": "6.5.1",
- "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz",
- "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==",
- "dev": true,
- "license": "Apache-2.0"
- },
- "node_modules/wrap-ansi": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
- "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "node_modules/tsx/node_modules/@esbuild/win32-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz",
+ "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "ansi-styles": "^6.1.0",
- "string-width": "^5.0.1",
- "strip-ansi": "^7.0.1"
- },
+ "optional": true,
+ "os": [
+ "win32"
+ ],
"engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ "node": ">=18"
}
},
- "node_modules/wrap-ansi-cjs": {
- "name": "wrap-ansi",
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "node_modules/tsx/node_modules/@esbuild/win32-ia32": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz",
+ "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==",
+ "cpu": [
+ "ia32"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
+ "optional": true,
+ "os": [
+ "win32"
+ ],
"engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ "node": ">=18"
}
},
- "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "node_modules/tsx/node_modules/@esbuild/win32-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz",
+ "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
"license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
"engines": {
- "node": ">=8"
+ "node": ">=18"
}
},
- "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/wrap-ansi-cjs/node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "node_modules/tsx/node_modules/esbuild": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz",
+ "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==",
"dev": true,
+ "hasInstallScript": true,
"license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
+ "bin": {
+ "esbuild": "bin/esbuild"
},
"engines": {
- "node": ">=8"
- }
- },
- "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.28.2",
+ "@esbuild/android-arm": "0.28.2",
+ "@esbuild/android-arm64": "0.28.2",
+ "@esbuild/android-x64": "0.28.2",
+ "@esbuild/darwin-arm64": "0.28.2",
+ "@esbuild/darwin-x64": "0.28.2",
+ "@esbuild/freebsd-arm64": "0.28.2",
+ "@esbuild/freebsd-x64": "0.28.2",
+ "@esbuild/linux-arm": "0.28.2",
+ "@esbuild/linux-arm64": "0.28.2",
+ "@esbuild/linux-ia32": "0.28.2",
+ "@esbuild/linux-loong64": "0.28.2",
+ "@esbuild/linux-mips64el": "0.28.2",
+ "@esbuild/linux-ppc64": "0.28.2",
+ "@esbuild/linux-riscv64": "0.28.2",
+ "@esbuild/linux-s390x": "0.28.2",
+ "@esbuild/linux-x64": "0.28.2",
+ "@esbuild/netbsd-arm64": "0.28.2",
+ "@esbuild/netbsd-x64": "0.28.2",
+ "@esbuild/openbsd-arm64": "0.28.2",
+ "@esbuild/openbsd-x64": "0.28.2",
+ "@esbuild/openharmony-arm64": "0.28.2",
+ "@esbuild/sunos-x64": "0.28.2",
+ "@esbuild/win32-arm64": "0.28.2",
+ "@esbuild/win32-ia32": "0.28.2",
+ "@esbuild/win32-x64": "0.28.2"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz",
+ "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==",
"license": "MIT",
"dependencies": {
- "ansi-regex": "^5.0.1"
+ "content-type": "^2.0.0",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
},
"engines": {
- "node": ">=8"
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/wrap-ansi/node_modules/ansi-styles": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
- "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
- "dev": true,
+ "node_modules/type-is/node_modules/content-type": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz",
+ "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==",
"license": "MIT",
"engines": {
- "node": ">=12"
+ "node": ">=18"
},
"funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/y18n": {
- "version": "5.0.8",
- "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
- "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
- "dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=10"
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
}
},
- "node_modules/yargs": {
- "version": "17.7.2",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
- "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
- "license": "MIT",
- "dependencies": {
- "cliui": "^8.0.1",
- "escalade": "^3.1.1",
- "get-caller-file": "^2.0.5",
- "require-directory": "^2.1.1",
- "string-width": "^4.2.3",
- "y18n": "^5.0.5",
- "yargs-parser": "^21.1.1"
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
},
"engines": {
- "node": ">=12"
+ "node": ">=14.17"
}
},
- "node_modules/yargs-parser": {
- "version": "21.1.1",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
- "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "node_modules/undici-types": {
+ "version": "7.18.2",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
+ "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
"dev": true,
- "license": "ISC",
- "engines": {
- "node": ">=12"
- }
+ "license": "MIT"
},
- "node_modules/yargs-unparser": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
- "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
- "dev": true,
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"license": "MIT",
- "dependencies": {
- "camelcase": "^6.0.0",
- "decamelize": "^4.0.0",
- "flat": "^5.0.2",
- "is-plain-obj": "^2.1.0"
- },
"engines": {
- "node": ">=10"
+ "node": ">= 0.8"
}
},
- "node_modules/yargs/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"license": "MIT",
"engines": {
- "node": ">=8"
+ "node": ">= 0.8"
}
},
- "node_modules/yargs/node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/yargs/node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "license": "MIT",
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
"dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
+ "isexe": "^2.0.0"
},
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/yargs/node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
+ "bin": {
+ "node-which": "bin/node-which"
},
"engines": {
- "node": ">=8"
+ "node": ">= 8"
}
},
- "node_modules/yocto-queue": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
- "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
- "dev": true,
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
+ "node_modules/zod": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.5.4.tgz",
+ "integrity": "sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==",
"license": "MIT",
- "engines": {
- "node": ">=10"
- },
"funding": {
- "url": "https://github.com/sponsors/sindresorhus"
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-to-json-schema": {
+ "version": "3.25.2",
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz",
+ "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==",
+ "license": "ISC",
+ "peerDependencies": {
+ "zod": "^3.25.28 || ^4"
}
}
}
diff --git a/package.json b/package.json
index e8c29c3..6a2e70d 100644
--- a/package.json
+++ b/package.json
@@ -1,85 +1,41 @@
{
- "name": "metascraper",
- "displayName": "metascraper",
- "description": "Remove metadata from your images",
- "version": "0.0.2",
- "publisher": "NatalieHill",
- "publisherUrl": "https://github.com/natalie-a-1/",
- "icon": "./images/icon.png",
+ "name": "lucci-pay",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "description": "Payment requests and native checkout for ChatGPT",
+ "license": "MIT",
"engines": {
- "vscode": "^1.98.0"
- },
- "categories": [
- "Other",
- "Formatters",
- "Visualization"
- ],
- "keywords": [
- "metadata",
- "image",
- "exif",
- "privacy",
- "security",
- "cleanup",
- "images"
- ],
- "resources": {
- "github": {
- "account": "https://github.com/natalie-a-1",
- "repository": "https://github.com/natalie-a-1/Meta-Scraper"
- },
- "documentation": {
- "usage": "https://github.com/natalie-a-1/Meta-Scraper/blob/main/README.md",
- "testing": "https://github.com/natalie-a-1/Meta-Scraper/blob/main/TESTING.md",
- "publishing": "https://github.com/natalie-a-1/Meta-Scraper/blob/main/PUBLISHING.md"
- }
- },
- "repository": {
- "type": "git",
- "url": "https://github.com/natalie-a-1/Meta-Scraper.git"
- },
- "activationEvents": [],
- "main": "./extension.js",
- "contributes": {
- "commands": [
- {
- "command": "metascraper.cleanMetadata",
- "title": "Clean Image Metadata"
- },
- {
- "command": "metascraper.viewMetadata",
- "title": "View Image Metadata"
- }
- ],
- "menus": {
- "explorer/context": [
- {
- "command": "metascraper.cleanMetadata",
- "when": "resourceExtname =~ /\\.(jpg|jpeg|png|gif|tiff|webp)$/i",
- "group": "metascraper"
- },
- {
- "command": "metascraper.viewMetadata",
- "when": "resourceExtname =~ /\\.(jpg|jpeg|png|gif|tiff|webp)$/i",
- "group": "metascraper"
- }
- ]
- }
+ "node": ">=24.0.0"
},
"scripts": {
- "lint": "eslint .",
- "pretest": "npm run lint",
- "test": "vscode-test"
- },
- "devDependencies": {
- "@types/mocha": "^10.0.10",
- "@types/node": "20.x",
- "@types/vscode": "^1.98.0",
- "@vscode/test-cli": "^0.0.10",
- "@vscode/test-electron": "^2.4.1",
- "eslint": "^9.21.0"
+ "dev": "npm run build:widget && node --import tsx --env-file-if-exists=.env server/index.ts",
+ "build:widget": "node scripts/build-widget.mjs",
+ "build": "npm run typecheck && npm run build:widget && tsc -p tsconfig.server.json",
+ "start": "node --env-file-if-exists=.env dist/server/index.js",
+ "typecheck": "tsc --noEmit",
+ "test": "node --import tsx --test tests/*.test.ts",
+ "check": "npm run typecheck && npm test && npm run build",
+ "demo:request": "node --import tsx --env-file-if-exists=.env examples/request-payment.ts"
},
"dependencies": {
- "exiftool-vendored": "^29.2.0"
+ "@modelcontextprotocol/ext-apps": "1.7.5",
+ "@modelcontextprotocol/sdk": "1.30.0",
+ "express": "^5.1.0",
+ "express-rate-limit": "^8.2.1",
+ "jose": "^6.1.3",
+ "react": "19.2.8",
+ "react-dom": "19.2.8",
+ "zod": "^4.3.6"
+ },
+ "devDependencies": {
+ "@types/express": "^5.0.6",
+ "@types/node": "^24.0.0",
+ "@types/react": "^19.2.0",
+ "@types/react-dom": "^19.2.0",
+ "esbuild": "^0.27.0",
+ "prettier": "3.9.6",
+ "tsx": "^4.21.0",
+ "typescript": "^5.9.3"
}
}
diff --git a/scripts/build-preview-harness.mjs b/scripts/build-preview-harness.mjs
new file mode 100644
index 0000000..9191dbe
--- /dev/null
+++ b/scripts/build-preview-harness.mjs
@@ -0,0 +1,29 @@
+// Offline browser fixture for visual and interaction checks. The shipped widget
+// never includes this transport. Server/MCP integration is tested separately.
+import { readFile, writeFile } from 'node:fs/promises';
+import { loadConfig } from '../dist/server/config.js';
+import { Store } from '../dist/server/store.js';
+import { Payments } from '../dist/server/payments.js';
+import { DemoProcessor } from '../dist/server/processor.js';
+const store = new Store(':memory:');
+const service = new Payments(loadConfig({ PAYMENT_MODE: 'demo' }), store, new DemoProcessor());
+const request = service.demoRequest();
+const fixture = await service.get(request.id, 'demo-user');
+store.close();
+const html = await readFile('dist/widget/payment.html', 'utf8');
+const transport = ``;
+await writeFile('dist/preview-harness.html', html.replace('', `${transport}`));
+console.log('Built dist/preview-harness.html (offline demo fixture).');
diff --git a/scripts/build-widget.mjs b/scripts/build-widget.mjs
new file mode 100644
index 0000000..dff6597
--- /dev/null
+++ b/scripts/build-widget.mjs
@@ -0,0 +1,22 @@
+import { build } from 'esbuild';
+import { mkdir, writeFile } from 'node:fs/promises';
+const result = await build({
+ entryPoints: ['web/index.tsx'],
+ bundle: true,
+ write: false,
+ outdir: 'dist/widget',
+ minify: true,
+ format: 'iife',
+ target: 'es2022',
+ define: { 'process.env.NODE_ENV': '"production"' },
+});
+const js = result.outputFiles.find((f) => f.path.endsWith('.js')).text;
+const css = result.outputFiles.find((f) => f.path.endsWith('.css'))?.text ?? '';
+await mkdir('dist/widget', { recursive: true });
+await writeFile(
+ 'dist/widget/payment.html',
+ `
+Lucci Pay
+
`,
+);
+console.log('Built self-contained payment widget.');
diff --git a/server/app.ts b/server/app.ts
new file mode 100644
index 0000000..61acdf2
--- /dev/null
+++ b/server/app.ts
@@ -0,0 +1,156 @@
+import express from 'express';
+import { rateLimit } from 'express-rate-limit';
+import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
+import { ZodError, z } from 'zod';
+import { authenticateMerchant, type Config } from './config.js';
+import { createAuthenticator, type Authenticate } from './auth.js';
+import { createMcpServer } from './mcp.js';
+import type { Payments } from './payments.js';
+import { safeError } from './errors.js';
+
+export function createApp(
+ config: Config,
+ payments: Payments,
+ widget: string,
+ authenticate: Authenticate = createAuthenticator(config),
+) {
+ const app = express();
+ app.disable('x-powered-by');
+ const host = new URL(config.baseUrl).host;
+ app.use((req, res, next) => {
+ const allowedHosts = new Set([
+ host,
+ ...(config.mode === 'demo' ? [`localhost:${config.port}`, `127.0.0.1:${config.port}`] : []),
+ ]);
+ if (!allowedHosts.has(req.get('host') ?? '')) {
+ res.status(403).json({ error: { code: 'invalid_host' } });
+ return;
+ }
+ const origin = req.get('origin');
+ if (
+ origin &&
+ ![config.baseUrl, 'https://chatgpt.com', 'https://chat.openai.com'].includes(origin)
+ ) {
+ res.status(403).json({ error: { code: 'invalid_origin' } });
+ return;
+ }
+ res.set({
+ 'Cache-Control': 'no-store',
+ 'X-Content-Type-Options': 'nosniff',
+ 'Referrer-Policy': 'no-referrer',
+ });
+ next();
+ });
+ app.use(
+ rateLimit({ windowMs: 60_000, limit: 120, standardHeaders: 'draft-8', legacyHeaders: false }),
+ );
+ app.get('/healthz', (_req, res) => {
+ res.json({ ok: true, mode: config.mode, native_checkout_enabled: config.checkoutEnabled });
+ });
+ app.get(
+ ['/.well-known/oauth-protected-resource', '/.well-known/oauth-protected-resource/mcp'],
+ (_req, res) => {
+ if (config.mode === 'demo') {
+ res.status(404).end();
+ return;
+ }
+ res.json({
+ resource: config.audience,
+ authorization_servers: [config.issuer],
+ scopes_supported: ['payments:read', 'payments:pay'],
+ bearer_methods_supported: ['header'],
+ });
+ },
+ );
+ app.use(express.json({ limit: '48kb', strict: true }));
+ app.post('/v1/payment-requests', async (req, res) => {
+ const merchant = authenticateMerchant(req.get('authorization'), config);
+ const request = payments.create(merchant, req.body, req.get('idempotency-key') ?? '');
+ res.status(201).json({
+ request_id: request.id,
+ mode: request.mode,
+ status: request.status,
+ expires_at: request.expires_at,
+ handoff: { tool: 'show_payment_request', arguments: { request_id: request.id } },
+ });
+ });
+ app.get('/v1/payment-requests/:id', async (req, res) => {
+ const merchant = authenticateMerchant(req.get('authorization'), config);
+ res.json(await payments.merchantStatus(req.params.id, merchant));
+ });
+ app.all('/mcp', async (req, res) => {
+ let identity;
+ try {
+ identity = await authenticate(req.get('authorization'));
+ } catch (error) {
+ const e = safeError(error);
+ res.set(
+ 'WWW-Authenticate',
+ `Bearer resource_metadata="${config.baseUrl}/.well-known/oauth-protected-resource", scope="payments:read payments:pay"`,
+ );
+ res.status(e.status).json({ error: { code: e.code, message: e.message } });
+ return;
+ }
+ const server = createMcpServer(payments, identity, widget);
+ const transport = new StreamableHTTPServerTransport({
+ sessionIdGenerator: undefined,
+ enableJsonResponse: true,
+ });
+ res.on('close', () => {
+ void transport.close().catch(() => {});
+ void server.close().catch(() => {});
+ });
+ await server.connect(transport);
+ await transport.handleRequest(req, res, req.body);
+ });
+ if (config.mode === 'demo') {
+ app.get(['/', '/preview'], (_req, res) => {
+ res.set(
+ 'Content-Security-Policy',
+ "default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; frame-ancestors 'self'; base-uri 'none'; form-action 'none'",
+ );
+ res.type('html').send(widget);
+ });
+ app.post('/api/demo', async (_req, res) => {
+ res.json({ payment: await payments.get(payments.demoRequest().id, 'demo-user') });
+ });
+ app.get('/api/demo/:id', async (req, res) => {
+ res.json({ payment: await payments.get(req.params.id, 'demo-user') });
+ });
+ app.post('/api/demo/:id/simulate', async (req, res) => {
+ const { outcome } = z
+ .object({ outcome: z.enum(['success', 'declined']) })
+ .strict()
+ .parse(req.body);
+ res.json({ payment: await payments.simulate(req.params.id, 'demo-user', outcome) });
+ });
+ app.post('/api/demo/:id/cancel', async (req, res) => {
+ res.json({ payment: await payments.cancel(req.params.id, 'demo-user') });
+ });
+ }
+ app.use(
+ (error: unknown, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
+ if (res.headersSent) return;
+ const e = safeError(error);
+ const malformed = error instanceof SyntaxError || error instanceof ZodError;
+ if (
+ error &&
+ typeof error === 'object' &&
+ 'type' in error &&
+ error.type === 'entity.too.large'
+ ) {
+ res
+ .status(413)
+ .json({ error: { code: 'request_too_large', message: 'The request is too large.' } });
+ return;
+ }
+ res.status(malformed ? 400 : e.status).json({
+ error: {
+ code: malformed ? 'invalid_request' : e.code,
+ message: malformed ? 'Check the request fields and try again.' : e.message,
+ },
+ });
+ },
+ );
+ return app;
+}
diff --git a/server/auth.ts b/server/auth.ts
new file mode 100644
index 0000000..504544e
--- /dev/null
+++ b/server/auth.ts
@@ -0,0 +1,41 @@
+import { createRemoteJWKSet, jwtVerify } from 'jose';
+import type { Config } from './config.js';
+import { PaymentError } from './errors.js';
+
+export interface Identity {
+ subject: string;
+ scopes: Set;
+}
+export type Authenticate = (header?: string) => Promise;
+export function createAuthenticator(config: Config): Authenticate {
+ if (config.mode === 'demo')
+ return async () => ({
+ subject: 'demo-user',
+ scopes: new Set(['payments:read', 'payments:pay']),
+ });
+ const jwks = createRemoteJWKSet(new URL(config.jwksUrl!));
+ return async (header) => {
+ if (!header?.startsWith('Bearer '))
+ throw new PaymentError('unauthorized', 'Connect your Lucci Pay account to continue.', 401);
+ try {
+ const { payload } = await jwtVerify(header.slice(7), jwks, {
+ issuer: config.issuer,
+ audience: config.audience,
+ algorithms: ['RS256', 'ES256'],
+ requiredClaims: ['sub', 'exp', 'iat'],
+ });
+ if (!payload.sub || typeof payload.scope !== 'string') throw new Error('Invalid claims');
+ return { subject: payload.sub, scopes: new Set(payload.scope.split(' ')) };
+ } catch {
+ throw new PaymentError('unauthorized', 'Reconnect your Lucci Pay account to continue.', 401);
+ }
+ };
+}
+export function requireScope(identity: Identity, scope: string) {
+ if (!identity.scopes.has(scope))
+ throw new PaymentError(
+ 'forbidden',
+ 'Your account connection does not have permission for this action.',
+ 403,
+ );
+}
diff --git a/server/config.ts b/server/config.ts
new file mode 100644
index 0000000..4d71b61
--- /dev/null
+++ b/server/config.ts
@@ -0,0 +1,128 @@
+import { readFileSync } from 'node:fs';
+import { createHash, timingSafeEqual } from 'node:crypto';
+import { z } from 'zod';
+import type { PaymentMode } from '../shared/contracts.js';
+import { PaymentError } from './errors.js';
+
+const https = z
+ .string()
+ .url()
+ .refine((v) => new URL(v).protocol === 'https:', 'HTTPS required');
+export const merchantSchema = z
+ .object({
+ id: z.string().regex(/^[a-z0-9_-]{1,64}$/),
+ name: z.string().min(1).max(80),
+ domain: z.string().regex(/^[a-z0-9.-]+$/),
+ api_key_sha256: z.string().regex(/^[a-f0-9]{64}$/),
+ provider_merchant_id: z.string().min(1).max(160),
+ stripe_account: z
+ .string()
+ .regex(/^acct_[A-Za-z0-9]+$/)
+ .optional(),
+ category: z.literal('physical_goods'),
+ terms_url: https,
+ privacy_url: https,
+ refund_url: https,
+ support_url: https,
+ })
+ .strict();
+export type Merchant = z.infer;
+export interface Config {
+ mode: PaymentMode;
+ port: number;
+ baseUrl: string;
+ databasePath: string;
+ checkoutEnabled: boolean;
+ merchants: Merchant[];
+ stripeKey?: string;
+ issuer?: string;
+ jwksUrl?: string;
+ audience?: string;
+}
+export const hash = (value: string) => createHash('sha256').update(value).digest('hex');
+export const demoMerchant: Merchant = {
+ id: 'studio-demo',
+ name: 'Studio Supply',
+ domain: 'studio.example',
+ api_key_sha256: hash('demo-merchant-key'),
+ provider_merchant_id: 'demo-only',
+ category: 'physical_goods',
+ terms_url: 'https://studio.example/terms',
+ privacy_url: 'https://studio.example/privacy',
+ refund_url: 'https://studio.example/refunds',
+ support_url: 'https://studio.example/support',
+};
+
+export function loadConfig(env: NodeJS.ProcessEnv = process.env): Config {
+ const mode = z.enum(['demo', 'test', 'live']).parse(env.PAYMENT_MODE ?? 'demo');
+ const baseUrl = z
+ .string()
+ .url()
+ .parse(env.PUBLIC_BASE_URL ?? 'http://localhost:4242')
+ .replace(/\/$/, '');
+ const config: Config = {
+ mode,
+ baseUrl,
+ port: z.coerce
+ .number()
+ .int()
+ .min(1)
+ .max(65535)
+ .parse(env.PORT ?? 4242),
+ databasePath: env.DATABASE_PATH ?? './data/lucci-pay.sqlite',
+ checkoutEnabled: mode !== 'demo' && env.CHATGPT_CHECKOUT_ENABLED === 'true',
+ merchants:
+ mode === 'demo'
+ ? [demoMerchant]
+ : z
+ .array(merchantSchema)
+ .min(1)
+ .parse(
+ JSON.parse(readFileSync(env.MERCHANTS_FILE ?? './config/merchants.json', 'utf8')),
+ ),
+ };
+ if (
+ new Set(config.merchants.map((m) => m.id)).size !== config.merchants.length ||
+ new Set(config.merchants.map((m) => m.api_key_sha256)).size !== config.merchants.length
+ )
+ throw new Error('Merchant IDs and API keys must be unique');
+ if (mode !== 'demo') {
+ https.parse(baseUrl);
+ config.issuer = https.parse(env.AUTH_ISSUER);
+ config.jwksUrl = https.parse(env.AUTH_JWKS_URL);
+ config.audience = https.parse(env.AUTH_AUDIENCE);
+ if (config.audience !== `${baseUrl}/mcp`)
+ throw new Error('AUTH_AUDIENCE must equal PUBLIC_BASE_URL/mcp');
+ const prefix = mode === 'test' ? 'sk_test_' : 'sk_live_';
+ if (!env.STRIPE_SECRET_KEY?.startsWith(prefix))
+ throw new Error(`A ${mode} Stripe key is required`);
+ config.stripeKey = env.STRIPE_SECRET_KEY;
+ if (mode === 'live' && (!config.checkoutEnabled || env.LIVE_PAYMENTS_APPROVED !== 'true')) {
+ throw new Error(
+ 'Live mode requires approved native checkout access and LIVE_PAYMENTS_APPROVED=true',
+ );
+ }
+ if (
+ config.merchants.some(
+ (m) =>
+ m.provider_merchant_id === 'demo-only' ||
+ m.domain.endsWith('.example') ||
+ m.api_key_sha256 === demoMerchant.api_key_sha256,
+ )
+ )
+ throw new Error('Replace all demo merchant settings before native checkout');
+ }
+ return config;
+}
+
+export function authenticateMerchant(header: string | undefined, config: Config): Merchant {
+ const token = header?.startsWith('Bearer ') ? header.slice(7) : '';
+ if (!token || (config.mode !== 'demo' && token.length < 32))
+ throw new PaymentError('unauthorized', 'Invalid merchant credentials.', 401);
+ const digest = Buffer.from(hash(token));
+ const merchant = config.merchants.find((m) =>
+ timingSafeEqual(digest, Buffer.from(m.api_key_sha256)),
+ );
+ if (!merchant) throw new PaymentError('unauthorized', 'Invalid merchant credentials.', 401);
+ return merchant;
+}
diff --git a/server/errors.ts b/server/errors.ts
new file mode 100644
index 0000000..f4c99e6
--- /dev/null
+++ b/server/errors.ts
@@ -0,0 +1,18 @@
+export class PaymentError extends Error {
+ constructor(
+ public code: string,
+ message: string,
+ public status = 400,
+ ) {
+ super(message);
+ }
+}
+export function safeError(error: unknown): PaymentError {
+ return error instanceof PaymentError
+ ? error
+ : new PaymentError(
+ 'internal_error',
+ 'We could not complete this request. Please try again.',
+ 500,
+ );
+}
diff --git a/server/index.ts b/server/index.ts
new file mode 100644
index 0000000..0eac81d
--- /dev/null
+++ b/server/index.ts
@@ -0,0 +1,28 @@
+import { readFileSync } from 'node:fs';
+import { resolve } from 'node:path';
+import { loadConfig } from './config.js';
+import { Store } from './store.js';
+import { DemoProcessor, StripeProcessor } from './processor.js';
+import { Payments } from './payments.js';
+import { createApp } from './app.js';
+
+const config = loadConfig();
+const store = new Store(config.databasePath);
+const payments = new Payments(
+ config,
+ store,
+ config.mode === 'demo' ? new DemoProcessor() : new StripeProcessor(config),
+);
+const widget = readFileSync(resolve('dist/widget/payment.html'), 'utf8');
+const app = createApp(config, payments, widget);
+const listener = app.listen(config.port, config.mode === 'demo' ? '127.0.0.1' : '0.0.0.0', () => {
+ console.log(`Lucci Pay Ā· ${config.mode} Ā· ${config.baseUrl}/mcp`);
+ if (config.mode === 'demo') console.log(`Preview: ${config.baseUrl}/preview Ā· No money moves.`);
+});
+for (const signal of ['SIGTERM', 'SIGINT'] as const)
+ process.once(signal, () => {
+ listener.close(() => {
+ store.close();
+ process.exit(0);
+ });
+ });
diff --git a/server/mcp.ts b/server/mcp.ts
new file mode 100644
index 0000000..2b15964
--- /dev/null
+++ b/server/mcp.ts
@@ -0,0 +1,267 @@
+// Wiring adapted from openai/openai-apps-sdk-examples, MCP App Basics
+// commit 18cc38e78a968712c357bacdc3c79fead5bfc6b4. See THIRD_PARTY_NOTICES.md.
+import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import {
+ registerAppResource,
+ registerAppTool,
+ RESOURCE_MIME_TYPE,
+} from '@modelcontextprotocol/ext-apps/server';
+import { z } from 'zod';
+import { paymentDataSchema, buyerSchema, type PaymentView } from '../shared/contracts.js';
+import type { Payments } from './payments.js';
+import { requireScope, type Identity } from './auth.js';
+import { safeError } from './errors.js';
+
+export const WIDGET_URI = 'ui://lucci-pay/payment-v1.html';
+const requestId = z.string().regex(/^pay_[a-f0-9]{32}$/);
+const paymentOutput = {
+ payment: z
+ .object({
+ request_id: requestId,
+ state_version: z.number().int().nonnegative(),
+ status: z.string(),
+ mode: z.enum(['demo', 'test', 'live']),
+ session: z.record(z.string(), z.unknown()),
+ })
+ .passthrough(),
+};
+const read = {
+ readOnlyHint: true,
+ destructiveHint: false,
+ openWorldHint: false,
+ idempotentHint: true,
+};
+const mutate = {
+ readOnlyHint: false,
+ destructiveHint: false,
+ openWorldHint: false,
+ idempotentHint: true,
+};
+
+export function paymentResult(view: PaymentView) {
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text:
+ view.mode === 'demo'
+ ? `Demo payment request: ${view.status}. No money moves.`
+ : `Payment request from ${view.merchant.name}: ${view.status}.`,
+ },
+ ],
+ structuredContent: { payment: view },
+ };
+}
+export function createMcpServer(payments: Payments, identity: Identity, widgetHtml: string) {
+ const server = new McpServer(
+ { name: 'lucci-pay', version: '0.1.0' },
+ {
+ instructions:
+ 'Show a payment request only when the user asks to review or pay it. Creating or displaying a request never pays it. The user must confirm payment in the payment sheet. Read authoritative payment status before describing a purchase as paid. Never ask for payment credentials in chat.',
+ },
+ );
+ const security = (scope: string) =>
+ payments.config.mode === 'demo' ? [{ type: 'noauth' }] : [{ type: 'oauth2', scopes: [scope] }];
+ const meta = (scope: string, render = false, privateTool = false) => ({
+ ui: {
+ ...(render ? { resourceUri: WIDGET_URI } : {}),
+ visibility: privateTool ? ['app'] : ['model', 'app'],
+ },
+ securitySchemes: security(scope),
+ ...(privateTool ? { 'openai/visibility': 'private', 'openai/widgetAccessible': true } : {}),
+ ...(render
+ ? {
+ 'openai/outputTemplate': WIDGET_URI,
+ 'openai/toolInvocation/invoking': 'Opening payment request',
+ 'openai/toolInvocation/invoked': 'Payment request ready',
+ }
+ : {}),
+ });
+ const guard = async (scope: string, action: () => Promise) => {
+ try {
+ requireScope(identity, scope);
+ return await action();
+ } catch (error) {
+ const e = safeError(error);
+ return {
+ isError: true,
+ content: [{ type: 'text' as const, text: e.message }],
+ structuredContent: { error: { code: e.code, message: e.message } },
+ };
+ }
+ };
+ registerAppResource(
+ server,
+ 'Lucci Pay payment sheet',
+ WIDGET_URI,
+ { mimeType: RESOURCE_MIME_TYPE },
+ async () => ({
+ contents: [
+ {
+ uri: WIDGET_URI,
+ mimeType: RESOURCE_MIME_TYPE,
+ text: widgetHtml,
+ _meta: {
+ ui: {
+ prefersBorder: false,
+ csp: { connectDomains: [], resourceDomains: [] },
+ ...(payments.config.mode !== 'demo' ? { domain: payments.config.baseUrl } : {}),
+ },
+ 'openai/widgetDescription':
+ 'A payment request showing the seller, items and exact total. Payment opens the native ChatGPT payment sheet after a user click.',
+ },
+ },
+ ],
+ }),
+ );
+ registerAppTool(
+ server,
+ 'show_payment_request',
+ {
+ title: 'Review payment request',
+ description:
+ 'Use this when the user wants to review or pay an existing Lucci Pay request created by a participating app. Requires the request ID returned by that app. Does not charge anyone.',
+ inputSchema: { request_id: requestId },
+ outputSchema: paymentOutput,
+ annotations: read,
+ _meta: meta('payments:read', true),
+ },
+ ({ request_id }) =>
+ guard('payments:read', async () =>
+ paymentResult(await payments.get(request_id, identity.subject)),
+ ),
+ );
+ registerAppTool(
+ server,
+ 'get_payment_status',
+ {
+ title: 'Check payment status',
+ description:
+ 'Use this when the user wants the verified status or receipt for a Lucci Pay request. A processing result is not a completed purchase.',
+ inputSchema: { request_id: requestId },
+ outputSchema: paymentOutput,
+ annotations: read,
+ _meta: meta('payments:read'),
+ },
+ ({ request_id }) =>
+ guard('payments:read', async () =>
+ paymentResult(await payments.get(request_id, identity.subject)),
+ ),
+ );
+ registerAppTool(
+ server,
+ 'cancel_payment_request',
+ {
+ title: 'Cancel payment request',
+ description:
+ 'Use this when the user presses Cancel on an unpaid request. Cannot cancel a payment in progress or issue refunds.',
+ inputSchema: { request_id: requestId },
+ outputSchema: paymentOutput,
+ annotations: mutate,
+ _meta: meta('payments:pay', false, true),
+ },
+ ({ request_id }) =>
+ guard('payments:pay', async () =>
+ paymentResult(await payments.cancel(request_id, identity.subject)),
+ ),
+ );
+ if (payments.config.mode !== 'demo') {
+ registerAppTool(
+ server,
+ 'complete_checkout',
+ {
+ title: 'Complete checkout',
+ description:
+ 'Finalize a checkout from the native ChatGPT payment sheet after the user confirms. Accepts a scoped Stripe shared payment token; never accepts raw card credentials. Reserved for the checkout host.',
+ inputSchema: {
+ checkout_session_id: requestId,
+ buyer: buyerSchema.optional(),
+ payment_data: paymentDataSchema,
+ },
+ outputSchema: z
+ .object({
+ id: requestId,
+ status: z.enum(['completed', 'not_ready_for_payment']),
+ currency: z.string(),
+ line_items: z.array(z.record(z.string(), z.unknown())),
+ totals: z.array(z.record(z.string(), z.unknown())),
+ order: z
+ .object({ id: z.string(), checkout_session_id: requestId, permalink_url: z.string() })
+ .optional(),
+ })
+ .passthrough(),
+ annotations: { ...mutate, destructiveHint: true, openWorldHint: true },
+ _meta: meta('payments:pay', false, true),
+ },
+ ({ checkout_session_id, payment_data }) =>
+ guard('payments:pay', async () => {
+ try {
+ const view = await payments.complete(
+ checkout_session_id,
+ identity.subject,
+ payment_data.token,
+ );
+ return {
+ content: [],
+ structuredContent: {
+ ...view.session,
+ status: view.status === 'paid' ? 'completed' : 'not_ready_for_payment',
+ order: view.receipt
+ ? { id: view.receipt.id, checkout_session_id, permalink_url: '' }
+ : undefined,
+ },
+ };
+ } catch (error) {
+ const e = safeError(error);
+ return {
+ isError: true,
+ content: [{ type: 'text' as const, text: e.message }],
+ structuredContent: {
+ id: checkout_session_id,
+ status: 'not_ready_for_payment',
+ messages: [
+ { type: 'error', code: e.code, content_type: 'plain', content: e.message },
+ ],
+ },
+ };
+ }
+ }),
+ );
+ } else {
+ registerAppTool(
+ server,
+ 'create_demo_payment_request',
+ {
+ title: 'Preview Lucci Pay',
+ description:
+ 'Use this when the user explicitly wants to try the Lucci Pay demo. Creates a fictional payment request. No money moves.',
+ inputSchema: {},
+ outputSchema: paymentOutput,
+ annotations: { ...mutate, idempotentHint: false },
+ _meta: meta('payments:read', true),
+ },
+ () =>
+ guard('payments:read', async () =>
+ paymentResult(await payments.get(payments.demoRequest().id, identity.subject)),
+ ),
+ );
+ registerAppTool(
+ server,
+ 'simulate_payment',
+ {
+ title: 'Simulate demo payment',
+ description:
+ 'Use this only for the demo payment button. Records a simulated outcome; cannot charge money.',
+ inputSchema: { request_id: requestId, outcome: z.enum(['success', 'declined']) },
+ outputSchema: paymentOutput,
+ annotations: mutate,
+ _meta: meta('payments:pay', false, true),
+ },
+ ({ request_id, outcome }) =>
+ guard('payments:pay', async () =>
+ paymentResult(await payments.simulate(request_id, identity.subject, outcome)),
+ ),
+ );
+ }
+ return server;
+}
diff --git a/server/payments.ts b/server/payments.ts
new file mode 100644
index 0000000..db8d8be
--- /dev/null
+++ b/server/payments.ts
@@ -0,0 +1,343 @@
+import { randomUUID } from 'node:crypto';
+import {
+ requestInput,
+ paymentDataSchema,
+ totalAmount,
+ type PaymentView,
+ type RequestInput,
+} from '../shared/contracts.js';
+import { hash, type Config, type Merchant } from './config.js';
+import { PaymentError } from './errors.js';
+import type { ChargeResult, Processor } from './processor.js';
+import { Store, type StoredRequest } from './store.js';
+
+export class Payments {
+ constructor(
+ readonly config: Config,
+ readonly store: Store,
+ private processor: Processor,
+ ) {}
+
+ create(merchant: Merchant, raw: unknown, key: string): StoredRequest {
+ if (!/^[A-Za-z0-9_.:-]{8,128}$/.test(key))
+ throw new PaymentError(
+ 'invalid_idempotency_key',
+ 'Supply an Idempotency-Key of 8ā128 characters.',
+ );
+ const input = requestInput.parse(raw);
+ if (this.config.mode === 'demo' && input.payer_subject !== 'demo-user')
+ throw new PaymentError('demo_only', 'Demo requests must use the demo user.');
+ if (new Set(input.line_items.map((i) => i.id)).size !== input.line_items.length)
+ throw new PaymentError('duplicate_item', 'Line item IDs must be unique.');
+ const lineItems = input.line_items.map((item) => {
+ const base = item.unit_amount * item.quantity;
+ if (item.discount > base)
+ throw new PaymentError('invalid_discount', 'A discount cannot exceed the line amount.');
+ return {
+ id: item.id,
+ item: { id: item.id, quantity: item.quantity },
+ name: item.name,
+ base_amount: base,
+ discount: item.discount,
+ subtotal: base - item.discount,
+ tax: item.tax,
+ total: base - item.discount + item.tax,
+ };
+ });
+ const sum = (key: 'base_amount' | 'subtotal' | 'tax' | 'total') =>
+ lineItems.reduce((n, i) => n + i[key], 0);
+ if (sum('total') < 50 || sum('total') > 10_000_000)
+ throw new PaymentError(
+ 'invalid_amount',
+ 'The total must be between 50 and 10,000,000 minor currency units.',
+ );
+ const id = `pay_${randomUUID().replaceAll('-', '')}`;
+ const now = Date.now();
+ return this.store.insert({
+ state_version: 0,
+ id,
+ merchant_id: merchant.id,
+ payer_subject: input.payer_subject,
+ idempotency_key: key,
+ fingerprint: hash(
+ JSON.stringify({
+ input,
+ mode: this.config.mode,
+ provider: merchant.provider_merchant_id,
+ account: merchant.stripe_account,
+ }),
+ ),
+ processor_account: merchant.stripe_account,
+ mode: this.config.mode,
+ status: 'pending',
+ attempt: 0,
+ created_at: new Date(now).toISOString(),
+ expires_at: new Date(now + input.expires_in_seconds * 1000).toISOString(),
+ session: {
+ id,
+ payment_provider: {
+ provider: 'stripe',
+ merchant_id: merchant.provider_merchant_id,
+ supported_payment_methods: [
+ { type: 'apple_pay' },
+ { type: 'card', allowed_card_brands: ['visa', 'mastercard', 'amex'] },
+ ],
+ },
+ payment_mode: this.config.mode === 'live' ? 'live' : 'test',
+ status: 'ready_for_payment',
+ currency: input.currency,
+ metadata: { merchant_order_reference: input.merchant_order_reference },
+ line_items: lineItems,
+ totals: [
+ { type: 'items_base_amount', display_text: 'Items', amount: sum('base_amount') },
+ { type: 'subtotal', display_text: 'Subtotal', amount: sum('subtotal') },
+ { type: 'tax', display_text: 'Tax', amount: sum('tax') },
+ { type: 'total', display_text: 'Total', amount: sum('total') },
+ ],
+ fulfillment_options: [
+ { id: 'pickup', ...input.fulfillment, subtotal: 0, tax: 0, total: 0 },
+ ],
+ fulfillment_option_id: 'pickup',
+ links: [
+ { type: 'terms_of_use', url: merchant.terms_url },
+ { type: 'privacy_policy', url: merchant.privacy_url },
+ { type: 'refund_policy', url: merchant.refund_url },
+ { type: 'support_url', url: merchant.support_url },
+ ],
+ },
+ });
+ }
+
+ private merchant(request: StoredRequest): Merchant {
+ const merchant = this.config.merchants.find((m) => m.id === request.merchant_id);
+ if (!merchant)
+ throw new PaymentError('merchant_unavailable', 'This merchant is unavailable.', 409);
+ if (request.mode !== this.config.mode)
+ throw new PaymentError(
+ 'mode_mismatch',
+ 'This request belongs to a different payment environment.',
+ 409,
+ );
+ if (
+ request.session.payment_provider.merchant_id !== merchant.provider_merchant_id ||
+ request.processor_account !== merchant.stripe_account
+ )
+ throw new PaymentError(
+ 'merchant_changed',
+ 'This merchantās payment setup changed. Request a new checkout.',
+ 409,
+ );
+ return merchant;
+ }
+ private own(id: string, subject: string): StoredRequest {
+ const request = this.store.get(id);
+ if (request.payer_subject !== subject)
+ throw new PaymentError('not_found', 'Payment request not found.', 404);
+ this.merchant(request);
+ return request;
+ }
+ private expire(id: string): StoredRequest {
+ return this.store.update(id, (request) => {
+ if (
+ ['pending', 'failed'].includes(request.status) &&
+ Date.parse(request.expires_at) <= Date.now()
+ )
+ request.status = 'expired';
+ });
+ }
+ private view(request: StoredRequest): PaymentView {
+ const m = this.merchant(request);
+ return {
+ state_version: request.state_version,
+ request_id: request.id,
+ merchant: { name: m.name, domain: m.domain },
+ mode: request.mode,
+ status: request.status,
+ expires_at: request.expires_at,
+ checkout_enabled: this.config.checkoutEnabled,
+ session: request.session,
+ receipt: request.receipt,
+ };
+ }
+ async get(id: string, subject: string): Promise {
+ let request = this.own(id, subject);
+ request = this.expire(id);
+ if (request.status === 'processing' && request.provider_id) {
+ try {
+ request = this.record(
+ request,
+ await this.processor.retrieve(request, this.merchant(request)),
+ );
+ } catch {
+ /* Retain processing until the provider's result is authoritative. */
+ }
+ }
+ return this.view(request);
+ }
+ async merchantStatus(id: string, merchant: Merchant) {
+ const request = this.store.get(id);
+ if (request.merchant_id !== merchant.id)
+ throw new PaymentError('not_found', 'Payment request not found.', 404);
+ const view = await this.get(id, request.payer_subject);
+ return {
+ request_id: id,
+ merchant_order_reference: request.session.metadata.merchant_order_reference,
+ status: view.status,
+ mode: view.mode,
+ receipt: view.receipt,
+ };
+ }
+
+ private record(request: StoredRequest, result: ChargeResult): StoredRequest {
+ return this.store.update(request.id, (current) => {
+ if (current.status === 'paid' || current.attempt !== request.attempt) return;
+ current.provider_id = result.id;
+ if (result.status === 'succeeded') {
+ current.status = 'paid';
+ current.session.status = 'completed';
+ current.receipt = {
+ id: `receipt_${current.id.slice(4)}`,
+ checkout_session_id: current.id,
+ amount: totalAmount(current.session),
+ currency: current.session.currency,
+ paid_at: new Date().toISOString(),
+ mode: current.mode,
+ merchant_name: this.merchant(current).name,
+ };
+ } else current.status = result.status === 'failed' ? 'failed' : 'processing';
+ });
+ }
+
+ async complete(id: string, subject: string, token: string): Promise {
+ paymentDataSchema.parse({ token });
+ this.own(id, subject);
+ if (this.config.mode === 'demo' || !this.config.checkoutEnabled)
+ throw new PaymentError(
+ 'checkout_unavailable',
+ 'Native checkout is not enabled for this app.',
+ 403,
+ );
+ this.expire(id);
+ let fresh = false;
+ const tokenHash = hash(token);
+ const request = this.store.update(id, (current) => {
+ if (current.status === 'paid') return;
+ if (['cancelled', 'expired'].includes(current.status))
+ throw new PaymentError('request_closed', 'This payment request is closed.', 409);
+ if (current.status === 'processing') {
+ if (current.token_hash !== tokenHash)
+ throw new PaymentError(
+ 'payment_pending',
+ 'The previous payment is still being checked.',
+ 409,
+ );
+ // Stripe keys are retained at least 24 hours. Never replay after that window.
+ if (
+ Date.now() - Date.parse(current.attempt_started_at!) > 23 * 3600_000 &&
+ !current.provider_id
+ )
+ throw new PaymentError(
+ 'reconciliation_required',
+ 'Contact support to reconcile this payment.',
+ 409,
+ );
+ return;
+ }
+ if (current.token_hash === tokenHash)
+ throw new PaymentError('payment_declined', 'Choose a new payment method to retry.', 402);
+ fresh = true;
+ current.status = 'processing';
+ current.attempt += 1;
+ current.token_hash = tokenHash;
+ current.attempt_started_at = new Date().toISOString();
+ delete current.provider_id;
+ });
+ if (request.status === 'paid') return this.view(request);
+ try {
+ const result = await this.processor.charge(request, this.merchant(request), token, fresh);
+ const updated = this.record(request, result);
+ if (updated.status !== 'paid' && result.errorCode)
+ throw new PaymentError(
+ result.errorCode,
+ result.errorCode === 'requires_3ds'
+ ? 'Please authenticate this payment in the payment sheet.'
+ : result.errorCode === 'payment_declined'
+ ? 'Payment declined. Choose another method.'
+ : 'Your payment is being confirmed.',
+ 402,
+ );
+ return this.view(updated);
+ } catch (error) {
+ if (
+ error instanceof PaymentError &&
+ ['payment_declined', 'invalid_payment_token'].includes(error.code)
+ ) {
+ this.store.update(id, (current) => {
+ if (current.status === 'processing' && current.attempt === request.attempt)
+ current.status = 'failed';
+ });
+ }
+ throw error;
+ }
+ }
+ async cancel(id: string, subject: string): Promise {
+ this.own(id, subject);
+ const request = this.store.update(id, (current) => {
+ if (current.status === 'processing' || current.status === 'paid')
+ throw new PaymentError(
+ 'cannot_cancel',
+ 'A payment in progress or already paid cannot be cancelled here.',
+ 409,
+ );
+ if (current.status !== 'expired') current.status = 'cancelled';
+ });
+ return this.view(request);
+ }
+ async simulate(
+ id: string,
+ subject: string,
+ outcome: 'success' | 'declined',
+ ): Promise {
+ this.own(id, subject);
+ if (this.config.mode !== 'demo')
+ throw new PaymentError('demo_disabled', 'Simulation is disabled.', 403);
+ this.expire(id);
+ const request = this.store.update(id, (current) => {
+ if (current.status === 'paid') return;
+ if (['cancelled', 'expired'].includes(current.status))
+ throw new PaymentError('request_closed', 'This demo request is closed.', 409);
+ current.status = outcome === 'success' ? 'processing' : 'failed';
+ });
+ return this.view(
+ outcome === 'success'
+ ? this.record(request, { id: `demo_${id}`, status: 'succeeded' })
+ : request,
+ );
+ }
+ demoRequest(): StoredRequest {
+ const input: RequestInput = {
+ merchant_order_reference: `demo-${randomUUID()}`,
+ payer_subject: 'demo-user',
+ currency: 'USD',
+ line_items: [
+ {
+ id: 'ceramic-cup',
+ name: 'Everyday ceramic cup',
+ quantity: 1,
+ unit_amount: 2400,
+ tax: 198,
+ discount: 0,
+ },
+ ],
+ fulfillment: {
+ type: 'pickup',
+ title: 'Pick up in store',
+ subtitle: 'Studio Supply Ā· Ready in 2 hours',
+ },
+ expires_in_seconds: 900,
+ };
+ if (this.config.mode !== 'demo')
+ throw new PaymentError('demo_disabled', 'Simulation is disabled.', 403);
+ return this.create(this.config.merchants[0]!, input, input.merchant_order_reference);
+ }
+}
diff --git a/server/processor.ts b/server/processor.ts
new file mode 100644
index 0000000..30d67f2
--- /dev/null
+++ b/server/processor.ts
@@ -0,0 +1,186 @@
+import { PaymentError } from './errors.js';
+import type { Config, Merchant } from './config.js';
+import type { StoredRequest } from './store.js';
+import { totalAmount } from '../shared/contracts.js';
+import { z } from 'zod';
+
+export interface ChargeResult {
+ id: string;
+ status: 'succeeded' | 'processing' | 'failed';
+ errorCode?: string;
+}
+export interface Processor {
+ charge(
+ request: StoredRequest,
+ merchant: Merchant,
+ token: string,
+ fresh: boolean,
+ ): Promise;
+ retrieve(request: StoredRequest, merchant: Merchant): Promise;
+}
+const intentSchema = z.object({
+ id: z.string().startsWith('pi_'),
+ status: z.string(),
+ amount: z.number().int(),
+ amount_received: z.number().int(),
+ currency: z.string(),
+ livemode: z.boolean(),
+ metadata: z.object({ lucci_request_id: z.string() }),
+});
+
+// Fixed origin and version. Requesting apps cannot select a PSP URL or account.
+export class StripeProcessor implements Processor {
+ constructor(
+ private config: Config,
+ private http: typeof fetch = fetch,
+ ) {}
+
+ private async call(
+ path: string,
+ merchant: Merchant,
+ options?: { form: URLSearchParams; key: string },
+ ): Promise {
+ const headers: Record = {
+ Authorization: `Bearer ${this.config.stripeKey}`,
+ 'Stripe-Version': '2026-04-22.preview',
+ };
+ if (merchant.stripe_account) headers['Stripe-Account'] = merchant.stripe_account;
+ if (options) {
+ headers['Content-Type'] = 'application/x-www-form-urlencoded';
+ headers['Idempotency-Key'] = options.key;
+ }
+ let response: Response;
+ try {
+ response = await this.http(`https://api.stripe.com/v1/${path}`, {
+ method: options ? 'POST' : 'GET',
+ headers,
+ body: options?.form,
+ signal: AbortSignal.timeout(12_000),
+ redirect: 'error',
+ });
+ } catch {
+ throw new PaymentError(
+ 'payment_pending',
+ 'Payment status is being checked. Please do not start another payment.',
+ 503,
+ );
+ }
+ const body = (await response.json()) as { error?: { type?: string; code?: string } };
+ if (!response.ok) {
+ if (body.error?.type === 'card_error')
+ throw new PaymentError(
+ 'payment_declined',
+ 'Your payment was declined. Choose another payment method.',
+ 402,
+ );
+ // Do not make an unknown provider response retryable with a new key.
+ throw new PaymentError(
+ 'payment_pending',
+ 'We are checking the payment with the provider.',
+ 503,
+ );
+ }
+ return body;
+ }
+
+ private result(raw: unknown, request: StoredRequest): ChargeResult {
+ const intent = intentSchema.parse(raw);
+ if (
+ intent.amount !== totalAmount(request.session) ||
+ intent.currency !== request.session.currency.toLowerCase() ||
+ intent.livemode !== (request.mode === 'live') ||
+ intent.metadata.lucci_request_id !== request.id ||
+ (intent.status === 'succeeded' && intent.amount_received !== intent.amount)
+ ) {
+ throw new PaymentError(
+ 'provider_mismatch',
+ 'The payment details need to be reconciled before an order can be confirmed.',
+ 502,
+ );
+ }
+ if (intent.status === 'succeeded') return { id: intent.id, status: 'succeeded' };
+ if (['requires_payment_method', 'canceled'].includes(intent.status))
+ return { id: intent.id, status: 'failed', errorCode: 'payment_declined' };
+ // A requires_action intent might later complete. Keep it locked so a new
+ // attempt cannot charge twice. The native host owns any 3DS authentication.
+ return {
+ id: intent.id,
+ status: 'processing',
+ errorCode: intent.status === 'requires_action' ? 'requires_3ds' : 'payment_pending',
+ };
+ }
+
+ async charge(
+ request: StoredRequest,
+ merchant: Merchant,
+ token: string,
+ fresh: boolean,
+ ): Promise {
+ if (request.mode === 'demo')
+ throw new PaymentError('invalid_mode', 'Demo requests cannot use Stripe.');
+ if (request.provider_id) return this.retrieve(request, merchant);
+ if (fresh) {
+ const grant = z
+ .object({
+ id: z.string(),
+ deactivated_at: z.number().nullable(),
+ usage_limits: z.object({
+ currency: z.string(),
+ max_amount: z.number().int(),
+ expires_at: z.number().int(),
+ }),
+ })
+ .parse(
+ await this.call(`shared_payment/granted_tokens/${encodeURIComponent(token)}`, merchant),
+ );
+ if (
+ grant.id !== token ||
+ grant.deactivated_at !== null ||
+ grant.usage_limits.expires_at <= Date.now() / 1000 ||
+ grant.usage_limits.currency !== request.session.currency.toLowerCase() ||
+ grant.usage_limits.max_amount < totalAmount(request.session)
+ ) {
+ throw new PaymentError(
+ 'invalid_payment_token',
+ 'This payment authorization does not match the request.',
+ 400,
+ );
+ }
+ }
+ const form = new URLSearchParams({
+ amount: String(totalAmount(request.session)),
+ currency: request.session.currency.toLowerCase(),
+ 'payment_method_data[shared_payment_granted_token]': token,
+ confirm: 'true',
+ 'metadata[lucci_request_id]': request.id,
+ });
+ return this.result(
+ await this.call('payment_intents', merchant, {
+ form,
+ key: `lucci:${request.id}:${request.attempt}`,
+ }),
+ request,
+ );
+ }
+ async retrieve(request: StoredRequest, merchant: Merchant): Promise {
+ if (!request.provider_id)
+ throw new PaymentError(
+ 'payment_pending',
+ 'Waiting for the provider to confirm this payment.',
+ 409,
+ );
+ return this.result(
+ await this.call(`payment_intents/${encodeURIComponent(request.provider_id)}`, merchant),
+ request,
+ );
+ }
+}
+
+export class DemoProcessor implements Processor {
+ async charge(): Promise {
+ throw new PaymentError('demo_only', 'Use the explicitly labeled demo flow.');
+ }
+ async retrieve(): Promise {
+ throw new PaymentError('demo_only', 'No provider exists in demo mode.');
+ }
+}
diff --git a/server/store.ts b/server/store.ts
new file mode 100644
index 0000000..b9ad3f7
--- /dev/null
+++ b/server/store.ts
@@ -0,0 +1,92 @@
+import { DatabaseSync } from 'node:sqlite';
+import { mkdirSync } from 'node:fs';
+import { dirname } from 'node:path';
+import type { CheckoutSession, PaymentMode, PaymentStatus, Receipt } from '../shared/contracts.js';
+import { PaymentError } from './errors.js';
+
+export interface StoredRequest {
+ state_version: number;
+ id: string;
+ merchant_id: string;
+ payer_subject: string;
+ idempotency_key: string;
+ fingerprint: string;
+ session: CheckoutSession;
+ mode: PaymentMode;
+ status: PaymentStatus;
+ processor_account?: string;
+ expires_at: string;
+ created_at: string;
+ attempt: number;
+ token_hash?: string;
+ attempt_started_at?: string;
+ provider_id?: string;
+ receipt?: Receipt;
+}
+
+export class Store {
+ private db: DatabaseSync;
+ constructor(path: string) {
+ if (path !== ':memory:') mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
+ this.db = new DatabaseSync(path);
+ this.db.exec(`PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000;
+ CREATE TABLE IF NOT EXISTS requests (
+ id TEXT PRIMARY KEY, merchant_id TEXT NOT NULL, idempotency_key TEXT NOT NULL,
+ document TEXT NOT NULL, UNIQUE(merchant_id, idempotency_key)
+ );`);
+ }
+ close() {
+ this.db.close();
+ }
+ get(id: string): StoredRequest {
+ const row = this.db.prepare('SELECT document FROM requests WHERE id = ?').get(id) as
+ { document: string } | undefined;
+ if (!row) throw new PaymentError('not_found', 'Payment request not found.', 404);
+ return JSON.parse(row.document) as StoredRequest;
+ }
+ insert(request: StoredRequest): StoredRequest {
+ return this.transaction(() => {
+ const old = this.db
+ .prepare('SELECT document FROM requests WHERE merchant_id = ? AND idempotency_key = ?')
+ .get(request.merchant_id, request.idempotency_key) as { document: string } | undefined;
+ if (old) {
+ const existing = JSON.parse(old.document) as StoredRequest;
+ if (existing.fingerprint !== request.fingerprint)
+ throw new PaymentError(
+ 'idempotency_conflict',
+ 'This idempotency key was already used for a different request.',
+ 409,
+ );
+ return existing;
+ }
+ this.db
+ .prepare('INSERT INTO requests VALUES (?, ?, ?, ?)')
+ .run(request.id, request.merchant_id, request.idempotency_key, JSON.stringify(request));
+ return request;
+ });
+ }
+ update(id: string, action: (request: StoredRequest) => void): StoredRequest {
+ return this.transaction(() => {
+ const request = this.get(id);
+ const before = JSON.stringify(request);
+ action(request);
+ if (before === JSON.stringify(request)) return request;
+ request.state_version += 1;
+ this.db
+ .prepare('UPDATE requests SET document = ? WHERE id = ?')
+ .run(JSON.stringify(request), id);
+ return request;
+ });
+ }
+ private transaction(action: () => T): T {
+ this.db.exec('BEGIN IMMEDIATE');
+ try {
+ const result = action();
+ this.db.exec('COMMIT');
+ return result;
+ } catch (error) {
+ this.db.exec('ROLLBACK');
+ throw error;
+ }
+ }
+}
diff --git a/shared/contracts.ts b/shared/contracts.ts
new file mode 100644
index 0000000..dfb0708
--- /dev/null
+++ b/shared/contracts.ts
@@ -0,0 +1,119 @@
+import { z } from 'zod';
+
+export const minorAmount = z.number().int().min(0).max(10_000_000);
+export const currencySchema = z.enum(['USD', 'CAD', 'GBP', 'EUR']);
+export const lineInput = z
+ .object({
+ id: z.string().min(1).max(100),
+ name: z.string().trim().min(1).max(160),
+ quantity: z.number().int().min(1).max(100),
+ unit_amount: minorAmount,
+ discount: minorAmount.default(0),
+ tax: minorAmount.default(0),
+ })
+ .strict();
+
+export const requestInput = z
+ .object({
+ merchant_order_reference: z.string().min(1).max(128),
+ payer_subject: z.string().min(1).max(256),
+ currency: currencySchema,
+ line_items: z.array(lineInput).min(1).max(30),
+ // This first release accepts merchant-priced pickup orders. Delivery requires
+ // an address/tax/fulfillment update flow before exposing shipping to buyers.
+ fulfillment: z
+ .object({
+ type: z.literal('pickup'),
+ title: z.string().min(1).max(100),
+ subtitle: z.string().min(1).max(180),
+ })
+ .strict(),
+ expires_in_seconds: z.number().int().min(60).max(1800).default(900),
+ })
+ .strict();
+
+export type RequestInput = z.infer;
+export type PaymentMode = 'demo' | 'test' | 'live';
+export type PaymentStatus = 'pending' | 'processing' | 'paid' | 'cancelled' | 'expired' | 'failed';
+export interface CheckoutSession {
+ id: string;
+ payment_provider: {
+ provider: 'stripe';
+ merchant_id: string;
+ supported_payment_methods: (
+ { type: 'apple_pay' } | { type: 'card'; allowed_card_brands: string[] }
+ )[];
+ };
+ payment_mode: 'test' | 'live';
+ status: 'ready_for_payment' | 'completed';
+ currency: z.infer;
+ metadata: Record;
+ line_items: {
+ id: string;
+ item: { id: string; quantity: number };
+ name: string;
+ base_amount: number;
+ discount: number;
+ subtotal: number;
+ tax: number;
+ total: number;
+ }[];
+ totals: { type: string; display_text: string; amount: number }[];
+ fulfillment_options: {
+ id: string;
+ type: 'pickup';
+ title: string;
+ subtitle: string;
+ subtotal: number;
+ tax: number;
+ total: number;
+ }[];
+ fulfillment_option_id: string;
+ links: { type: string; url: string }[];
+}
+export interface Receipt {
+ id: string;
+ checkout_session_id: string;
+ amount: number;
+ currency: string;
+ paid_at: string;
+ mode: PaymentMode;
+ merchant_name: string;
+}
+export interface PaymentView {
+ state_version: number;
+ request_id: string;
+ merchant: { name: string; domain: string };
+ mode: PaymentMode;
+ status: PaymentStatus;
+ expires_at: string;
+ checkout_enabled: boolean;
+ session: CheckoutSession;
+ receipt?: Receipt;
+}
+export const paymentDataSchema = z
+ .object({
+ token: z
+ .string()
+ .regex(/^spt_[A-Za-z0-9_]+$/)
+ .max(500),
+ provider: z.literal('stripe').optional(),
+ })
+ .strict();
+export const buyerSchema = z
+ .object({
+ first_name: z.string().max(100).optional(),
+ last_name: z.string().max(100).optional(),
+ email: z.string().email().max(254).optional(),
+ })
+ .strict();
+
+export function totalAmount(session: CheckoutSession): number {
+ const total = session.totals.find((t) => t.type === 'total');
+ if (!total || !Number.isSafeInteger(total.amount) || total.amount < 1)
+ throw new Error('Invalid checkout total');
+ return total.amount;
+}
+export function money(amount: number, currency: string): string {
+ return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount / 100);
+}
diff --git a/test-image/README.md b/test-image/README.md
deleted file mode 100644
index 9f4d1f6..0000000
--- a/test-image/README.md
+++ /dev/null
@@ -1,20 +0,0 @@
-# Test Images
-
-This directory contains sample images for testing the metadata scraper extension.
-
-## How to Test
-
-1. Open VS Code with the metascraper extension installed or running in development mode
-2. Right-click on any image in this folder
-3. Select "View Image Metadata" to see what metadata exists in the image
-4. Select "Clean Image Metadata" to remove the metadata
-5. Use "View Image Metadata" again to verify the metadata has been removed
-
-## Adding Your Own Test Images
-
-You can add your own images to this folder. Images from digital cameras, smartphones, or downloaded from the internet typically contain rich metadata that can be cleaned with this extension.
-
-Metadata-rich images typically include:
-- Photos taken with smartphones (contain GPS coordinates)
-- Professional camera photos (contain camera settings)
-- Stock photos (contain copyright information)
\ No newline at end of file
diff --git a/test-image/add-test-metadata.js b/test-image/add-test-metadata.js
deleted file mode 100644
index be3613f..0000000
--- a/test-image/add-test-metadata.js
+++ /dev/null
@@ -1,80 +0,0 @@
-const { ExifTool } = require('exiftool-vendored');
-const path = require('path');
-const fs = require('fs');
-
-// Check for command line arguments
-if (process.argv.length < 3) {
- console.log('Usage: node add-test-metadata.js ');
- process.exit(1);
-}
-
-const imagePath = process.argv[2];
-
-// Make sure the file exists
-if (!fs.existsSync(imagePath)) {
- console.error(`Error: File ${imagePath} does not exist`);
- process.exit(1);
-}
-
-// Make sure it's an image
-const fileExt = path.extname(imagePath).toLowerCase();
-const supportedExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.tiff', '.webp'];
-if (!supportedExtensions.includes(fileExt)) {
- console.error(`Error: File ${imagePath} is not a supported image format`);
- process.exit(1);
-}
-
-// Sample metadata to add for testing
-const testMetadata = {
- // GPS coordinates (Central Park, NYC)
- GPSLatitude: 40.7812,
- GPSLatitudeRef: 'N',
- GPSLongitude: 73.9665,
- GPSLongitudeRef: 'W',
-
- // Camera info
- Make: 'Test Camera Brand',
- Model: 'Test Camera Model X100',
- LensModel: 'Test Lens 18-55mm',
-
- // Author and copyright
- Artist: 'Test Photographer',
- Copyright: 'Ā© 2024 Test Copyright',
-
- // Software
- Software: 'Adobe Photoshop 2024',
-
- // Custom metadata
- UserComment: 'This is a test comment with tracking info',
-
- // Date time
- DateTimeOriginal: '2024:05:01 10:30:00',
-};
-
-// Add the metadata
-async function addMetadata() {
- const exiftool = new ExifTool();
-
- try {
- console.log(`Adding test metadata to ${imagePath}...`);
-
- // Add metadata
- await exiftool.write(imagePath, testMetadata);
-
- console.log('Metadata added successfully!');
- console.log('Metadata added:');
- console.log(JSON.stringify(testMetadata, null, 2));
-
- // Read back to verify
- const updatedMetadata = await exiftool.read(imagePath);
- console.log('\nVerify metadata now in file:');
- console.log(JSON.stringify(updatedMetadata, null, 2));
-
- } catch (error) {
- console.error('Error adding metadata:', error);
- } finally {
- await exiftool.end();
- }
-}
-
-addMetadata();
\ No newline at end of file
diff --git a/test-image/download-test-image.html b/test-image/download-test-image.html
deleted file mode 100644
index 038cb11..0000000
--- a/test-image/download-test-image.html
+++ /dev/null
@@ -1,90 +0,0 @@
-
-
-
-
-
- Test Images for Metadata Scraper
-
-
-
- Test Images for Metadata Scraper
-
- This page contains sample images that have embedded metadata for testing your VS Code extension.
-
-
-
Sample Image 1: Mountain Landscape
-
-
Description: A mountain landscape with metadata. Right-click and save this image to your test-image folder.
-
Download Image
-
-
-
-
Sample Image 2: Portrait Photo
-
-
Description: A portrait photo with metadata. Right-click and save this image to your test-image folder.
-
Download Image
-
-
-
-
Sample Image 3: Urban Scene
-
-
Description: An urban cityscape with metadata. Right-click and save this image to your test-image folder.
-
Download Image
-
-
-
-
Testing Steps:
-
- Download one or more images using the buttons above
- Save the images to your test-image folder
- Open VS Code with the Metadata Scraper extension installed
- Right-click on an image and select "View Image Metadata" to see what data is embedded
- Right-click again and select "Clean Image Metadata" to remove the metadata
- Verify the cleaning worked by using "View Image Metadata" again
-
-
-
-
\ No newline at end of file
diff --git a/test-image/example-usage.js b/test-image/example-usage.js
deleted file mode 100644
index 39b5204..0000000
--- a/test-image/example-usage.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/**
- * Example script demonstrating how to use ExifTool directly to view and clean metadata
- * This shows the core functionality that powers the VS Code extension
- */
-
-const { ExifTool } = require('exiftool-vendored');
-const fs = require('fs');
-const path = require('path');
-
-// Check for command line arguments
-if (process.argv.length < 3) {
- console.log('Usage: node example-usage.js ');
- process.exit(1);
-}
-
-const imagePath = process.argv[2];
-
-// Demo the metadata operations
-async function demoMetadataOperations() {
- // Initialize ExifTool
- const exiftool = new ExifTool();
-
- try {
- console.log(`\n--- DEMONSTRATING METADATA OPERATIONS ON: ${imagePath} ---\n`);
-
- // Step 1: Read and display the current metadata
- console.log('STEP 1: VIEWING METADATA');
- console.log('------------------------');
-
- const metadata = await exiftool.read(imagePath);
- console.log(JSON.stringify(metadata, null, 2));
-
- // Step 2: Clean the metadata by creating a copy
- console.log('\nSTEP 2: CLEANING METADATA');
- console.log('------------------------');
-
- // Create a temp file path for the clean image
- const fileExt = path.extname(imagePath);
- const baseFilename = path.basename(imagePath, fileExt);
- const outputPath = path.join(
- path.dirname(imagePath),
- `${baseFilename}-clean${fileExt}`
- );
-
- console.log(`Creating clean version at: ${outputPath}`);
-
- // Keep only basic metadata, remove all others
- await exiftool.write(
- imagePath,
- { all: '' },
- ['-P', '-o', outputPath]
- );
-
- console.log('Metadata cleaning completed!');
-
- // Step 3: Verify the cleaned metadata
- console.log('\nSTEP 3: VERIFYING CLEANED METADATA');
- console.log('-------------------------------');
-
- const cleanedMetadata = await exiftool.read(outputPath);
- console.log(JSON.stringify(cleanedMetadata, null, 2));
-
- console.log('\nCOMPARISON:');
- console.log(`- Original metadata had ${Object.keys(metadata).length} properties`);
- console.log(`- Cleaned metadata has ${Object.keys(cleanedMetadata).length} properties`);
-
- console.log('\nDONE! Check the cleaned image file.');
-
- } catch (error) {
- console.error('Error during metadata operations:', error);
- } finally {
- // Make sure to close exiftool process
- await exiftool.end();
- }
-}
-
-// Run the demo
-demoMetadataOperations();
\ No newline at end of file
diff --git a/test/extension.test.js b/test/extension.test.js
deleted file mode 100644
index 69ead83..0000000
--- a/test/extension.test.js
+++ /dev/null
@@ -1,15 +0,0 @@
-const assert = require('assert');
-
-// You can import and use all API from the 'vscode' module
-// as well as import your extension to test it
-const vscode = require('vscode');
-// const myExtension = require('../extension');
-
-suite('Extension Test Suite', () => {
- vscode.window.showInformationMessage('Start all tests.');
-
- test('Sample test', () => {
- assert.strictEqual(-1, [1, 2, 3].indexOf(5));
- assert.strictEqual(-1, [1, 2, 3].indexOf(0));
- });
-});
diff --git a/tests/auth.test.ts b/tests/auth.test.ts
new file mode 100644
index 0000000..9e6cad1
--- /dev/null
+++ b/tests/auth.test.ts
@@ -0,0 +1,81 @@
+import test from 'node:test';
+import assert from 'node:assert/strict';
+import { createServer } from 'node:http';
+import { generateKeyPair, exportJWK, SignJWT } from 'jose';
+import { createAuthenticator, requireScope } from '../server/auth.js';
+import { authenticateMerchant, demoMerchant, hash, type Config } from '../server/config.js';
+
+test('OAuth verifies signature, expiry, issuer, audience, subject and permissions', async (t) => {
+ const { publicKey, privateKey } = await generateKeyPair('RS256');
+ const jwk = { ...(await exportJWK(publicKey)), kid: 'test-key', alg: 'RS256', use: 'sig' };
+ const server = createServer((_req, res) => {
+ res.setHeader('Content-Type', 'application/json');
+ res.end(JSON.stringify({ keys: [jwk] }));
+ }).listen(0, '127.0.0.1');
+ await new Promise((resolve) => server.on('listening', resolve));
+ t.after(() => server.close());
+ const address = server.address();
+ assert.ok(address && typeof address !== 'string');
+ const config: Config = {
+ mode: 'test',
+ port: 4242,
+ baseUrl: 'https://pay.example',
+ databasePath: ':memory:',
+ checkoutEnabled: true,
+ merchants: [demoMerchant],
+ issuer: 'https://identity.example',
+ jwksUrl: `http://127.0.0.1:${address.port}`,
+ audience: 'https://pay.example/mcp',
+ };
+ const auth = createAuthenticator(config);
+ const token = (claims: Record = {}) =>
+ new SignJWT({ sub: 'customer-1', scope: 'payments:read', ...claims })
+ .setProtectedHeader({ alg: 'RS256', kid: 'test-key' })
+ .setIssuedAt()
+ .setIssuer('https://identity.example')
+ .setAudience('https://pay.example/mcp')
+ .setExpirationTime('5m')
+ .sign(privateKey);
+ const identity = await auth(`Bearer ${await token()}`);
+ assert.equal(identity.subject, 'customer-1');
+ requireScope(identity, 'payments:read');
+ assert.throws(() => requireScope(identity, 'payments:pay'), { code: 'forbidden' });
+ await assert.rejects(auth(), { code: 'unauthorized' });
+ await assert.rejects(auth(`Bearer ${await token({ scope: undefined })}`), {
+ code: 'unauthorized',
+ });
+ await assert.rejects(auth(`Bearer ${await token({ sub: '' })}`), { code: 'unauthorized' });
+ for (const fields of [
+ { iss: 'https://attacker.example' },
+ { aud: 'https://other.example' },
+ { exp: 1 },
+ ]) {
+ const bad = await new SignJWT({
+ sub: 'customer-1',
+ scope: 'payments:read',
+ iss: config.issuer,
+ aud: config.audience,
+ exp: Math.floor(Date.now() / 1000) + 300,
+ ...fields,
+ })
+ .setProtectedHeader({ alg: 'RS256', kid: 'test-key' })
+ .setIssuedAt()
+ .sign(privateKey);
+ await assert.rejects(auth(`Bearer ${bad}`), { code: 'unauthorized' });
+ }
+});
+
+test('merchant keys are distinct from user OAuth identities and empty credentials fail', () => {
+ const key = 'merchant-test-secret-at-least-32-chars';
+ const config: Config = {
+ mode: 'test',
+ port: 4242,
+ baseUrl: 'https://pay.example',
+ databasePath: ':memory:',
+ checkoutEnabled: true,
+ merchants: [{ ...demoMerchant, api_key_sha256: hash(key) }],
+ };
+ assert.equal(authenticateMerchant(`Bearer ${key}`, config).id, demoMerchant.id);
+ for (const header of [undefined, '', 'Bearer ', 'Bearer bad', 'Bearer demo-merchant-key'])
+ assert.throws(() => authenticateMerchant(header, config), { code: 'unauthorized' });
+});
diff --git a/tests/http.test.ts b/tests/http.test.ts
new file mode 100644
index 0000000..cd9a147
--- /dev/null
+++ b/tests/http.test.ts
@@ -0,0 +1,138 @@
+import test from 'node:test';
+import assert from 'node:assert/strict';
+import { createApp } from '../server/app.js';
+import { Store } from '../server/store.js';
+import { Payments } from '../server/payments.js';
+import { DemoProcessor } from '../server/processor.js';
+import { loadConfig } from '../server/config.js';
+import { Client } from '@modelcontextprotocol/sdk/client/index.js';
+import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
+import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
+import { createMcpServer } from '../server/mcp.js';
+import { demoMerchant } from '../server/config.js';
+
+test('native completion tool validates the token envelope, scope and completed output schema', async (t) => {
+ const store = new Store(':memory:');
+ t.after(() => store.close());
+ const config = loadConfig({ PAYMENT_MODE: 'demo' });
+ config.mode = 'test';
+ config.checkoutEnabled = true;
+ const payments = new Payments(config, store, {
+ charge: async () => ({ id: 'pi_contract', status: 'succeeded' }),
+ retrieve: async () => ({ id: 'pi_contract', status: 'succeeded' }),
+ });
+ const request = payments.create(
+ demoMerchant,
+ {
+ merchant_order_reference: 'native-order',
+ payer_subject: 'payer',
+ currency: 'USD',
+ line_items: [{ id: 'cup', name: 'Cup', quantity: 1, unit_amount: 2400 }],
+ fulfillment: { type: 'pickup', title: 'Pickup', subtitle: 'Ready now' },
+ },
+ 'native-order',
+ );
+ async function connect(scopes: string[]) {
+ const server = createMcpServer(
+ payments,
+ { subject: 'payer', scopes: new Set(scopes) },
+ 'Widget',
+ );
+ const client = new Client({ name: 'native-contract', version: '1.0.0' });
+ const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
+ await Promise.all([server.connect(serverTransport), client.connect(clientTransport)]);
+ t.after(() => client.close());
+ t.after(() => server.close());
+ return client;
+ }
+ const reader = await connect(['payments:read']);
+ const denied = await reader.callTool({
+ name: 'complete_checkout',
+ arguments: { checkout_session_id: request.id, payment_data: { token: 'spt_fixture' } },
+ });
+ assert.equal(denied.isError, true);
+ const client = await connect(['payments:read', 'payments:pay']);
+ const tools = (await client.listTools()).tools;
+ assert.ok(!tools.some((t) => t.name === 'simulate_payment'));
+ const tool = tools.find((t) => t.name === 'complete_checkout')!;
+ assert.ok(tool.outputSchema);
+ assert.deepEqual((tool._meta?.ui as { visibility: string[] }).visibility, ['app']);
+ const invalid = await client.callTool({
+ name: 'complete_checkout',
+ arguments: {
+ checkout_session_id: request.id,
+ payment_data: { token: 'spt_fixture', card_number: '4242424242424242' },
+ },
+ });
+ assert.equal(invalid.isError, true);
+ const result = await client.callTool({
+ name: 'complete_checkout',
+ arguments: {
+ checkout_session_id: request.id,
+ buyer: { email: 'test@example.com' },
+ payment_data: { token: 'spt_fixture', provider: 'stripe' },
+ },
+ });
+ assert.equal(result.isError, undefined);
+ assert.equal((result.structuredContent as { status: string }).status, 'completed');
+ assert.ok(!JSON.stringify(result).includes('spt_fixture'));
+ assert.ok(!JSON.stringify(result).includes('test@example.com'));
+});
+
+test('HTTP server and MCP client complete the request, simulation and receipt workflow', async (t) => {
+ const config = loadConfig({ PAYMENT_MODE: 'demo' });
+ const store = new Store(':memory:');
+ const payments = new Payments(config, store, new DemoProcessor());
+ const server = createApp(config, payments, 'Widget').listen(
+ 0,
+ '127.0.0.1',
+ );
+ await new Promise((resolve) => server.on('listening', resolve));
+ const address = server.address();
+ assert.ok(address && typeof address !== 'string');
+ config.port = address.port;
+ config.baseUrl = `http://127.0.0.1:${address.port}`;
+ t.after(() => {
+ server.close();
+ store.close();
+ });
+ const health = await fetch(`${config.baseUrl}/healthz`);
+ assert.equal(health.status, 200);
+ const client = new Client({ name: 'integration-test', version: '1.0.0' });
+ const transport = new StreamableHTTPClientTransport(new URL(`${config.baseUrl}/mcp`));
+ await client.connect(transport);
+ t.after(() => client.close());
+ const tools = (await client.listTools()).tools;
+ assert.ok(tools.some((t) => t.name === 'show_payment_request'));
+ assert.ok(!tools.some((t) => t.name === 'complete_checkout'));
+ const simulate = tools.find((t) => t.name === 'simulate_payment')!;
+ assert.deepEqual((simulate._meta?.ui as { visibility: string[] }).visibility, ['app']);
+ const resource = await client.readResource({ uri: 'ui://lucci-pay/payment-v1.html' });
+ assert.equal(resource.contents[0]?.mimeType, 'text/html;profile=mcp-app');
+ const demo = await client.callTool({ name: 'create_demo_payment_request', arguments: {} });
+ const id = (demo.structuredContent as { payment: { request_id: string } }).payment.request_id;
+ assert.equal(
+ (await client.callTool({ name: 'show_payment_request', arguments: { request_id: id } }))
+ .isError,
+ undefined,
+ );
+ const result = await client.callTool({
+ name: 'simulate_payment',
+ arguments: { request_id: id, outcome: 'success' },
+ });
+ const view = (
+ result.structuredContent as { payment: { status: string; receipt: { mode: string } } }
+ ).payment;
+ assert.equal(view.status, 'paid');
+ assert.equal(view.receipt.mode, 'demo');
+ const foreignOrigin = await fetch(`${config.baseUrl}/healthz`, {
+ headers: { Origin: 'https://attacker.example' },
+ });
+ assert.equal(foreignOrigin.status, 403);
+ const noKey = await fetch(`${config.baseUrl}/v1/payment-requests/${id}`);
+ assert.equal(noKey.status, 401);
+ const status = await fetch(`${config.baseUrl}/v1/payment-requests/${id}`, {
+ headers: { Authorization: 'Bearer demo-merchant-key' },
+ });
+ assert.equal(((await status.json()) as { status: string }).status, 'paid');
+});
diff --git a/tests/payments.test.ts b/tests/payments.test.ts
new file mode 100644
index 0000000..6ed8ef6
--- /dev/null
+++ b/tests/payments.test.ts
@@ -0,0 +1,263 @@
+import test from 'node:test';
+import assert from 'node:assert/strict';
+import { mkdtempSync, rmSync } from 'node:fs';
+import { tmpdir } from 'node:os';
+import { join } from 'node:path';
+import { Store } from '../server/store.js';
+import { Payments } from '../server/payments.js';
+import { demoMerchant, loadConfig, hash, type Config } from '../server/config.js';
+import { PaymentError } from '../server/errors.js';
+import type { Processor } from '../server/processor.js';
+import { requestInput, totalAmount } from '../shared/contracts.js';
+import { latestPayment } from '../web/bridge.js';
+
+test('late host results cannot regress a paid receipt to a pending request', async (t) => {
+ const { service } = fixture(t);
+ const request = service.create(merchant, input, 'versioned-order');
+ const pending = await service.get(request.id, 'customer-1');
+ const paid = await service.complete(request.id, 'customer-1', 'spt_versioned');
+ assert.ok(paid.state_version > pending.state_version);
+ assert.equal(latestPayment(paid, pending).status, 'paid');
+ assert.equal(latestPayment(pending, paid).status, 'paid');
+ assert.equal((await service.get(request.id, 'customer-1')).state_version, paid.state_version);
+});
+
+export const input = {
+ merchant_order_reference: 'order-123',
+ payer_subject: 'customer-1',
+ currency: 'USD',
+ line_items: [
+ { id: 'cup', name: 'Ceramic cup', quantity: 2, unit_amount: 1500, discount: 200, tax: 231 },
+ ],
+ fulfillment: { type: 'pickup', title: 'Pickup', subtitle: 'Ready tomorrow' },
+};
+export const merchant = {
+ ...demoMerchant,
+ id: 'studio',
+ name: 'Studio',
+ provider_merchant_id: 'profile_studio',
+};
+export const testConfig = (): Config => ({
+ mode: 'test',
+ port: 4242,
+ baseUrl: 'http://localhost:4242',
+ databasePath: ':memory:',
+ checkoutEnabled: true,
+ merchants: [merchant],
+});
+const noop: Processor = {
+ charge: async () => ({ id: 'pi_1', status: 'succeeded' }),
+ retrieve: async () => ({ id: 'pi_1', status: 'succeeded' }),
+};
+function fixture(t: test.TestContext, processor: Processor = noop) {
+ const store = new Store(':memory:');
+ t.after(() => store.close());
+ return { store, service: new Payments(testConfig(), store, processor) };
+}
+test('server computes totals in minor units, with line discounts and tax', (t) => {
+ const { service } = fixture(t);
+ const r = service.create(merchant, input, 'order-123');
+ assert.equal(totalAmount(r.session), 3031);
+ assert.equal(r.session.line_items[0]!.subtotal, 2800);
+ assert.deepEqual(r.session.payment_provider.supported_payment_methods[0], { type: 'apple_pay' });
+ assert.equal(r.session.payment_provider.merchant_id, 'profile_studio');
+});
+test('rejects fractions, negatives, caller totals and caller payment account overrides', (t) => {
+ const { service } = fixture(t);
+ for (const unit_amount of [-1, 0.3, 20_000_000])
+ assert.throws(() =>
+ service.create(
+ merchant,
+ { ...input, line_items: [{ ...input.line_items[0], unit_amount }] },
+ 'order-123',
+ ),
+ );
+ assert.throws(() => service.create(merchant, { ...input, total: 1 }, 'order-123'));
+ assert.throws(() =>
+ service.create(
+ merchant,
+ { ...input, payment_provider: { merchant_id: 'attacker' } },
+ 'order-123',
+ ),
+ );
+ assert.throws(() =>
+ service.create(
+ merchant,
+ { ...input, line_items: [{ ...input.line_items[0], discount: 5000 }] },
+ 'order-123',
+ ),
+ );
+ assert.throws(() =>
+ service.create(
+ merchant,
+ { ...input, line_items: [input.line_items[0], input.line_items[0]] },
+ 'order-123',
+ ),
+ );
+});
+test('creation retries return the same request and changed payloads conflict', (t) => {
+ const { service } = fixture(t);
+ const first = service.create(merchant, input, 'order-123');
+ assert.equal(service.create(merchant, input, 'order-123').id, first.id);
+ assert.throws(() => service.create(merchant, { ...input, currency: 'EUR' }, 'order-123'), {
+ code: 'idempotency_conflict',
+ });
+});
+test('payer and merchant boundaries apply to every read and write', async (t) => {
+ const { service } = fixture(t);
+ const r = service.create(merchant, input, 'order-123');
+ await assert.rejects(service.get(r.id, 'other-user'), { code: 'not_found' });
+ await assert.rejects(service.complete(r.id, 'other-user', 'spt_token'), { code: 'not_found' });
+ await assert.rejects(service.cancel(r.id, 'other-user'), { code: 'not_found' });
+ await assert.rejects(service.merchantStatus(r.id, { ...merchant, id: 'another-merchant' }), {
+ code: 'not_found',
+ });
+});
+test('merchant configuration changes cannot redirect an existing payment', async (t) => {
+ const { service } = fixture(t);
+ const r = service.create(merchant, input, 'order-123');
+ service.config.merchants = [{ ...merchant, stripe_account: 'acct_changed' }];
+ await assert.rejects(service.complete(r.id, 'customer-1', 'spt_token'), {
+ code: 'merchant_changed',
+ });
+});
+test('cancel and expiry cannot be paid or turned into simulated success', async (t) => {
+ const { service, store } = fixture(t);
+ const r = service.create(merchant, input, 'order-123');
+ await service.cancel(r.id, 'customer-1');
+ await assert.rejects(service.complete(r.id, 'customer-1', 'spt_token'), {
+ code: 'request_closed',
+ });
+ const expired = service.create(merchant, input, 'order-456');
+ store.update(expired.id, (r) => {
+ r.expires_at = '2000-01-01T00:00:00.000Z';
+ });
+ await assert.rejects(service.complete(expired.id, 'customer-1', 'spt_token'), {
+ code: 'request_closed',
+ });
+ await assert.rejects(service.simulate(expired.id, 'customer-1', 'success'), {
+ code: 'demo_disabled',
+ });
+});
+test('paid retries are idempotent and cannot be cancelled', async (t) => {
+ let charges = 0;
+ const { service } = fixture(t, {
+ ...noop,
+ charge: async () => {
+ charges++;
+ return { id: 'pi_paid', status: 'succeeded' };
+ },
+ });
+ const r = service.create(merchant, input, 'order-123');
+ const one = await service.complete(r.id, 'customer-1', 'spt_token');
+ const two = await service.complete(r.id, 'customer-1', 'spt_token');
+ assert.equal(charges, 1);
+ assert.deepEqual(one.receipt, two.receipt);
+ assert.equal(one.receipt?.amount, 3031);
+ await assert.rejects(service.cancel(r.id, 'customer-1'), { code: 'cannot_cancel' });
+});
+test('unknown provider outcomes remain locked; never accept another token', async (t) => {
+ const { service, store } = fixture(t, {
+ ...noop,
+ charge: async () => {
+ throw new PaymentError('payment_pending', 'Timeout');
+ },
+ });
+ const r = service.create(merchant, input, 'order-123');
+ await assert.rejects(service.complete(r.id, 'customer-1', 'spt_first'));
+ assert.equal(store.get(r.id).status, 'processing');
+ await assert.rejects(service.complete(r.id, 'customer-1', 'spt_other'), {
+ code: 'payment_pending',
+ });
+ await assert.rejects(service.cancel(r.id, 'customer-1'), { code: 'cannot_cancel' });
+ assert.equal((await service.get(r.id, 'customer-1')).receipt, undefined);
+});
+test('a declined attempt can retry with a new token but not reuse a failed token', async (t) => {
+ let count = 0;
+ const { service } = fixture(t, {
+ ...noop,
+ charge: async () => {
+ if (++count === 1) throw new PaymentError('payment_declined', 'Declined');
+ return { id: 'pi_second', status: 'succeeded' };
+ },
+ });
+ const r = service.create(merchant, input, 'order-123');
+ await assert.rejects(service.complete(r.id, 'customer-1', 'spt_first'));
+ await assert.rejects(service.complete(r.id, 'customer-1', 'spt_first'), {
+ code: 'payment_declined',
+ });
+ assert.equal((await service.complete(r.id, 'customer-1', 'spt_second')).status, 'paid');
+});
+test('provider processing and 3DS are not reported as paid', async (t) => {
+ const { service, store } = fixture(t, {
+ ...noop,
+ charge: async () => ({ id: 'pi_auth', status: 'processing', errorCode: 'requires_3ds' }),
+ retrieve: async () => ({ id: 'pi_auth', status: 'processing' }),
+ });
+ const r = service.create(merchant, input, 'order-123');
+ await assert.rejects(service.complete(r.id, 'customer-1', 'spt_auth'), { code: 'requires_3ds' });
+ assert.equal(store.get(r.id).status, 'processing');
+ assert.equal((await service.get(r.id, 'customer-1')).receipt, undefined);
+});
+test('restart recovers the request and reuses the same attempt after an ambiguous timeout', async (t) => {
+ const directory = mkdtempSync(join(tmpdir(), 'lucci-pay-test-'));
+ t.after(() => rmSync(directory, { recursive: true, force: true }));
+ const path = join(directory, 'pay.sqlite');
+ let store = new Store(path);
+ const attempts: number[] = [];
+ let first = true;
+ const processor: Processor = {
+ ...noop,
+ charge: async (request) => {
+ attempts.push(request.attempt);
+ if (first) {
+ first = false;
+ throw new PaymentError('payment_pending', 'Timeout');
+ }
+ return { id: 'pi_recovered', status: 'succeeded' };
+ },
+ };
+ let service = new Payments(testConfig(), store, processor);
+ const r = service.create(merchant, input, 'order-123');
+ await assert.rejects(service.complete(r.id, 'customer-1', 'spt_original'));
+ assert.equal(store.get(r.id).token_hash, hash('spt_original'));
+ assert.ok(!JSON.stringify(store.get(r.id)).includes('spt_original'));
+ store.close();
+ store = new Store(path);
+ t.after(() => store.close());
+ service = new Payments(testConfig(), store, processor);
+ assert.equal((await service.complete(r.id, 'customer-1', 'spt_original')).status, 'paid');
+ assert.deepEqual(attempts, [1, 1]);
+});
+test('stale ambiguous attempts do not replay after provider idempotency retention', async (t) => {
+ const { service, store } = fixture(t);
+ const r = service.create(merchant, input, 'order-123');
+ store.update(r.id, (r) => {
+ r.status = 'processing';
+ r.token_hash = hash('spt_old');
+ r.attempt_started_at = '2000-01-01T00:00:00Z';
+ });
+ await assert.rejects(service.complete(r.id, 'customer-1', 'spt_old'), {
+ code: 'reconciliation_required',
+ });
+});
+test('demo cannot reach a payment processor and production cannot simulate', async (t) => {
+ const store = new Store(':memory:');
+ t.after(() => store.close());
+ const service = new Payments(loadConfig({ PAYMENT_MODE: 'demo' }), store, {
+ ...noop,
+ charge: async () => {
+ throw new Error('Should never call provider');
+ },
+ });
+ const r = service.demoRequest();
+ await assert.rejects(service.complete(r.id, 'demo-user', 'spt_real'), {
+ code: 'checkout_unavailable',
+ });
+ const result = await service.simulate(r.id, 'demo-user', 'success');
+ assert.equal(result.receipt?.mode, 'demo');
+ assert.equal(
+ requestInput.safeParse({ ...input, fulfillment: { type: 'shipping' } }).success,
+ false,
+ );
+});
diff --git a/tests/processor.test.ts b/tests/processor.test.ts
new file mode 100644
index 0000000..e3918f7
--- /dev/null
+++ b/tests/processor.test.ts
@@ -0,0 +1,173 @@
+import test from 'node:test';
+import assert from 'node:assert/strict';
+import { Store } from '../server/store.js';
+import { Payments } from '../server/payments.js';
+import { StripeProcessor } from '../server/processor.js';
+import { demoMerchant, type Config } from '../server/config.js';
+
+const merchant = {
+ ...demoMerchant,
+ provider_merchant_id: 'profile_seller',
+ stripe_account: 'acct_seller',
+};
+const config: Config = {
+ mode: 'test',
+ port: 4242,
+ baseUrl: 'http://localhost:4242',
+ databasePath: ':memory:',
+ checkoutEnabled: true,
+ merchants: [merchant],
+ stripeKey: 'sk_test_fixture',
+};
+const fixture = (t: test.TestContext, http: typeof fetch) => {
+ const store = new Store(':memory:');
+ t.after(() => store.close());
+ const service = new Payments(config, store, new StripeProcessor(config, http));
+ const request = service.create(
+ merchant,
+ {
+ merchant_order_reference: 'processor-order',
+ payer_subject: 'customer-1',
+ currency: 'USD',
+ line_items: [{ id: 'cup', name: 'Cup', quantity: 1, unit_amount: 2400 }],
+ fulfillment: { type: 'pickup', title: 'Pickup', subtitle: 'Ready now' },
+ },
+ 'processor-order',
+ );
+ return { service, store, request };
+};
+
+test('Stripe receives a scoped SPT, exact amount, registered account and stable retry key', async (t) => {
+ let id = '';
+ const calls: { path: string; options?: RequestInit }[] = [];
+ const http: typeof fetch = async (url, options) => {
+ calls.push({ path: String(url), options });
+ if (String(url).includes('granted_tokens'))
+ return Response.json({
+ id: 'spt_fixture',
+ deactivated_at: null,
+ usage_limits: {
+ currency: 'usd',
+ max_amount: 2400,
+ expires_at: Math.floor(Date.now() / 1000) + 900,
+ },
+ });
+ return Response.json({
+ id: 'pi_fixture',
+ status: 'succeeded',
+ amount: 2400,
+ amount_received: 2400,
+ currency: 'usd',
+ livemode: false,
+ metadata: { lucci_request_id: id },
+ });
+ };
+ const { service, request } = fixture(t, http);
+ id = request.id;
+ assert.equal((await service.complete(id, 'customer-1', 'spt_fixture')).status, 'paid');
+ const options = calls[1]!.options!;
+ const headers = new Headers(options.headers);
+ assert.equal(headers.get('Stripe-Account'), 'acct_seller');
+ assert.equal(headers.get('Stripe-Version'), '2026-04-22.preview');
+ assert.equal(headers.get('Idempotency-Key'), `lucci:${id}:1`);
+ const form = options.body as URLSearchParams;
+ assert.equal(form.get('amount'), '2400');
+ assert.equal(form.get('payment_method_data[shared_payment_granted_token]'), 'spt_fixture');
+ assert.equal(form.get('confirm'), 'true');
+ assert.equal(calls.length, 2);
+});
+
+test('invalid or expired token limits are rejected before a charge', async (t) => {
+ let calls = 0;
+ const { service, request } = fixture(t, async () => {
+ calls++;
+ return Response.json({
+ id: 'spt_fixture',
+ deactivated_at: null,
+ usage_limits: { currency: 'eur', max_amount: 1, expires_at: 1 },
+ });
+ });
+ await assert.rejects(service.complete(request.id, 'customer-1', 'spt_fixture'), {
+ code: 'invalid_payment_token',
+ });
+ assert.equal(calls, 1);
+});
+
+test('provider amount, currency, mode, metadata and captured amount must match before receipt', async (t) => {
+ for (const mismatch of [
+ { amount: 1 },
+ { currency: 'eur' },
+ { livemode: true },
+ { amount_received: 10 },
+ { metadata: { lucci_request_id: 'other' } },
+ ]) {
+ let id = '';
+ const { service, request, store } = fixture(t, async (url) => {
+ if (String(url).includes('granted_tokens'))
+ return Response.json({
+ id: 'spt_fixture',
+ deactivated_at: null,
+ usage_limits: { currency: 'usd', max_amount: 2400, expires_at: (Date.now() / 1000) | 0 },
+ });
+ return Response.json({
+ id: 'pi_fixture',
+ status: 'succeeded',
+ amount: 2400,
+ amount_received: 2400,
+ currency: 'usd',
+ livemode: false,
+ metadata: { lucci_request_id: id },
+ ...mismatch,
+ });
+ });
+ id = request.id;
+ // Resume a persisted uncertain attempt to exercise provider response validation.
+ const { hash } = await import('../server/config.js');
+ store.update(id, (r) => {
+ r.status = 'processing';
+ r.attempt = 1;
+ r.token_hash = hash('spt_fixture');
+ r.attempt_started_at = new Date().toISOString();
+ });
+ await assert.rejects(service.complete(id, 'customer-1', 'spt_fixture'), {
+ code: 'provider_mismatch',
+ });
+ assert.equal(store.get(id).receipt, undefined);
+ }
+});
+
+test('concurrent confirmations share one Stripe idempotency key', async (t) => {
+ let id = '';
+ const keys: string[] = [];
+ const { service, request } = fixture(t, async (url, options) => {
+ if (String(url).includes('granted_tokens'))
+ return Response.json({
+ id: 'spt_fixture',
+ deactivated_at: null,
+ usage_limits: {
+ currency: 'usd',
+ max_amount: 2400,
+ expires_at: Math.floor(Date.now() / 1000) + 900,
+ },
+ });
+ keys.push(new Headers(options?.headers).get('Idempotency-Key')!);
+ await new Promise((resolve) => setTimeout(resolve, 10));
+ return Response.json({
+ id: 'pi_fixture',
+ status: 'succeeded',
+ amount: 2400,
+ amount_received: 2400,
+ currency: 'usd',
+ livemode: false,
+ metadata: { lucci_request_id: id },
+ });
+ });
+ id = request.id;
+ const results = await Promise.all([
+ service.complete(id, 'customer-1', 'spt_fixture'),
+ service.complete(id, 'customer-1', 'spt_fixture'),
+ ]);
+ assert.equal(new Set(keys).size, 1);
+ assert.ok(keys.length >= 1);
+ assert.deepEqual(results[0]!.receipt, results[1]!.receipt);
+});
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..83a0e74
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "compilerOptions": {
+ "target": "ES2023",
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "jsx": "react-jsx",
+ "strict": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "resolveJsonModule": true,
+ "noUncheckedIndexedAccess": true,
+ "types": ["node"]
+ },
+ "include": ["server", "web", "shared", "tests", "examples"]
+}
diff --git a/tsconfig.server.json b/tsconfig.server.json
new file mode 100644
index 0000000..e9d8e60
--- /dev/null
+++ b/tsconfig.server.json
@@ -0,0 +1,11 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "rootDir": ".",
+ "outDir": "dist",
+ "declaration": true,
+ "module": "NodeNext",
+ "moduleResolution": "NodeNext"
+ },
+ "include": ["server", "shared"]
+}
diff --git a/web/PaymentSheet.tsx b/web/PaymentSheet.tsx
new file mode 100644
index 0000000..acd27e8
--- /dev/null
+++ b/web/PaymentSheet.tsx
@@ -0,0 +1,547 @@
+import { useEffect, useRef, useState } from 'react';
+import type { App } from '@modelcontextprotocol/ext-apps';
+import { money, totalAmount, type PaymentView } from '../shared/contracts.js';
+import { invoke, latestPayment } from './bridge.js';
+
+function Icon({
+ name,
+ className = '',
+}: {
+ name: 'lock' | 'bag' | 'wallet' | 'close' | 'arrow' | 'check';
+ className?: string;
+}) {
+ const paths = {
+ lock: (
+ <>
+
+
+ >
+ ),
+ bag: (
+ <>
+
+
+ >
+ ),
+ wallet: (
+ <>
+
+
+ >
+ ),
+ close: ,
+ arrow: ,
+ check: ,
+ };
+ return (
+
+ {paths[name]}
+
+ );
+}
+
+function DemoDialog({
+ view,
+ onFinish,
+ onCancel,
+}: {
+ view: PaymentView;
+ onFinish: (outcome: 'success' | 'declined') => Promise;
+ onCancel: () => void;
+}) {
+ const dialog = useRef(null);
+ const [busy, setBusy] = useState(false);
+ const [error, setError] = useState('');
+ useEffect(() => {
+ dialog.current?.showModal();
+ }, []);
+ const complete = async (outcome: 'success' | 'declined') => {
+ if (busy) return;
+ setBusy(true);
+ setError('');
+ try {
+ await onFinish(outcome);
+ } catch {
+ setError('The demo could not finish. Try again.');
+ setBusy(false);
+ }
+ };
+ return (
+ {
+ e.preventDefault();
+ if (!busy) onCancel();
+ }}
+ >
+
+
+
+ L Lucci Pay
+
+
+
+
+
+ DEMO PAYMENT SHEET
+ Confirm demo payment
+
+ No money moves. No card is needed.
+
+
+
+
+
+
+ Demo payment method
+ For preview only
+
+
+
+ {view.merchant.name}
+ {money(totalAmount(view.session), view.session.currency)}
+
+ {error && (
+
+ {error}
+
+ )}
+ void complete('success')}>
+ {busy ? (
+ <>
+
+ Confirmingā¦
+ >
+ ) : (
+ 'Confirm demo payment'
+ )}
+
+ void complete('declined')}>
+ Preview a declined payment
+
+
+ );
+}
+
+export function PaymentSheet({
+ initial,
+ app,
+ onNewDemo,
+}: {
+ initial: PaymentView;
+ app: App | null;
+ onNewDemo?: () => void;
+}) {
+ const [view, setView] = useState(initial);
+ const [busy, setBusy] = useState(false);
+ const [error, setError] = useState('');
+ const [notice, setNotice] = useState('');
+ const [showDemo, setShowDemo] = useState(false);
+ const [now, setNow] = useState(Date.now());
+ const paying = useRef(false);
+ const mounted = useRef(true);
+ const activeRequest = useRef(initial.request_id);
+ const dismissRef = useRef(null);
+ const receiptHeading = useRef(null);
+ useEffect(() => {
+ mounted.current = true;
+ return () => {
+ mounted.current = false;
+ };
+ }, []);
+ useEffect(() => {
+ activeRequest.current = initial.request_id;
+ setView((current) => latestPayment(current, initial));
+ setError('');
+ setNotice('');
+ }, [initial]);
+ useEffect(() => {
+ const timeout = setTimeout(
+ () => setNow(Date.now()),
+ Math.max(0, Date.parse(view.expires_at) - Date.now()) + 50,
+ );
+ return () => clearTimeout(timeout);
+ }, [view.expires_at]);
+ useEffect(() => {
+ if (view.status === 'paid') receiptHeading.current?.focus();
+ }, [view.status]);
+
+ const update = (next: PaymentView) => {
+ if (mounted.current && next.request_id === activeRequest.current)
+ setView((current) => latestPayment(current, next));
+ };
+ const refresh = async () => {
+ const next = await invoke(app, 'get_payment_status', view.request_id);
+ update(next);
+ return next;
+ };
+ useEffect(() => {
+ if (view.status !== 'processing') return;
+ let stopped = false;
+ let polls = 0;
+ let timer: ReturnType;
+ const poll = async () => {
+ try {
+ const next = await invoke(app, 'get_payment_status', view.request_id);
+ if (!stopped) update(next);
+ if (next.status !== 'processing') return;
+ } catch {
+ /* Leave a manual check available if connectivity is lost. */
+ }
+ if (!stopped && ++polls < 20) timer = setTimeout(() => void poll(), 2000);
+ };
+ timer = setTimeout(() => void poll(), 1500);
+ return () => {
+ stopped = true;
+ clearTimeout(timer);
+ };
+ }, [view.request_id, view.status, app]);
+
+ const demo = view.mode === 'demo';
+ const expired =
+ view.status === 'expired' ||
+ (['pending', 'failed'].includes(view.status) && now >= Date.parse(view.expires_at));
+ const native = Boolean(view.checkout_enabled && window.openai?.requestCheckout);
+ const total = money(totalAmount(view.session), view.session.currency);
+ const payable = ['pending', 'failed'].includes(view.status) && !expired && (demo || native);
+ const dismissDemo = () => {
+ setShowDemo(false);
+ setNotice('Payment cancelled. You have not been charged.');
+ dismissRef.current?.focus();
+ };
+
+ const pay = async () => {
+ if (paying.current || !payable) return;
+ if (demo) {
+ setShowDemo(true);
+ return;
+ }
+ paying.current = true;
+ setBusy(true);
+ setError('');
+ setNotice('');
+ try {
+ // Called directly from the user gesture. The host owns Apple Pay, card
+ // entry, biometrics and 3DS. Never render a substitute credential form.
+ await window.openai!.requestCheckout!(view.session);
+ const next = await refresh();
+ if (next.status !== 'paid')
+ setNotice('Your payment is being confirmed. Check its status before trying again.');
+ } catch {
+ try {
+ const next = await refresh();
+ if (next.status === 'failed')
+ setError('Payment wasnāt completed. You can choose another payment method.');
+ else if (next.status === 'pending')
+ setNotice('Payment sheet closed. You have not been charged.');
+ else if (next.status !== 'paid')
+ setNotice('Weāre checking your payment. Please donāt start another request.');
+ } catch {
+ setError('We couldnāt confirm the payment status. Check again before retrying.');
+ }
+ } finally {
+ paying.current = false;
+ if (mounted.current) setBusy(false);
+ }
+ };
+ const cancel = async () => {
+ if (busy || paying.current) return;
+ setBusy(true);
+ setError('');
+ try {
+ update(await invoke(app, 'cancel_payment_request', view.request_id));
+ } catch {
+ setError('This request could not be cancelled. Check its payment status.');
+ } finally {
+ setBusy(false);
+ }
+ };
+ const check = async () => {
+ if (busy) return;
+ setBusy(true);
+ setError('');
+ try {
+ await refresh();
+ } catch {
+ setError('Couldnāt check the payment. Try again.');
+ } finally {
+ setBusy(false);
+ }
+ };
+ const paid = view.status === 'paid' && view.receipt;
+ return (
+
+
+
+ L Lucci Pay
+
+
+ {demo ? (
+ 'Demo'
+ ) : view.mode === 'test' ? (
+ 'Test payment'
+ ) : (
+ <>
+
+ Secure payment
+ >
+ )}
+
+
+ {paid ? (
+
+
+
+
+
+ {demo
+ ? 'Demo complete'
+ : view.mode === 'test'
+ ? 'Test payment complete'
+ : 'Payment complete'}
+
+
+ {view.mode !== 'live'
+ ? 'No money was charged.'
+ : `Your payment to ${view.merchant.name} is confirmed.`}
+
+
{total}
+
{view.merchant.name}
+
+
+
Status
+ {demo ? 'Simulated' : view.mode === 'test' ? 'Test payment' : 'Paid'}
+
+
+
Reference
+ {view.receipt!.id.slice(-12).toUpperCase()}
+
+
+ {onNewDemo ? (
+
+ Try another demo
+
+ ) : window.openai?.requestClose ? (
+
window.openai?.requestClose?.()}>
+ Done
+
+ ) : (
+
You can continue your conversation.
+ )}
+
+ ) : view.status === 'cancelled' || expired ? (
+
+
+
+
+
{expired ? 'Request expired' : 'Request cancelled'}
+
+ {expired
+ ? 'Ask the requesting app for a new payment request.'
+ : 'You have not been charged.'}
+
+ {onNewDemo && (
+
+ Start a new demo
+
+ )}
+
+ ) : (
+ <>
+
+
+ {view.merchant.name.charAt(0)}
+
+
PAYMENT REQUEST
+
{view.merchant.name}
+
{view.merchant.domain}
+
+ {total}
+ {view.session.currency}
+
+
+
+ {view.session.line_items.map((item) => (
+
+
+
+
+
+ {item.name}
+ Qty {item.item.quantity}
+
+
{money(item.subtotal, view.session.currency)}
+
+ ))}
+ {view.session.totals
+ .filter((t) => t.type === 'tax' && t.amount > 0)
+ .map((t) => (
+
+ {t.display_text}
+ {money(t.amount, view.session.currency)}
+
+ ))}
+ {view.session.line_items.some((i) => i.discount > 0) && (
+
+ Discount included
+
+ ā
+ {money(
+ view.session.line_items.reduce((n, i) => n + i.discount, 0),
+ view.session.currency,
+ )}
+
+
+ )}
+
+ Total due
+ {total}
+
+
+
+
+
+ {view.session.fulfillment_options[0]?.title}
+ {view.session.fulfillment_options[0]?.subtitle}
+
+
+
+
+
+
+
+ {demo ? 'Try the payment experience' : 'Apple Pay & cards'}
+
+ {demo ? 'A preview. No money moves.' : 'Choose in the secure payment sheet'}
+
+
+ {!demo &&
}
+
+
+ {error && (
+
+ {error}
+
+ )}
+ {notice &&
{notice}
}
+ {view.status === 'processing' && (
+
Your payment is being confirmed. Please donāt pay again.
+ )}
+ {!demo && !native && view.status !== 'processing' && (
+
In-chat payments arenāt available for this request yet.
+ )}
+ {view.status === 'failed' && !error && (
+
Payment declined. You can try again.
+ )}
+
+ {view.status === 'processing' ? (
+ void check()}>
+ {busy ? (
+ <>
+
+ Checkingā¦
+ >
+ ) : (
+ 'Check payment status'
+ )}
+
+ ) : (
+ void pay()}
+ disabled={!payable || busy}
+ >
+ {busy ? (
+ <>
+
+ Opening payment sheetā¦
+ >
+ ) : demo ? (
+ <>
+
+ Try demo payment
+ >
+ ) : native ? (
+ <>
+
+ Pay {total}
+ >
+ ) : (
+ 'Payment unavailable'
+ )}
+
+ )}
+ {error && view.status !== 'processing' && (
+ void check()} disabled={busy}>
+ Check payment status
+
+ )}
+ void cancel()}
+ disabled={busy || view.status === 'processing'}
+ >
+ Cancel request
+
+
+
+
+ {demo
+ ? 'Preview only Ā· no payment details collected'
+ : 'Your payment details stay in the payment sheet'}
+
+
+ {!demo && (
+
+ {view.session.links.map((link) => (
+
+ {{
+ terms_of_use: 'Terms',
+ privacy_policy: 'Privacy',
+ refund_policy: 'Refunds',
+ support_url: 'Support',
+ }[link.type] ?? link.type}
+
+ ))}
+
+ )}
+ >
+ )}
+ {showDemo && (
+ {
+ const next = await invoke(app, 'simulate_payment', view.request_id, outcome);
+ update(next);
+ setShowDemo(false);
+ setNotice('');
+ setError(
+ outcome === 'declined'
+ ? 'Demo declined. No money was charged. Try again when youāre ready.'
+ : '',
+ );
+ }}
+ />
+ )}
+
+ );
+}
diff --git a/web/bridge.ts b/web/bridge.ts
new file mode 100644
index 0000000..b3811a2
--- /dev/null
+++ b/web/bridge.ts
@@ -0,0 +1,50 @@
+import type { App } from '@modelcontextprotocol/ext-apps';
+import type { PaymentView } from '../shared/contracts.js';
+
+export type PaymentAction = 'get_payment_status' | 'cancel_payment_request' | 'simulate_payment';
+export function latestPayment(current: PaymentView, next: PaymentView): PaymentView {
+ return next.request_id === current.request_id && next.state_version >= current.state_version
+ ? next
+ : current;
+}
+export function extractPayment(result: unknown): PaymentView | undefined {
+ if (!result || typeof result !== 'object') return undefined;
+ const record = result as Record;
+ const content = (record.structuredContent ?? record) as { payment?: PaymentView };
+ if (record.isError || !content.payment?.session?.line_items || !content.payment?.request_id)
+ return undefined;
+ return content.payment;
+}
+export async function invoke(
+ app: App | null,
+ action: PaymentAction,
+ id: string,
+ outcome?: string,
+): Promise {
+ let result: unknown;
+ if (window.parent === window) {
+ const path =
+ action === 'get_payment_status'
+ ? ''
+ : action === 'cancel_payment_request'
+ ? '/cancel'
+ : '/simulate';
+ const response = await fetch(`/api/demo/${encodeURIComponent(id)}${path}`, {
+ method: path ? 'POST' : 'GET',
+ headers: { 'Content-Type': 'application/json' },
+ body: path ? JSON.stringify(outcome ? { outcome } : {}) : undefined,
+ });
+ if (!response.ok) throw new Error('Could not update this request. Please try again.');
+ result = await response.json();
+ } else {
+ if (!app) throw new Error('The payment request is still connecting.');
+ // callServerTool is the ext-apps wrapper for the standard tools/call bridge.
+ result = await app.callServerTool({
+ name: action,
+ arguments: { request_id: id, ...(outcome ? { outcome } : {}) },
+ });
+ }
+ const payment = extractPayment(result);
+ if (!payment) throw new Error('Could not confirm the payment status. Please check again.');
+ return payment;
+}
diff --git a/web/host.d.ts b/web/host.d.ts
new file mode 100644
index 0000000..e589d6d
--- /dev/null
+++ b/web/host.d.ts
@@ -0,0 +1,10 @@
+import type { CheckoutSession } from '../shared/contracts.js';
+declare global {
+ interface Window {
+ openai?: {
+ requestCheckout?: (session: CheckoutSession) => Promise;
+ requestClose?: () => void;
+ };
+ }
+}
+export {};
diff --git a/web/index.tsx b/web/index.tsx
new file mode 100644
index 0000000..6a2e5ac
--- /dev/null
+++ b/web/index.tsx
@@ -0,0 +1,119 @@
+import { App } from '@modelcontextprotocol/ext-apps';
+import { createRoot } from 'react-dom/client';
+import { useEffect, useState } from 'react';
+import type { PaymentView } from '../shared/contracts.js';
+import { extractPayment } from './bridge.js';
+import { PaymentSheet } from './PaymentSheet.js';
+import './styles.css';
+
+function EmbeddedPayment() {
+ const [payment, setPayment] = useState();
+ const [app, setApp] = useState(null);
+ const [error, setError] = useState(false);
+ useEffect(() => {
+ const bridge = new App({ name: 'Lucci Pay', version: '0.1.0' }, {});
+ let disposed = false;
+ bridge.ontoolresult = (result) => {
+ const next = extractPayment(result);
+ if (!disposed && next) setPayment(next);
+ };
+ bridge.onhostcontextchanged = (context) => {
+ if (context.theme) document.documentElement.dataset.theme = context.theme;
+ };
+ void bridge
+ .connect()
+ .then(() => {
+ if (disposed) return;
+ setApp(bridge);
+ const theme = bridge.getHostContext()?.theme;
+ if (theme) document.documentElement.dataset.theme = theme;
+ })
+ .catch(() => {
+ if (!disposed) setError(true);
+ });
+ return () => {
+ disposed = true;
+ void bridge.close();
+ };
+ }, []);
+ if (error)
+ return (
+
+ The payment request couldnāt connect. Open it again to retry.
+
+ );
+ if (!payment)
+ return (
+
+
+ Opening payment requestā¦
+
+ );
+ return ;
+}
+
+function Preview() {
+ const [payment, setPayment] = useState();
+ const [error, setError] = useState('');
+ const load = async () => {
+ setError('');
+ try {
+ const result = await fetch('/api/demo', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: '{}',
+ });
+ const next = extractPayment(await result.json());
+ if (!result.ok || !next) throw new Error();
+ setPayment(next);
+ } catch {
+ setError('Couldnāt open the preview. Try again.');
+ }
+ };
+ useEffect(() => {
+ void load();
+ }, []);
+ return (
+
+
+
+ luccipay
+
+ INTERACTIVE PREVIEW
+
+
+
PAYMENTS, IN THE CONVERSATION
+
+ A little less between
+
+ yes and done.
+
+
Review. Confirm. Keep going.
+
+ {payment ? (
+ void load()}
+ />
+ ) : (
+
+ {error || 'Opening the previewā¦'}
+
+ )}
+ {error && (
+ void load()}>
+ Retry preview
+
+ )}
+
+ An independent Lucci project. Demo payments do not move money.
+
+
+ );
+}
+
+createRoot(document.getElementById('root')!).render(
+ window.parent === window ? : ,
+);
diff --git a/web/styles.css b/web/styles.css
new file mode 100644
index 0000000..8416d7e
--- /dev/null
+++ b/web/styles.css
@@ -0,0 +1,758 @@
+:root {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
+ color: #1d1d1f;
+ background: transparent;
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ --ink: #1d1d1f;
+ --muted: #737378;
+ --surface: #fff;
+ --group: #f5f5f7;
+ --line: #e8e8ec;
+ --button: #18191b;
+ --button-ink: #fff;
+ --green: #19834d;
+}
+* {
+ box-sizing: border-box;
+}
+body {
+ margin: 0;
+}
+button,
+a {
+ -webkit-tap-highlight-color: transparent;
+}
+button {
+ font: inherit;
+ cursor: pointer;
+}
+button:disabled {
+ cursor: default;
+ opacity: 0.5;
+}
+button:focus-visible,
+a:focus-visible {
+ outline: 3px solid #4588ec;
+ outline-offset: 4px;
+}
+svg {
+ width: 22px;
+ height: 22px;
+ flex-shrink: 0;
+}
+h1,
+h2,
+p {
+ margin: 0;
+}
+h1 {
+ font-size: 25px;
+ font-weight: 650;
+ letter-spacing: -0.85px;
+ overflow-wrap: anywhere;
+}
+strong {
+ font-weight: 600;
+}
+button {
+ touch-action: manipulation;
+}
+.muted {
+ font-size: 14px;
+ line-height: 1.6;
+ color: var(--muted);
+}
+.payment-sheet {
+ width: 100%;
+ max-width: 440px;
+ margin: 0 auto;
+ background: var(--surface);
+ color: var(--ink);
+ border: 1px solid var(--line);
+ border-radius: 28px;
+ padding: 23px 25px 18px;
+ overflow: hidden;
+ box-shadow: 0 14px 46px #20202008;
+ animation: arrive 0.42s cubic-bezier(0.16, 1, 0.3, 1);
+}
+.sheet-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ margin-bottom: 29px;
+}
+.brand {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ font-size: 15px;
+ letter-spacing: -0.45px;
+ font-weight: 650;
+}
+.brand-mark {
+ height: 23px;
+ width: 23px;
+ display: grid;
+ place-items: center;
+ color: var(--button-ink);
+ background: var(--button);
+ border-radius: 7px;
+ font-family: Georgia, serif;
+ font-size: 17px;
+ font-style: italic;
+ font-weight: 500;
+}
+.mode {
+ font-size: 11px;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ gap: 3px;
+ white-space: nowrap;
+}
+.mode svg {
+ width: 13px;
+ height: 13px;
+}
+.mode.demo,
+.mode.test {
+ border: 1px solid var(--line);
+ border-radius: 6px;
+ padding: 4px 7px;
+ color: var(--muted);
+ background: var(--group);
+}
+.merchant-hero {
+ text-align: center;
+}
+.merchant-monogram {
+ margin: 0 auto 18px;
+ background: #eae9e3;
+ color: #575b50;
+ height: 48px;
+ width: 48px;
+ display: grid;
+ place-items: center;
+ border-radius: 15px;
+ font-family: Georgia, serif;
+ font-size: 28px;
+}
+.eyebrow {
+ font-size: 9px;
+ letter-spacing: 1.6px;
+ font-weight: 600;
+ color: var(--muted);
+}
+.merchant-hero .eyebrow {
+ margin-bottom: 6px;
+}
+.merchant-domain {
+ font-size: 12px;
+ color: var(--muted);
+ margin-top: 5px;
+ overflow-wrap: anywhere;
+}
+.amount {
+ font-size: 52px;
+ letter-spacing: -2.8px;
+ line-height: 1.15;
+ font-weight: 600;
+ margin: 20px 0 25px;
+ font-variant-numeric: tabular-nums;
+}
+.amount > span {
+ font-size: 11px;
+ letter-spacing: 0.7px;
+ font-weight: 500;
+ display: block;
+ color: var(--muted);
+ margin-top: 6px;
+}
+.details-group {
+ background: var(--group);
+ border-radius: 16px;
+ overflow: hidden;
+ padding: 0 15px;
+}
+.item-row {
+ display: flex;
+ gap: 11px;
+ align-items: center;
+ padding: 15px 0;
+}
+.item-icon {
+ width: 35px;
+ height: 39px;
+ border-radius: 10px;
+ background: var(--surface);
+ display: grid;
+ place-items: center;
+ color: #777b70;
+ flex-shrink: 0;
+}
+.item-icon svg {
+ width: 20px;
+ height: 20px;
+}
+.item-description {
+ flex: 1;
+ min-width: 0;
+}
+.item-description strong {
+ display: block;
+ font-size: 12px;
+ line-height: 1.45;
+ overflow-wrap: anywhere;
+}
+.item-description span {
+ font-size: 11px;
+ color: var(--muted);
+ margin-top: 4px;
+ display: block;
+}
+.item-price {
+ font-size: 12px;
+ font-variant-numeric: tabular-nums;
+}
+.detail-row {
+ border-top: 1px solid var(--line);
+ display: flex;
+ justify-content: space-between;
+ gap: 12px;
+ font-size: 12px;
+ padding: 11px 0;
+ color: var(--muted);
+}
+.total-row {
+ color: var(--ink);
+ font-size: 13px;
+ padding: 13px 0;
+}
+.fulfillment {
+ display: flex;
+ gap: 10px;
+ align-items: center;
+ padding: 14px 3px 15px;
+ color: var(--muted);
+}
+.fulfillment svg {
+ height: 18px;
+ width: 18px;
+}
+.fulfillment strong {
+ display: block;
+ font-size: 11px;
+ color: var(--ink);
+ font-weight: 500;
+}
+.fulfillment span {
+ display: block;
+ margin-top: 4px;
+ font-size: 10px;
+ line-height: 1.5;
+ overflow-wrap: anywhere;
+}
+.method-row {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ border-top: 1px solid var(--line);
+ padding: 16px 0 4px;
+}
+.method-row > div {
+ flex: 1;
+}
+.method-row strong {
+ display: block;
+ font-size: 12px;
+}
+.method-row span:not(.wallet-icon) {
+ display: block;
+ color: var(--muted);
+ font-size: 10px;
+ margin-top: 4px;
+}
+.wallet-icon {
+ height: 32px;
+ width: 37px;
+ border-radius: 9px;
+ display: grid;
+ place-items: center;
+ background: var(--group);
+}
+.wallet-icon svg {
+ height: 21px;
+ width: 21px;
+}
+.chevron {
+ color: #a3a3a7;
+ width: 13px;
+ height: 13px;
+}
+.message-area:empty {
+ height: 18px;
+}
+.error,
+.notice {
+ font-size: 12px;
+ line-height: 1.5;
+ padding: 11px 0 13px;
+}
+.error {
+ color: #bb3939;
+}
+.notice {
+ color: var(--muted);
+}
+.message-area > * + * {
+ padding-top: 0;
+}
+.primary {
+ width: 100%;
+ border: 0;
+ border-radius: 13px;
+ background: var(--button);
+ color: var(--button-ink);
+ min-height: 49px;
+ font-size: 13px;
+ font-weight: 550;
+ letter-spacing: -0.1px;
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ justify-content: center;
+ padding: 13px 12px;
+ transition:
+ transform 0.15s,
+ opacity 0.15s;
+}
+.primary svg {
+ width: 17px;
+ height: 17px;
+}
+.primary:not(:disabled):active {
+ transform: scale(0.982);
+}
+.primary:not(:disabled):hover {
+ opacity: 0.9;
+}
+.text-button {
+ display: block;
+ margin: 0 auto;
+ border: 0;
+ background: transparent;
+ color: var(--muted);
+ font-size: 12px;
+ min-height: 44px;
+ padding: 10px 16px;
+}
+.text-button:hover {
+ color: var(--ink);
+}
+.payment-footer {
+ display: flex;
+ gap: 4px;
+ align-items: center;
+ justify-content: center;
+ font-size: 9px;
+ color: var(--muted);
+ line-height: 1.5;
+ text-align: center;
+}
+.payment-footer svg {
+ width: 11px;
+ height: 11px;
+}
+.legal {
+ display: flex;
+ gap: 15px;
+ justify-content: center;
+ flex-wrap: wrap;
+ margin-top: 13px;
+}
+.legal a {
+ color: var(--muted);
+ font-size: 10px;
+ min-height: 26px;
+ display: flex;
+ align-items: center;
+ text-decoration: none;
+}
+.legal a:hover {
+ text-decoration: underline;
+}
+.receipt-panel {
+ text-align: center;
+ padding: 12px 0 10px;
+}
+.success-circle {
+ height: 67px;
+ width: 67px;
+ border: 1.5px solid #70af8b;
+ background: #edf7f0;
+ color: var(--green);
+ border-radius: 50%;
+ display: grid;
+ place-items: center;
+ margin: 13px auto 22px;
+ animation: arrive 0.5s ease;
+}
+.success-circle svg {
+ width: 31px;
+ height: 31px;
+}
+.receipt-panel h1 {
+ font-size: 24px;
+ margin-bottom: 8px;
+}
+.receipt-panel h1:focus {
+ outline: none;
+}
+.receipt-amount {
+ font-size: 43px;
+ letter-spacing: -2px;
+ font-weight: 600;
+ margin: 29px 0 5px;
+}
+.receipt-merchant {
+ font-size: 14px;
+ color: var(--muted);
+}
+.receipt-details {
+ font-size: 12px;
+ text-align: left;
+ background: var(--group);
+ border-radius: 14px;
+ padding: 3px 15px;
+ margin: 25px 0;
+}
+.receipt-details div {
+ display: flex;
+ justify-content: space-between;
+ padding: 14px 0;
+ gap: 10px;
+}
+.receipt-details div + div {
+ border-top: 1px solid var(--line);
+}
+dt {
+ color: var(--muted);
+}
+dd {
+ margin: 0;
+ font-weight: 550;
+ font-variant-numeric: tabular-nums;
+}
+.footnote {
+ font-size: 12px;
+ color: var(--muted);
+ padding: 10px;
+}
+.closed-panel {
+ text-align: center;
+ padding: 38px 0 25px;
+}
+.closed-panel p {
+ margin: 12px 0 28px;
+}
+.closed-icon {
+ display: grid;
+ place-items: center;
+ background: var(--group);
+ width: 55px;
+ height: 55px;
+ border-radius: 50%;
+ margin: 0 auto 20px;
+ color: var(--muted);
+}
+.demo-dialog {
+ border: 1px solid var(--line);
+ color: var(--ink);
+ background: var(--surface);
+ width: calc(100% - 24px);
+ max-width: 410px;
+ border-radius: 27px;
+ padding: 15px 24px 11px;
+ box-shadow: 0 30px 120px #0002;
+ animation: arrive 0.3s ease;
+}
+.demo-dialog::backdrop {
+ background: #1f222652;
+ backdrop-filter: blur(4px);
+}
+.dialog-handle {
+ width: 33px;
+ height: 4px;
+ border-radius: 20px;
+ background: #d0d0d4;
+ margin: 0 auto 15px;
+}
+.demo-dialog .sheet-header {
+ margin-bottom: 25px;
+}
+.icon-button {
+ height: 30px;
+ width: 30px;
+ border: 0;
+ border-radius: 50%;
+ display: grid;
+ place-items: center;
+ background: var(--group);
+ color: var(--muted);
+}
+.icon-button svg {
+ height: 18px;
+ width: 18px;
+}
+.demo-dialog h2 {
+ font-size: 21px;
+ letter-spacing: -0.65px;
+ margin: 8px 0;
+}
+.demo-dialog .muted {
+ font-size: 12px;
+}
+.demo-method {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ background: var(--group);
+ border-radius: 14px;
+ padding: 16px;
+ margin: 23px 0;
+}
+.card-symbol {
+ height: 37px;
+ width: 55px;
+ color: #f1f2f3;
+ background: #555c59;
+ border-radius: 7px;
+ display: grid;
+ place-items: center;
+}
+.demo-method strong {
+ font-size: 12px;
+}
+.demo-method span:not(.card-symbol) {
+ font-size: 11px;
+ display: block;
+ margin-top: 4px;
+ color: var(--muted);
+}
+.dialog-total {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 15px;
+ font-size: 13px;
+ padding: 0 0 20px;
+}
+.dialog-total strong {
+ font-size: 23px;
+ letter-spacing: -0.6px;
+}
+.spinner {
+ width: 16px;
+ height: 16px;
+ border: 2px solid currentColor;
+ border-right-color: transparent;
+ border-radius: 50%;
+ display: inline-block;
+ animation: spin 0.8s linear infinite;
+}
+.empty-state {
+ min-height: 180px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ font-size: 13px;
+ color: var(--muted);
+ padding: 24px;
+}
+.preview-page {
+ min-height: 100vh;
+ background: #f4f3ef;
+ padding: 25px 5vw 28px;
+}
+.preview-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ max-width: 1440px;
+ margin: auto;
+}
+.preview-wordmark {
+ font-family: Georgia, serif;
+ font-size: 30px;
+ letter-spacing: -2px;
+}
+.preview-wordmark span {
+ font-family: -apple-system, BlinkMacSystemFont, sans-serif;
+ font-size: 14px;
+ letter-spacing: -0.3px;
+ margin-left: 7px;
+ color: #65655f;
+ font-weight: 450;
+}
+.preview-label {
+ font-size: 9px;
+ letter-spacing: 1.6px;
+ color: #797970;
+}
+.preview-intro {
+ text-align: center;
+ margin: 34px 0 27px;
+}
+.preview-intro h2 {
+ font-size: 36px;
+ font-weight: 500;
+ line-height: 1.08;
+ letter-spacing: -1.8px;
+ margin: 12px 0;
+}
+.preview-intro p {
+ color: #86867d;
+ font-size: 12px;
+}
+.preview-caption {
+ text-align: center;
+ margin: 22px 0 0;
+ color: #8b8b83;
+ font-size: 10px;
+ line-height: 1.5;
+}
+.preview-page .payment-sheet {
+ box-shadow: 0 25px 65px #3737290c;
+}
+:root[data-theme='dark'] {
+ --ink: #f4f4f5;
+ --muted: #a2a2aa;
+ --surface: #212124;
+ --group: #2c2c30;
+ --line: #3c3c42;
+ --button: #f4f4f6;
+ --button-ink: #18191b;
+ --green: #64ba8c;
+}
+.payment-sheet .amount,
+.dialog-total strong {
+ overflow-wrap: anywhere;
+}
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme='light']) {
+ --ink: #f4f4f5;
+ --muted: #a2a2aa;
+ --surface: #212124;
+ --group: #2c2c30;
+ --line: #3c3c42;
+ --button: #f4f4f6;
+ --button-ink: #18191b;
+ --green: #64ba8c;
+ }
+ .preview-page {
+ background: #161719;
+ color: #f4f4f5;
+ }
+ .preview-wordmark span,
+ .preview-caption,
+ .preview-intro p {
+ color: #a2a2a6;
+ }
+}
+@media (min-width: 1050px) {
+ .preview-page {
+ position: relative;
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ align-items: center;
+ padding: 100px max(7vw, 45px) 65px;
+ gap: 45px;
+ }
+ .preview-header {
+ position: absolute;
+ left: 5vw;
+ right: 5vw;
+ top: 25px;
+ }
+ .preview-intro {
+ text-align: left;
+ justify-self: center;
+ margin: 0;
+ }
+ .preview-intro h2 {
+ font-size: 58px;
+ letter-spacing: -3px;
+ margin: 20px 0;
+ }
+ .preview-intro p {
+ font-size: 16px;
+ }
+ .preview-page .payment-sheet {
+ margin: 0;
+ justify-self: center;
+ }
+ .preview-caption {
+ position: absolute;
+ bottom: 23px;
+ left: 0;
+ right: 0;
+ }
+}
+@media (max-width: 440px) {
+ .payment-sheet {
+ padding: 20px 20px 17px;
+ border-radius: 23px;
+ }
+ .preview-page {
+ padding: 18px 14px 24px;
+ }
+ .preview-intro {
+ margin: 27px 0 22px;
+ }
+ .preview-intro h2 {
+ font-size: 30px;
+ }
+ .amount {
+ font-size: 46px;
+ }
+ .merchant-hero h1 {
+ font-size: 24px;
+ }
+ .preview-label {
+ font-size: 8px;
+ }
+ .item-row {
+ gap: 8px;
+ }
+ .item-description strong {
+ font-size: 11px;
+ }
+}
+@media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ animation: none !important;
+ transition: none !important;
+ scroll-behavior: auto !important;
+ }
+}
+@keyframes arrive {
+ from {
+ opacity: 0;
+ transform: translateY(12px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}