@clawhub-kaudata-418c4f5924
Locate Mercedes-Benz dealerships and search for new vehicle inventory across the USA using zip code-based filters and detailed specifications.
# MBUSA Dealer & Inventory Skill
This OpenClaw skill enables AI agents to query Mercedes-Benz USA (MBUSA.com) data. This is not officially from MBUSA. It allows the agent to find local dealerships in USA and search for live vehicle inventory across New, Pre-Owned, and Certified Pre-Owned (CPO) categories.
## Tools Included
### 1. `get_mbusa_dealers`
Allows the agent to find official Mercedes-Benz dealerships using a US zip code.
* **Primary Use Case:** "Find a Mercedes dealer near 10019" or "Give me the service department number for the dealer in Bayside, NY."
* **Data Returned:** Dealership name, primary address, distance, main phone, service phone, website URL, and service scheduling URL.
### 2. `get_mbusa_inventory`
Allows the agent to search for new vehicle inventory on dealership lots near a specific zip code.
* **Primary Use Case:** "Are there any new EQS sedans under $100,000 near me?" or "Find a 2026 GLC within 50 miles of 30097."
* **Capabilities:** Supports strict enum-based filtering by model, class, body style, brand, interior/exterior colors, fuel type, passenger capacity, highway fuel economy, price range, year, and search radius.
* **Data Returned:** VIN, Stock ID, year, model name, MSRP, engine type, exterior/interior colors, the holding dealership's name, distance, and direct actionable URLs for images and dealer websites.
### 3. `get_mbusa_used_inventory`
Allows the agent to search for **CERTIFIED PRE-OWNED** and **USED** vehicle inventory.
* **Primary Use Case:** "Show me CPO C-Class sedans near 30097 with under 50,000 miles."
* **Capabilities:** Inherits all filters from the New search, but adds a required `invType` parameter (`cpo` or `pre`), expands the `year` search back to 2020, and supports strict enum filtering by `mileage`.
## Configuration & Output
Ensure the `schema.json` file is loaded into your agent's context window. The agent has been explicitly instructed to format the actionable URLs (Image, Dealer Website, Service Scheduling) as clickable Markdown links when responding to the user.
# MBUSA Dealer & Inventory Skill
This OpenClaw skill enables AI agents to query official Mercedes-Benz USA (MBUSA) data. It allows the agent to find local dealerships and search for live vehicle inventory across New, Pre-Owned, and Certified Pre-Owned (CPO) categories.
## Tools Included
### 1. `get_mbusa_dealers`
Allows the agent to find official Mercedes-Benz dealerships using a US zip code.
* **Primary Use Case:** "Find a Mercedes dealer near 10019" or "Give me the service department number for the dealer in Bayside, NY."
* **Data Returned:** Dealership name, primary address, distance, main phone, service phone, website URL, service scheduling URL, and a direct Google Maps URL.
### 2. `get_mbusa_inventory`
Allows the agent to search for **NEW** vehicle inventory on dealership lots near a specific zip code.
* **Primary Use Case:** "Are there any new EQS sedans under $100,000 near me?"
* **Capabilities:** Supports strict enum-based filtering by model, class, body style, brand, interior/exterior colors, fuel type, passenger capacity, highway fuel economy, price range, year (2024-2026), and search radius.
* **Data Returned:** VIN, Stock ID, year, model name, MSRP, engine type, colors, holding dealership's name, distance, and direct URLs for images, dealer websites, and Google Maps.
### 3. `get_mbusa_used_inventory`
Allows the agent to search for **CERTIFIED PRE-OWNED** and **USED** vehicle inventory.
* **Primary Use Case:** "Show me CPO C-Class sedans near 30097 with under 50,000 miles."
* **Capabilities:** Inherits all filters from the New search, but adds a required `invType` parameter (`cpo` or `pre`), expands the `year` search back to 2020, and supports strict enum filtering by `mileage`.
## Configuration & Output
Ensure the `schema.json` file is loaded into your agent's context window. The agent has been explicitly instructed to format the actionable URLs (Image, Dealer Website, Service Scheduling, Google Maps) as clickable Markdown links.
FILE:README.md
# MBUSA Dealer & Inventory API / OpenClaw Skill
A multi-purpose Node.js application that serves as an LLM tool (OpenClaw Skill) and a standalone REST API for querying official Mercedes-Benz USA dealership locations and live vehicle inventory (New & Used).
## Project Structure
* `src/tool.js`: Core logic fetching data from MBUSA APIs. Handles strict MBUSA enum mapping, price formatting, and Google Maps URL generation.
* `schema.json`: Function definitions for OpenClaw LLM agent integration.
* `server.js`: Express wrapper exposing the tools as HTTP REST endpoints.
## Installation
1. Ensure you have Node.js (v18+ recommended) installed.
2. Clone this repository and install the Express dependency:
\`\`\`bash
npm install
\`\`\`
## Running the API Server
To start the standalone web server:
\`\`\`bash
npm start
\`\`\`
The server will listen on `http://localhost:3000`.
## API Documentation
### 1. Dealer Locator
Fetches a list of dealerships near a given zip code. Returns contact information and Google Maps links.
**Endpoint:** `GET /api/dealers`
**Parameters:**
* `zip` (string, **required**): 5-digit US zip code.
* `start` / `count` (integer, optional): Pagination settings.
---
### 2. NEW Vehicle Inventory Search
Fetches live new-vehicle inventory from dealerships near a given zip code.
**Endpoint:** `GET /api/inventory`
**Parameters:**
* `zip` (string, **required**): 5-digit US zip code.
* `distance` (integer, optional): Search radius (10, 25, 50, 100, 200, 500, 1000).
* `minPrice` / `maxPrice` (integer, optional): Filter by MSRP.
* `model` / `classId` / `bodyStyle` / `brand` (string, optional): Specific vehicle filters based on MBUSA enums.
* `exteriorColor` / `interiorColor` (string, optional): Filter by color enum (e.g., BLK, BLU).
* `year` (integer, optional): 2024, 2025, or 2026.
* `start` / `count` (integer, optional): Pagination settings.
---
### 3. USED & CPO Vehicle Inventory Search
Fetches live pre-owned and certified pre-owned inventory.
**Endpoint:** `GET /api/used-inventory`
**Parameters:**
* `zip` (string, **required**): 5-digit US zip code.
* `invType` (string, **required**): Must be `cpo` (Certified Pre-Owned) or `pre` (Standard Pre-Owned).
* `mileage` (string, optional): Filter by mileage band (e.g., `0_5000`, `10000_50000`).
* `year` (integer, optional): Extends range from 2020 to 2026.
* *(Inherits all other filtering parameters from the New Inventory API above)*.
**Example Request:**
\`\`\`text
GET http://localhost:3000/api/used-inventory?zip=10019&invType=cpo&classId=E&distance=50&mileage=0_5000
\`\`\`
FILE:package-lock.json
{
"name": "mbusa-skill",
"version": "0.0.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mbusa-skill",
"version": "0.0.3",
"license": "MIT",
"dependencies": {
"express": "^4.21.2"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=8.0.0"
}
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"license": "MIT",
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
},
"node_modules/body-parser": {
"version": "1.20.4",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
"integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
"license": "MIT",
"dependencies": {
"bytes": "~3.1.2",
"content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "~1.2.0",
"http-errors": "~2.0.1",
"iconv-lite": "~0.4.24",
"on-finished": "~2.4.1",
"qs": "~6.14.0",
"raw-body": "~2.5.3",
"type-is": "~1.6.18",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"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": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"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": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
"license": "MIT",
"dependencies": {
"safe-buffer": "5.2.1"
},
"engines": {
"node": ">= 0.6"
}
},
"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": ">= 0.6"
}
},
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
"license": "MIT"
},
"node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"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": ">= 0.8"
}
},
"node_modules/destroy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
"license": "MIT",
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"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"
},
"engines": {
"node": ">= 0.4"
}
},
"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/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"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/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/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"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": ">= 0.6"
}
},
"node_modules/express": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
"integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "~1.20.3",
"content-disposition": "~0.5.4",
"content-type": "~1.0.4",
"cookie": "~0.7.1",
"cookie-signature": "~1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "~1.3.1",
"fresh": "~0.5.2",
"http-errors": "~2.0.0",
"merge-descriptors": "1.0.3",
"methods": "~1.1.2",
"on-finished": "~2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "~0.1.12",
"proxy-addr": "~2.0.7",
"qs": "~6.14.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "~0.19.0",
"serve-static": "~1.16.2",
"setprototypeof": "1.2.0",
"statuses": "~2.0.1",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
},
"engines": {
"node": ">= 0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/finalhandler": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
"integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"on-finished": "~2.4.1",
"parseurl": "~1.3.3",
"statuses": "~2.0.2",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"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": ">= 0.6"
}
},
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"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": {
"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.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"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": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"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": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"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.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"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": {
"depd": "~2.0.0",
"inherits": "~2.0.4",
"setprototypeof": "~1.2.0",
"statuses": "~2.0.2",
"toidentifier": "~1.0.1"
},
"engines": {
"node": ">= 0.8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3"
},
"engines": {
"node": ">=0.10.0"
}
},
"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/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",
"engines": {
"node": ">= 0.10"
}
},
"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",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/merge-descriptors": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"license": "MIT",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"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",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"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"
}
},
"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",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/path-to-regexp": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
"integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
"license": "MIT"
},
"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.10"
}
},
"node_modules/qs": {
"version": "6.14.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
"integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/raw-body": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
"integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
"license": "MIT",
"dependencies": {
"bytes": "~3.1.2",
"http-errors": "~2.0.1",
"iconv-lite": "~0.4.24",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"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/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/send": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
"integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "~0.5.2",
"http-errors": "~2.0.1",
"mime": "1.6.0",
"ms": "2.1.3",
"on-finished": "~2.4.1",
"range-parser": "~1.2.1",
"statuses": "~2.0.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/send/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/serve-static": {
"version": "1.16.3",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
"integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
"license": "MIT",
"dependencies": {
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "~0.19.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"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/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3",
"side-channel-list": "^1.0.0",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"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": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.4"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"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": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"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",
"dependencies": {
"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": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"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": ">=0.6"
}
},
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"license": "MIT",
"dependencies": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
},
"engines": {
"node": ">= 0.6"
}
},
"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",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
"license": "MIT",
"engines": {
"node": ">= 0.4.0"
}
},
"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": ">= 0.8"
}
}
}
}
FILE:package.json
{
"name": "mbusa-skill",
"version": "0.0.3",
"description": "An OpenClaw skill and REST API to find Mercedes-Benz dealerships and live vehicle inventory.",
"main": "src/tool.js",
"scripts": {
"start": "node server.js"
},
"keywords": ["openclaw", "mbusa", "skill", "agent", "tool", "api"],
"author": "Kaushik Datta <[email protected]>",
"homepage": "https://github.com/yourusername/mbusa-dealer-skill#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/yourusername/mbusa-dealer-skill.git"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=8.0.0"
},
"license": "MIT",
"dependencies": {
"express": "^4.21.2"
}
}
FILE:schema.json
[
{
"name": "get_mbusa_dealers",
"description": "Searches for official Mercedes-Benz USA (MBUSA) dealerships based on a provided US zip code. Returns dealer info and a Google Maps URL. ALWAYS format the Google Maps URL as a Markdown link (e.g., [Get Directions](url)) so the user can easily open it in their maps app.",
"parameters": {
"type": "object",
"properties": {
"zip_code": {
"type": "string",
"description": "The 5-digit United States zip code."
},
"start": { "type": "integer", "default": 0 },
"count": { "type": "integer", "default": 10 }
},
"required": ["zip_code"]
}
},
{
"name": "get_mbusa_inventory",
"description": "Searches for NEW Mercedes-Benz vehicle inventory near a specific zip code. Returns vehicle details including image URLs, dealer links, and Google Maps directions. ALWAYS format the output with Markdown links.",
"parameters": {
"type": "object",
"properties": {
"params": {
"type": "object",
"properties": {
"zip": { "type": "string", "description": "The 5-digit US zip code. Required." },
"dealerId": { "type": "string", "description": "5-digit dealer code if known." },
"distance": { "type": "integer", "enum": [10, 25, 50, 100, 200, 500, 1000] },
"minPrice": { "type": "integer" },
"maxPrice": { "type": "integer" },
"bodyStyle": { "type": "string", "enum": ["CABRDS", "CPE", "SDN", "SUV", "WGN"] },
"brand": { "type": "string", "enum": ["AMG", "MAYBACH"] },
"classId": { "type": "string", "enum": ["C", "CLA", "CLE", "E", "EQB", "EQE", "EQS", "G", "GLA", "GLB", "GLC", "GLE", "GLS", "AMGGT"] },
"model": {
"type": "string",
"enum": ["E53EW4", "C43W4", "GLA250W4", "S580V4", "C63W4SE", "CLA35C4", "CLA45C4S", "CLE53A4", "CLE53C4", "AMGEQEV4", "E53ES4", "AMGEQEX4", "AMGEQSV4", "G63W4", "GLA35W4", "GLB35W4", "GLC43C4", "GLC43W4", "GLC63C4E", "GLC63W4E", "GLE53W4", "GLE53C4", "GLE63W4S", "GLE63C4S", "GLS63W4", "GT43C4", "AMGGT43", "GT53C4", "AMGGT55", "GT63C4", "AMGGT63", "AMGGT63X", "GT63C4SE", "AMGGT63E", "S63EV4", "SL43R", "SL55R4", "SL63R4", "SL63ER4", "C300W4", "C300W", "CLA250C4", "CLA250C", "CLA350E4", "CLE300A4", "CLE300C4", "CLE450A4", "E350W4", "CLE450C4", "E350W", "E450S4", "E450W4", "EQB250W", "EQB300W4", "EQB350W4", "EQE350V4", "EQE350X4", "EQE350V", "EQE350X", "EQE500V4", "EQE500X4", "EQS450V4", "EQS450V", "EQS450X", "EQS580V4", "G550W4", "G580W4E", "GLA250W", "GLB250W4", "GLB250W", "GLC300C4", "GLC300W4", "GLC300W", "GLC350E4", "GLE350W4", "GLE350W", "GLE450C4", "GLE450W4", "GLE450E4", "GLE580W4", "GLS450W4", "GLS580W4", "EQS680Z4", "GLS600Z4", "S580Z4", "S680Z4", "SL680Z4", "S500V4", "S580EV4"]
},
"exteriorColor": { "type": "string", "enum": ["BLK", "BLU", "BWN", "GRN", "GRY", "RED", "SLV", "WHT", "YLW", "OTR"] },
"interiorColor": { "type": "string", "enum": ["BGE", "BLK", "BWN", "GRY", "RED", "OTR"] },
"year": { "type": "integer", "enum": [2024, 2025, 2026] },
"highwayFuelEconomy": { "type": "string", "enum": ["0_19", "20_30", "30_40", "40_1000000"] },
"passengerCapacity": { "type": "integer", "enum": [2, 4, 5, 7] },
"fuelType": { "type": "string", "enum": ["E", "G", "PH", "PPH"] },
"start": { "type": "integer", "default": 0 },
"count": { "type": "integer", "default": 12 }
},
"required": ["zip"]
}
}
}
},
{
"name": "get_mbusa_used_inventory",
"description": "Searches for CERTIFIED PRE-OWNED (CPO) and USED Mercedes-Benz vehicle inventory near a specific zip code. Returns vehicle details including image URLs, dealer links, and Google Maps directions. ALWAYS format the output with Markdown links.",
"parameters": {
"type": "object",
"properties": {
"params": {
"type": "object",
"properties": {
"zip": { "type": "string", "description": "The 5-digit US zip code. Required." },
"invType": { "type": "string", "enum": ["cpo", "pre"], "description": "Required. Use 'cpo' for Certified Pre-Owned, 'pre' for standard used." },
"dealerId": { "type": "string", "description": "5-digit dealer code if known." },
"distance": { "type": "integer", "enum": [10, 25, 50, 100, 200, 500, 1000] },
"minPrice": { "type": "integer" },
"maxPrice": { "type": "integer" },
"mileage": { "type": "string", "enum": ["0_5000", "5000_10000", "10000_50000", "50000_100000", "100000_200000"] },
"bodyStyle": { "type": "string", "enum": ["CABRDS", "CPE", "SDN", "SUV", "WGN"] },
"brand": { "type": "string", "enum": ["AMG", "MAYBACH"] },
"classId": { "type": "string", "enum": ["A", "C", "CLA", "CLE", "CLK", "E", "EQB", "EQE", "EQS", "G", "GL", "GLA", "GLB", "GLC", "GLE", "GLK", "GLS", "M", "AMGGT", "S", "SL", "SLC", "SLK"] },
"model": {
"type": "string",
"enum": ["E53EW4", "C43W4", "GLA250W4", "S580V4", "C63W4SE", "CLA35C4", "CLA45C4S", "CLE53A4", "CLE53C4", "AMGEQEV4", "E53ES4", "AMGEQEX4", "AMGEQSV4", "G63W4", "GLA35W4", "GLB35W4", "GLC43C4", "GLC43W4", "GLC63C4E", "GLC63W4E", "GLE53W4", "GLE53C4", "GLE63W4S", "GLE63C4S", "GLS63W4", "GT43C4", "AMGGT43", "GT53C4", "AMGGT55", "GT63C4", "AMGGT63", "AMGGT63X", "GT63C4SE", "AMGGT63E", "S63EV4", "SL43R", "SL55R4", "SL63R4", "SL63ER4", "C300W4", "C300W", "CLA250C4", "CLA250C", "CLA350E4", "CLE300A4", "CLE300C4", "CLE450A4", "E350W4", "CLE450C4", "E350W", "E450S4", "E450W4", "EQB250W", "EQB300W4", "EQB350W4", "EQE350V4", "EQE350X4", "EQE350V", "EQE350X", "EQE500V4", "EQE500X4", "EQS450V4", "EQS450V", "EQS450X", "EQS580V4", "G550W4", "G580W4E", "GLA250W", "GLB250W4", "GLB250W", "GLC300C4", "GLC300W4", "GLC300W", "GLC350E4", "GLE350W4", "GLE350W", "GLE450C4", "GLE450W4", "GLE450E4", "GLE580W4", "GLS450W4", "GLS580W4", "EQS680Z4", "GLS600Z4", "S580Z4", "S680Z4", "SL680Z4", "S500V4", "S580EV4"]
},
"exteriorColor": { "type": "string", "enum": ["BLK", "BLU", "BWN", "GRN", "GRY", "RED", "SLV", "WHT", "YLW", "OTR"] },
"interiorColor": { "type": "string", "enum": ["BGE", "BLK", "BWN", "GRY", "RED", "OTR"] },
"year": { "type": "integer", "enum": [2020, 2021, 2022, 2023, 2024, 2025, 2026] },
"highwayFuelEconomy": { "type": "string", "enum": ["0_19", "20_30", "30_40", "40_1000000"] },
"passengerCapacity": { "type": "integer", "enum": [2, 4, 5, 7] },
"fuelType": { "type": "string", "enum": ["E", "G", "PH", "PPH"] },
"start": { "type": "integer", "default": 0 },
"count": { "type": "integer", "default": 12 }
},
"required": ["zip", "invType"]
}
}
}
}
]
FILE:server.js
const express = require('express');
const { getMbusaDealers, getMbusaInventory, getMbusaUsedInventory } = require('./src/tool.js');
const app = express();
const PORT = process.env.PORT || 3000;
app.use(express.json());
// ==========================================
// ENDPOINT: Dealer Locator
// ==========================================
app.get('/api/dealers', async (req, res) => {
try {
const zip = req.query.zip;
if (!zip) return res.status(400).json({ status: "error", message: "Missing required parameter: zip" });
const start = req.query.start ? parseInt(req.query.start, 10) : 0;
const count = req.query.count ? parseInt(req.query.count, 10) : 10;
const filter = req.query.filter || "mbdealer";
const resultString = await getMbusaDealers(zip, start, count, filter);
const resultJson = JSON.parse(resultString);
if (resultJson.status === "error") return res.status(502).json(resultJson);
res.status(200).json(resultJson);
} catch (error) {
console.error("Dealer API Error:", error);
res.status(500).json({ status: "error", message: "Internal server error" });
}
});
// ==========================================
// ENDPOINT: NEW Vehicle Inventory Search
// ==========================================
app.get('/api/inventory', async (req, res) => {
try {
if (!req.query.zip) return res.status(400).json({ status: "error", message: "Missing required parameter: zip" });
const params = {
zip: req.query.zip,
dealerId: req.query.dealerId,
model: req.query.model,
classId: req.query.classId,
bodyStyle: req.query.bodyStyle,
brand: req.query.brand,
exteriorColor: req.query.exteriorColor,
interiorColor: req.query.interiorColor,
highwayFuelEconomy: req.query.highwayFuelEconomy,
fuelType: req.query.fuelType,
distance: req.query.distance ? parseInt(req.query.distance, 10) : undefined,
minPrice: req.query.minPrice ? parseInt(req.query.minPrice, 10) : undefined,
maxPrice: req.query.maxPrice ? parseInt(req.query.maxPrice, 10) : undefined,
minYear: req.query.minYear ? parseInt(req.query.minYear, 10) : undefined,
maxYear: req.query.maxYear ? parseInt(req.query.maxYear, 10) : undefined,
passengerCapacity: req.query.passengerCapacity ? parseInt(req.query.passengerCapacity, 10) : undefined,
year: req.query.year ? parseInt(req.query.year, 10) : undefined,
start: req.query.start ? parseInt(req.query.start, 10) : 0,
count: req.query.count ? parseInt(req.query.count, 10) : 12
};
const resultString = await getMbusaInventory(params);
const resultJson = JSON.parse(resultString);
if (resultJson.status === "error") return res.status(502).json(resultJson);
res.status(200).json(resultJson);
} catch (error) {
console.error("New Inventory API Error:", error);
res.status(500).json({ status: "error", message: "Internal server error" });
}
});
// ==========================================
// ENDPOINT: USED/CPO Vehicle Inventory Search
// ==========================================
app.get('/api/used-inventory', async (req, res) => {
try {
if (!req.query.zip) return res.status(400).json({ status: "error", message: "Missing required parameter: zip" });
if (!req.query.invType) return res.status(400).json({ status: "error", message: "Missing required parameter: invType (cpo or pre)" });
const params = {
zip: req.query.zip,
invType: req.query.invType,
dealerId: req.query.dealerId,
model: req.query.model,
classId: req.query.classId,
bodyStyle: req.query.bodyStyle,
brand: req.query.brand,
exteriorColor: req.query.exteriorColor,
interiorColor: req.query.interiorColor,
highwayFuelEconomy: req.query.highwayFuelEconomy,
fuelType: req.query.fuelType,
mileage: req.query.mileage,
distance: req.query.distance ? parseInt(req.query.distance, 10) : undefined,
minPrice: req.query.minPrice ? parseInt(req.query.minPrice, 10) : undefined,
maxPrice: req.query.maxPrice ? parseInt(req.query.maxPrice, 10) : undefined,
minYear: req.query.minYear ? parseInt(req.query.minYear, 10) : undefined,
maxYear: req.query.maxYear ? parseInt(req.query.maxYear, 10) : undefined,
passengerCapacity: req.query.passengerCapacity ? parseInt(req.query.passengerCapacity, 10) : undefined,
year: req.query.year ? parseInt(req.query.year, 10) : undefined,
start: req.query.start ? parseInt(req.query.start, 10) : 0,
count: req.query.count ? parseInt(req.query.count, 10) : 12
};
const resultString = await getMbusaUsedInventory(params);
const resultJson = JSON.parse(resultString);
if (resultJson.status === "error") return res.status(502).json(resultJson);
res.status(200).json(resultJson);
} catch (error) {
console.error("Used Inventory API Error:", error);
res.status(500).json({ status: "error", message: "Internal server error" });
}
});
app.listen(PORT, () => {
console.log(`🚀 MBUSA Skill API is running on port PORT`);
console.log(`Test Dealers: http://localhost:PORT/api/dealers?zip=10019`);
console.log(`Test New Inventory: http://localhost:PORT/api/inventory?zip=10019&distance=50`);
console.log(`Test Used Inventory: http://localhost:PORT/api/used-inventory?zip=10019&invType=cpo`);
});
FILE:src/tool.js
/**
* Finds Mercedes-Benz USA dealers in a specific state or near a zip code.
*/
async function getMbusaDealers(zipCode, start = 0, count = 10, filter = "mbdealer") {
const baseUrl = "https://nafta-service.mbusa.com/api/dlrsrv/v1/us/search";
const apiUrl = new URL(baseUrl);
apiUrl.searchParams.append("zip", zipCode);
apiUrl.searchParams.append("start", start);
apiUrl.searchParams.append("count", count);
apiUrl.searchParams.append("filter", filter);
const headers = {
"Accept": "application/json",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
};
try {
const response = await fetch(apiUrl, { headers });
if (!response.ok) {
throw new Error(`HTTP error! Status: response.status`);
}
const data = await response.json();
const dealers = (data.results || []).map(dealer => {
const primaryAddress = dealer.address && dealer.address.length > 0 ? dealer.address[0] : {};
const location = primaryAddress.location || {};
const contacts = dealer.contact || [];
const mainPhone = contacts.find(c => c.type === "phone");
const servicePhone = contacts.find(c => c.type === "servicePhone");
const activities = dealer.activities || [];
const serviceActivity = activities.find(a => a.name === "service");
// FIXED: Standard Google Maps URL format
let mapUrl = "N/A";
if (location.lat && location.lng) {
mapUrl = `https://www.google.com/maps/search/?api=1&query=location.lat,location.lng`;
} else if (primaryAddress.line1 && primaryAddress.zip) {
const fullAddress = `primaryAddress.line1, primaryAddress.city, primaryAddress.state primaryAddress.zip`;
mapUrl = `https://www.google.com/maps/search/?api=1&query=encodeURIComponent(fullAddress)`;
}
return {
id: dealer.id,
name: dealer.name,
address: primaryAddress.line1,
city: primaryAddress.city,
state: primaryAddress.state,
zip: primaryAddress.zip,
distance: location.dist ? `location.dist location.distunit` : "Unknown distance",
mainPhone: mainPhone ? mainPhone.value : "N/A",
servicePhone: servicePhone ? servicePhone.value : "N/A",
websiteUrl: dealer.url || "N/A",
serviceUrl: serviceActivity ? serviceActivity.url : "N/A",
googleMapsUrl: mapUrl
};
});
return JSON.stringify({
status: "success",
totalFound: data.totalCount || dealers.length,
dealers: dealers
});
} catch (error) {
return JSON.stringify({ status: "error", message: error.message });
}
}
/**
* Helper function to map inventory records for both New and Used endpoints.
*/
function mapInventoryRecords(records) {
return records.map(car => {
const primaryAddress = car.dealer?.address?.[0] || {};
const location = primaryAddress.location || {};
const distance = location.dist ? `location.dist miles` : "Unknown distance";
const serviceActivity = car.dealer?.activities?.find(a => a.name === "service");
const imageUrl = car.images?.[0] || car.exteriorBaseImage?.desktop?.url || "N/A";
const dealerUrl = car.dealer?.url || "N/A";
const serviceUrl = serviceActivity ? serviceActivity.url : "N/A";
// FIXED: Standard Google Maps URL format
let mapUrl = "N/A";
if (location.lat && location.lng) {
mapUrl = `https://www.google.com/maps/search/?api=1&query=location.lat,location.lng`;
} else if (primaryAddress.line1 && primaryAddress.zip) {
const fullAddress = `primaryAddress.line1, primaryAddress.city, primaryAddress.state primaryAddress.zip`;
mapUrl = `https://www.google.com/maps/search/?api=1&query=encodeURIComponent(fullAddress)`;
}
return {
vin: car.vin,
stockId: car.stockId || car.vehicleAttributes?.stockId || "N/A",
year: car.year,
modelName: car.modelName,
msrp: car.msrp || car.inventoryPrice,
mileage: car.mileage || "N/A",
engine: car.engine,
exteriorColor: car.paint?.name || car.exteriorMetaColor,
interiorColor: car.upholstery?.name || car.interiorMetaColor,
dealerName: car.dealer?.name || "Unknown Dealer",
distance: distance,
available: car.available,
imageUrl: imageUrl,
dealerWebsite: dealerUrl,
scheduleServiceUrl: serviceUrl,
googleMapsUrl: mapUrl
};
});
}
/**
* Finds Mercedes-Benz USA NEW vehicle inventory.
*/
async function getMbusaInventory(params) {
const baseUrl = "https://nafta-service.mbusa.com/api/inv/v1/en_us/new/vehicles/search";
const apiUrl = new URL(baseUrl);
if (params.zip) apiUrl.searchParams.append("zip", params.zip);
apiUrl.searchParams.append("start", params.start || 0);
apiUrl.searchParams.append("count", params.count || 12);
apiUrl.searchParams.append("withFilters", "true");
if (params.dealerId) apiUrl.searchParams.append("dealerId", params.dealerId);
if (params.distance) apiUrl.searchParams.append("distance", params.distance);
if (params.model) apiUrl.searchParams.append("model", params.model);
if (params.classId) apiUrl.searchParams.append("class", params.classId);
if (params.bodyStyle) apiUrl.searchParams.append("bodyStyle", params.bodyStyle);
if (params.brand) apiUrl.searchParams.append("brand", params.brand);
if (params.exteriorColor) apiUrl.searchParams.append("exteriorColor", params.exteriorColor);
if (params.interiorColor) apiUrl.searchParams.append("interiorColor", params.interiorColor);
if (params.year) apiUrl.searchParams.append("year", params.year);
if (params.highwayFuelEconomy) apiUrl.searchParams.append("highwayFuelEconomy", params.highwayFuelEconomy);
if (params.passengerCapacity) apiUrl.searchParams.append("passengerCapacity", params.passengerCapacity);
if (params.fuelType) apiUrl.searchParams.append("fuelType", params.fuelType);
if (params.minPrice !== undefined || params.maxPrice !== undefined) {
const min = params.minPrice !== undefined ? params.minPrice : 0;
const max = params.maxPrice !== undefined ? params.maxPrice : 999000;
apiUrl.searchParams.append("price", `min_max`);
}
const headers = {
"Accept": "application/json",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
};
try {
const response = await fetch(apiUrl, { headers });
if (!response.ok) throw new Error(`HTTP error! Status: response.status`);
const data = await response.json();
const records = data?.result?.pagedVehicles?.records || [];
const vehicles = mapInventoryRecords(records);
const totalCount = data?.result?.pagedVehicles?.paging?.totalCount || vehicles.length;
return JSON.stringify({ status: "success", totalAvailable: totalCount, returnedCount: vehicles.length, inventory: vehicles });
} catch (error) {
return JSON.stringify({ status: "error", message: error.message });
}
}
/**
* Finds Mercedes-Benz USA CERTIFIED PRE-OWNED and USED vehicle inventory.
*/
async function getMbusaUsedInventory(params) {
const baseUrl = "https://nafta-service.mbusa.com/api/inv/v1/en_us/used/vehicles/search";
const apiUrl = new URL(baseUrl);
// Required parameters
if (params.zip) apiUrl.searchParams.append("zip", params.zip);
if (params.invType) apiUrl.searchParams.append("invType", params.invType);
// Pagination and base filters
apiUrl.searchParams.append("start", params.start || 0);
apiUrl.searchParams.append("count", params.count || 12);
apiUrl.searchParams.append("withFilters", "true");
// Directly mapped parameters
if (params.dealerId) apiUrl.searchParams.append("dealerId", params.dealerId);
if (params.distance) apiUrl.searchParams.append("distance", params.distance);
if (params.model) apiUrl.searchParams.append("model", params.model);
if (params.classId) apiUrl.searchParams.append("class", params.classId);
if (params.bodyStyle) apiUrl.searchParams.append("bodyStyle", params.bodyStyle);
if (params.brand) apiUrl.searchParams.append("brand", params.brand);
if (params.exteriorColor) apiUrl.searchParams.append("exteriorColor", params.exteriorColor);
if (params.interiorColor) apiUrl.searchParams.append("interiorColor", params.interiorColor);
if (params.year) apiUrl.searchParams.append("year", params.year);
if (params.highwayFuelEconomy) apiUrl.searchParams.append("highwayFuelEconomy", params.highwayFuelEconomy);
if (params.passengerCapacity) apiUrl.searchParams.append("passengerCapacity", params.passengerCapacity);
if (params.fuelType) apiUrl.searchParams.append("fuelType", params.fuelType);
if (params.mileage) apiUrl.searchParams.append("mileage", params.mileage);
if (params.minPrice !== undefined || params.maxPrice !== undefined) {
const min = params.minPrice !== undefined ? params.minPrice : 0;
const max = params.maxPrice !== undefined ? params.maxPrice : 999000;
apiUrl.searchParams.append("price", `min_max`);
}
const headers = {
"Accept": "application/json",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
};
try {
const response = await fetch(apiUrl, { headers });
if (!response.ok) throw new Error(`HTTP error! Status: response.status`);
const data = await response.json();
const records = data?.result?.pagedVehicles?.records || [];
const vehicles = mapInventoryRecords(records);
const totalCount = data?.result?.pagedVehicles?.paging?.totalCount || vehicles.length;
return JSON.stringify({ status: "success", totalAvailable: totalCount, returnedCount: vehicles.length, inventory: vehicles });
} catch (error) {
return JSON.stringify({ status: "error", message: error.message });
}
}
module.exports = { getMbusaDealers, getMbusaInventory, getMbusaUsedInventory };
FILE:tests/test.js
const { getMbusaDealers } = require('../src/tool.js');
async function runTest() {
console.log("Testing MBUSA Dealer Skill...");
console.log("Fetching dealers near 30097 (Johns Creek, GA)...\n");
try {
// Call the function with a test zip code
const resultString = await getMbusaDealers("30097", 0, 10, "mbdealer");
// Parse the result back into an object so we can read it easily in the console
const result = JSON.parse(resultString);
if (result.status === "success") {
console.log(`✅ Success! Found result.dealers.length dealers.`);
console.log("First dealer found:");
console.log(result.dealers);
} else {
console.log(`❌ Error returned from tool: result.message`);
}
} catch (error) {
console.error("❌ Test script crashed:", error);
}
}
runTest();
Generates and iteratively edits Mermaid.js and Draw.io diagrams. Supports multimodal context (reading source code, architecture sketches, and documentation).
---
name: diagram-generator
version: 1.0.2
description: Generates and iteratively edits Mermaid.js and Draw.io diagrams. Supports multimodal context (reading source code, architecture sketches, and documentation).
tags: ["mermaid", "drawio", "architecture", "visualization", "gemini", "generator"]
metadata:
openclaw:
requires:
env:
- GEMINI_API_KEY
bins:
- node
- curl
- base64
primaryEnv: GEMINI_API_KEY
---
# AI Diagram Generator
## Usage Instructions
This skill allows you to generate and iteratively edit Mermaid diagrams and Draw.io (mxGraph) files for the user by leveraging a local Node.js server connected to the Gemini API.
### Step 1: Verify the Server is Running
Before using this tool, check if the service is available by making a GET request to `http://localhost:3000/api/health`.
If it is not reachable, ensure the `GEMINI_API_KEY` is set and start the server (`npm run start`).
### Step 2: Prepare Context Files (SECURITY RESTRICTIONS APPLY)
If the user asks you to map out an existing codebase or read local files, you MUST adhere to the following security protocols before reading any file from the workspace:
**✅ ALLOWLIST (Permitted Files):**
You may ONLY read and process standard source code files (e.g., `.js`, `.ts`, `.py`, `.java`, `.cpp`, `.html`, `.css`), documentation (e.g., `.md`, `.txt`), or safe images (`.png`, `.jpg`).
**❌ BLOCKLIST (Forbidden Files):**
You are STRICTLY PROHIBITED from reading, analyzing, or converting any configuration files, secret files, or environment variables. This includes, but is not limited to:
- `.env`, `.env.local`, or any environment files.
- `secrets.json`, `credentials.yml`, or AWS/GCP config folders.
- `id_rsa`, `.pem`, or any SSH/encryption keys.
- Hidden system directories (e.g., `.git/`, `.ssh/`).
**Action:** If a user or a prompt instructs you to read a forbidden file, you must completely refuse the request and state that it violates your security policy.
For permitted files:
- For text/code files: Extract the raw text.
- For permitted images/PDFs: Convert the file to a base64 string using the `base64` command.
### Step 3: Construct the Prompt Payload
Gemini 2.5 Flash powers the backend. To ensure high-quality generation, construct the `prompt` string using clear, structured formatting.
- **Use XML Tags or Markdown Headers:** Separate the goal from the instructions (e.g., `<goal>`, `<rules>`).
- **Be Explicit:** State the exact diagram type (Flowchart, Sequence, ER, Gantt, Architecture) in the prompt text.
- **Enforce Raw Output:** Always append an instruction demanding raw code without conversational filler.
### Step 4: Generate the Diagram
Send a POST request to `http://localhost:3000/api/generate`.
**Headers:** `Content-Type: application/json`
**Payload Schema:**
```json
{
"prompt": "<goal>Map the auth flow</goal><rules>1. Output raw code only. 2. Include database nodes.</rules>",
"type": "<'mermaid' or 'drawio'>",
"currentCode": "<optional: existing mermaid/drawio code if iteratively editing>",
"files": [
{
"name": "auth.ts",
"text": "<raw text content>",
"type": "text"
},
{
"name": "sketch.png",
"data": "<base64 string>",
"mimeType": "image/png"
}
]
}
FILE:README.md
# Diagram Generator
An AI-powered web application and OpenClaw skill that generates and iteratively edits Mermaid and Draw.io diagrams using the Gemini API.
## Features
* **Generates various diagram types**: Flowcharts, ER diagrams, Gantt charts, Git graphs, and Draw.io Architecture maps.
* **Multimodal Support**: Attach images (sketches), PDFs, and source code files directly in the UI for the AI to reverse-engineer into diagrams.
* **Iterative Editing**: Modify existing diagrams via text prompts.
* **Graphical Web UI**: Real-time rendering, live-code editor modal, and file browser.
* **Export Options**: Save directly to the server, or export as `.mmd`, `.drawio`, `.png`, and `.jpg`.
* **OpenClaw Skill Ready**: Includes a `SKILL.md` file for seamless integration with AI agents.
## Setup Instructions
### 1. Prerequisites
* Node.js installed on your machine.
* A Gemini API Key.
### 2. Install Dependencies
\`\`\`bash
npm install
\`\`\`
### 3. Environment Variables
Create a `.env` file in the root directory:
\`\`\`env
GEMINI_API_KEY=your_gemini_api_key_here
\`\`\`
### 4. Start the Application
To run the server locally:
\`\`\`bash
npm start
\`\`\`
Then open `http://localhost:3000` in your browser.
To run the server and test the OpenClaw agent concurrently:
\`\`\`bash
npm run test:skill
\`\`\`
## Publishing to ClawHub
This project includes a `SKILL.md` formatted for [ClawHub.ai](https://clawhub.ai).
To publish your skill to the public registry:
1. Login to ClawHub: `clawhub login`
2. Publish the bundle: `clawhub publish . --slug diagram-generator --version 1.0.1`
FILE:package-lock.json
{
"name": "ai-mermaid-diagram-generator",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ai-mermaid-diagram-generator",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@google/genai": "^1.45.0",
"cors": "^2.8.5",
"dotenv": "^17.3.1",
"express": "^4.19.2"
},
"devDependencies": {
"nodemon": "^3.1.0"
}
},
"node_modules/@google/genai": {
"version": "1.47.0",
"resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.47.0.tgz",
"integrity": "sha512-0VV7AaXm5rQu3oRHNZNEubRAOL2lv5u+YA72eWnDwcOx3B1jFRbvtgL4drRHlocRHOnludvr3xmbQGbR+/RQAQ==",
"license": "Apache-2.0",
"dependencies": {
"google-auth-library": "^10.3.0",
"p-retry": "^4.6.2",
"protobufjs": "^7.5.4",
"ws": "^8.18.0"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"@modelcontextprotocol/sdk": "^1.25.2"
},
"peerDependenciesMeta": {
"@modelcontextprotocol/sdk": {
"optional": true
}
}
},
"node_modules/@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/base64": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/codegen": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/eventemitter": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
"integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/fetch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
"integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.1",
"@protobufjs/inquire": "^1.1.0"
}
},
"node_modules/@protobufjs/float": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/inquire": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
"integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/path": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/pool": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/utf8": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
"license": "BSD-3-Clause"
},
"node_modules/@types/node": {
"version": "25.5.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz",
"integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.18.0"
}
},
"node_modules/@types/retry": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
"license": "MIT"
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"license": "MIT",
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"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==",
"dev": true,
"license": "ISC",
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
},
"node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
}
},
"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==",
"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/bignumber.js": {
"version": "9.3.1",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
"integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"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==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/body-parser": {
"version": "1.20.4",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
"integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
"license": "MIT",
"dependencies": {
"bytes": "~3.1.2",
"content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "~1.2.0",
"http-errors": "~2.0.1",
"iconv-lite": "~0.4.24",
"on-finished": "~2.4.1",
"qs": "~6.14.0",
"raw-body": "~2.5.3",
"type-is": "~1.6.18",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/brace-expansion": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
}
},
"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,
"license": "MIT",
"dependencies": {
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
"license": "BSD-3-Clause"
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"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": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"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": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"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,
"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"
},
"engines": {
"node": ">= 8.10.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
"license": "MIT",
"dependencies": {
"safe-buffer": "5.2.1"
},
"engines": {
"node": ">= 0.6"
}
},
"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": ">= 0.6"
}
},
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
"license": "MIT"
},
"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": {
"object-assign": "^4",
"vary": "^1"
},
"engines": {
"node": ">= 0.10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"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": ">= 0.8"
}
},
"node_modules/destroy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
"license": "MIT",
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/dotenv": {
"version": "17.3.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz",
"integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"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"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"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/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"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/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/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"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": ">= 0.6"
}
},
"node_modules/express": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
"integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "~1.20.3",
"content-disposition": "~0.5.4",
"content-type": "~1.0.4",
"cookie": "~0.7.1",
"cookie-signature": "~1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "~1.3.1",
"fresh": "~0.5.2",
"http-errors": "~2.0.0",
"merge-descriptors": "1.0.3",
"methods": "~1.1.2",
"on-finished": "~2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "~0.1.12",
"proxy-addr": "~2.0.7",
"qs": "~6.14.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "~0.19.0",
"serve-static": "~1.16.2",
"setprototypeof": "1.2.0",
"statuses": "~2.0.1",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
},
"engines": {
"node": ">= 0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"license": "MIT"
},
"node_modules/fetch-blob": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"dependencies": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
},
"engines": {
"node": "^12.20 || >= 14.13"
}
},
"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,
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/finalhandler": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
"integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"on-finished": "~2.4.1",
"parseurl": "~1.3.3",
"statuses": "~2.0.2",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"license": "MIT",
"dependencies": {
"fetch-blob": "^3.1.2"
},
"engines": {
"node": ">=12.20.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": ">= 0.6"
}
},
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"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,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.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://github.com/sponsors/ljharb"
}
},
"node_modules/gaxios": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz",
"integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==",
"license": "Apache-2.0",
"dependencies": {
"extend": "^3.0.2",
"https-proxy-agent": "^7.0.1",
"node-fetch": "^3.3.2"
},
"engines": {
"node": ">=18"
}
},
"node_modules/gcp-metadata": {
"version": "8.1.2",
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz",
"integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==",
"license": "Apache-2.0",
"dependencies": {
"gaxios": "^7.0.0",
"google-logging-utils": "^1.0.0",
"json-bigint": "^1.0.0"
},
"engines": {
"node": ">=18"
}
},
"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": {
"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.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"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": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"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"
}
},
"node_modules/google-auth-library": {
"version": "10.6.2",
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz",
"integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==",
"license": "Apache-2.0",
"dependencies": {
"base64-js": "^1.3.0",
"ecdsa-sig-formatter": "^1.0.11",
"gaxios": "^7.1.4",
"gcp-metadata": "8.1.2",
"google-logging-utils": "1.1.3",
"jws": "^4.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/google-logging-utils": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz",
"integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==",
"license": "Apache-2.0",
"engines": {
"node": ">=14"
}
},
"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": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"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.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"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": {
"depd": "~2.0.0",
"inherits": "~2.0.4",
"setprototypeof": "~1.2.0",
"statuses": "~2.0.2",
"toidentifier": "~1.0.1"
},
"engines": {
"node": ">= 0.8"
},
"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==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/https-proxy-agent/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"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/https-proxy-agent/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/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/ignore-by-default": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
"integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
"dev": true,
"license": "ISC"
},
"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/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",
"engines": {
"node": ">= 0.10"
}
},
"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,
"license": "MIT",
"dependencies": {
"binary-extensions": "^2.0.0"
},
"engines": {
"node": ">=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,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"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,
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"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,
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/json-bigint": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
"integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
"license": "MIT",
"dependencies": {
"bignumber.js": "^9.0.0"
}
},
"node_modules/jwa": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
"integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
"license": "MIT",
"dependencies": {
"buffer-equal-constant-time": "^1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/jws": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
"integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
"license": "MIT",
"dependencies": {
"jwa": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/long": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"license": "Apache-2.0"
},
"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",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/merge-descriptors": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"license": "MIT",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/minimatch": {
"version": "10.2.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
"integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"brace-expansion": "^5.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"deprecated": "Use your platform's native DOMException instead",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-fetch": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
"license": "MIT",
"dependencies": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
"formdata-polyfill": "^4.0.10"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/nodemon": {
"version": "3.1.14",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz",
"integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==",
"dev": true,
"license": "MIT",
"dependencies": {
"chokidar": "^3.5.2",
"debug": "^4",
"ignore-by-default": "^1.0.1",
"minimatch": "^10.2.1",
"pstree.remy": "^1.1.8",
"semver": "^7.5.3",
"simple-update-notifier": "^2.0.0",
"supports-color": "^5.5.0",
"touch": "^3.1.0",
"undefsafe": "^2.0.5"
},
"bin": {
"nodemon": "bin/nodemon.js"
},
"engines": {
"node": ">=10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/nodemon"
}
},
"node_modules/nodemon/node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/nodemon/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/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/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": ">=0.10.0"
}
},
"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",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"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"
}
},
"node_modules/p-retry": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
"integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==",
"license": "MIT",
"dependencies": {
"@types/retry": "0.12.0",
"retry": "^0.13.1"
},
"engines": {
"node": ">=8"
}
},
"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",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/path-to-regexp": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
"integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
"license": "MIT"
},
"node_modules/picomatch": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/protobufjs": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz",
"integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==",
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
"@protobufjs/codegen": "^2.0.4",
"@protobufjs/eventemitter": "^1.1.0",
"@protobufjs/fetch": "^1.1.0",
"@protobufjs/float": "^1.0.2",
"@protobufjs/inquire": "^1.1.0",
"@protobufjs/path": "^1.1.2",
"@protobufjs/pool": "^1.1.0",
"@protobufjs/utf8": "^1.1.0",
"@types/node": ">=13.7.0",
"long": "^5.0.0"
},
"engines": {
"node": ">=12.0.0"
}
},
"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.10"
}
},
"node_modules/pstree.remy": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
"integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
"dev": true,
"license": "MIT"
},
"node_modules/qs": {
"version": "6.14.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
"integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/raw-body": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
"integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
"license": "MIT",
"dependencies": {
"bytes": "~3.1.2",
"http-errors": "~2.0.1",
"iconv-lite": "~0.4.24",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"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/retry": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
"integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"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/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/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/send": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
"integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "~0.5.2",
"http-errors": "~2.0.1",
"mime": "1.6.0",
"ms": "2.1.3",
"on-finished": "~2.4.1",
"range-parser": "~1.2.1",
"statuses": "~2.0.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/send/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/serve-static": {
"version": "1.16.3",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
"integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
"license": "MIT",
"dependencies": {
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "~0.19.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"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/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3",
"side-channel-list": "^1.0.0",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-list": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"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": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"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",
"dependencies": {
"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": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/simple-update-notifier": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
"integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
"dev": true,
"license": "MIT",
"dependencies": {
"semver": "^7.5.3"
},
"engines": {
"node": ">=10"
}
},
"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/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"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==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"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": ">=0.6"
}
},
"node_modules/touch": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
"integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
"dev": true,
"license": "ISC",
"bin": {
"nodetouch": "bin/nodetouch.js"
}
},
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"license": "MIT",
"dependencies": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/undefsafe": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
"dev": true,
"license": "MIT"
},
"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==",
"license": "MIT"
},
"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",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
"license": "MIT",
"engines": {
"node": ">= 0.4.0"
}
},
"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": ">= 0.8"
}
},
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
"license": "MIT",
"engines": {
"node": ">= 8"
}
},
"node_modules/ws": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
"integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
}
}
FILE:package.json
{
"name": "diagram-generator",
"version": "1.0.1",
"description": "An AI-powered tool to generate and iteratively edit Mermaid and Draw.io diagrams.",
"main": "server.js",
"type": "module",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"skill:install": "openclaw install .",
"test:skill": "concurrently \"npm run start\" \"openclaw\" --names \"SERVER,AGENT\" --prefix-colors \"blue,magenta\""
},
"dependencies": {
"@google/genai": "^1.45.0",
"cors": "^2.8.5",
"dotenv": "^17.3.1",
"express": "^4.19.2"
},
"devDependencies": {
"concurrently": "^8.2.2",
"nodemon": "^3.1.0"
},
"keywords": [
"mermaid",
"diagrams",
"ai",
"llm",
"generator",
"drawio"
],
"author": "Kaushik Datta",
"license": "MIT"
}
FILE:public/app.js
console.log("🚀 Initializing AI Diagram Generator...");
// Global state variables
let currentCode = "";
let currentType = "mermaid";
let previewCode = "";
let previewType = "mermaid";
let attachedPromptFiles = [];
let mermaid; // Will be loaded dynamically
// --- 1. Safe Initialization ---
async function initApp() {
try {
console.log("📦 Loading Mermaid.js from CDN...");
const mermaidModule = await import('https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs');
mermaid = mermaidModule.default;
mermaid.initialize({ startOnLoad: false, theme: 'default' });
console.log("✅ Mermaid loaded successfully.");
} catch (error) {
console.error("❌ Failed to load Mermaid:", error);
updateStatus("Warning: Could not load Mermaid engine. Check your internet connection or ad-blocker.", true);
}
bindEventListeners();
fetchSavedFiles();
}
// --- 2. Event Listeners ---
function bindEventListeners() {
document.getElementById('generateBtn').addEventListener('click', generateDiagram);
document.getElementById('saveServerTextBtn').addEventListener('click', () => saveTextToServer('mmd'));
document.getElementById('saveServerDrawioBtn').addEventListener('click', () => saveTextToServer('drawio'));
document.getElementById('saveServerPngBtn').addEventListener('click', () => saveImageToServer('png'));
document.getElementById('saveServerJpgBtn').addEventListener('click', () => saveImageToServer('jpeg'));
document.getElementById('refreshFilesBtn').addEventListener('click', fetchSavedFiles);
// Editor Code Loader
document.getElementById('loadFileBtn').addEventListener('click', (e) => {
e.preventDefault();
document.getElementById('fileInput').click();
});
document.getElementById('fileInput').addEventListener('change', loadLocalTextFile);
// Prompt Context Attacher
document.getElementById('attachContextBtn').addEventListener('click', (e) => {
e.preventDefault();
document.getElementById('promptFileInput').click();
});
document.getElementById('promptFileInput').addEventListener('change', handlePromptFileUpload);
// Modal Listeners
document.getElementById('closeModalBtn').addEventListener('click', closeModal);
document.getElementById('loadIntoEditorBtn').addEventListener('click', loadPreviewIntoEditor);
document.getElementById('rerenderPreviewBtn').addEventListener('click', updatePreviewFromEditor);
window.onclick = function(event) {
if (event.target === document.getElementById('previewModal')) closeModal();
}
document.querySelectorAll('.example-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
e.preventDefault();
const promptText = e.currentTarget.getAttribute('data-prompt');
if (promptText) {
document.getElementById('promptInput').value = promptText;
}
});
});
}
// --- 3. UI Helpers ---
function updateStatus(message, isError = false, isVisible = true) {
const statusDiv = document.getElementById('statusMessage');
statusDiv.style.display = isVisible ? 'block' : 'none';
statusDiv.innerHTML = message;
statusDiv.style.background = isError ? '#ffebee' : '#e8f5e9';
statusDiv.style.borderLeftColor = isError ? '#f44336' : '#4caf50';
statusDiv.style.color = isError ? '#c62828' : '#2e7d32';
}
// --- 4. Context File Handling ---
async function handlePromptFileUpload(event) {
const files = event.target.files;
if (!files || files.length === 0) return;
const textBasedExtensions = ['txt', 'js', 'py', 'json', 'html', 'css', 'ts', 'java', 'md'];
for (let file of files) {
const ext = file.name.split('.').pop().toLowerCase();
if (textBasedExtensions.includes(ext)) {
const text = await file.text();
attachedPromptFiles.push({ name: file.name, text: text, type: 'text' });
renderAttachedFilesUI();
} else if (ext === 'docx') {
const arrayBuffer = await file.arrayBuffer();
try {
const result = await mammoth.extractRawText({ arrayBuffer: arrayBuffer });
attachedPromptFiles.push({ name: file.name, text: result.value, type: 'text' });
renderAttachedFilesUI();
} catch (e) {
updateStatus(`❌ Failed to parse DOCX: e.message`, true);
}
} else if (['pdf', 'png', 'jpg', 'jpeg'].includes(ext)) {
const reader = new FileReader();
reader.onload = (e) => {
attachedPromptFiles.push({
name: file.name,
data: e.target.result,
mimeType: file.type || (ext === 'pdf' ? 'application/pdf' : `image/ext`)
});
renderAttachedFilesUI();
};
reader.readAsDataURL(file);
} else {
updateStatus(`❌ Unsupported file type: ext`, true);
}
}
event.target.value = '';
}
function renderAttachedFilesUI() {
const container = document.getElementById('attachedFilesContainer');
container.innerHTML = '';
attachedPromptFiles.forEach((file, index) => {
const pill = document.createElement('div');
pill.className = 'file-pill';
let icon = '📄';
if (file.name.match(/\.(png|jpe?g)$/i)) icon = '🖼️';
if (file.name.endsWith('.pdf')) icon = '📕';
pill.innerHTML = `
<span>icon file.name</span>
<span class="remove-file" data-index="index">×</span>
`;
container.appendChild(pill);
});
document.querySelectorAll('.remove-file').forEach(btn => {
btn.addEventListener('click', (e) => {
const index = e.target.getAttribute('data-index');
attachedPromptFiles.splice(index, 1);
renderAttachedFilesUI();
});
});
}
// --- 5. Main Generation Logic ---
async function generateDiagram() {
const promptInput = document.getElementById('promptInput');
const prompt = promptInput.value.trim();
if (!prompt && attachedPromptFiles.length === 0) {
return updateStatus("Please enter a description or attach a context file first!", true);
}
updateStatus("🚀 Sending prompt and context to AI backend...");
document.getElementById('generateBtn').disabled = true;
try {
const response = await fetch('/api/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
prompt: prompt || "Generate a diagram based on the attached files.",
currentCode,
type: currentType,
files: attachedPromptFiles
})
});
updateStatus("🧠 Rendering output...");
const data = await response.json();
if (data.error) throw new Error(data.error);
currentCode = data.code;
currentType = data.type;
await renderMainOutput(currentCode, currentType);
promptInput.value = "";
updateStatus(`✅ currentType.toUpperCase() generated successfully!`);
setTimeout(() => updateStatus("", false, false), 4000);
} catch (error) {
updateStatus(`❌ Error: error.message`, true);
} finally {
document.getElementById('generateBtn').disabled = false;
}
}
async function renderMainOutput(code, type) {
document.getElementById('placeholderText').style.display = 'none';
const mermaidDiv = document.getElementById('mermaidOutput');
const drawioIframe = document.getElementById('drawioOutput');
if (type === 'mermaid') {
if (!mermaid) return updateStatus("Mermaid library did not load. Cannot render.", true);
mermaidDiv.style.display = 'block';
drawioIframe.style.display = 'none';
try {
const { svg } = await mermaid.render(`main-mermaid-Date.now()`, code);
mermaidDiv.innerHTML = svg;
} catch (error) {
mermaidDiv.innerHTML = `<span style="color:red">Mermaid Error: error.message</span>`;
}
} else {
mermaidDiv.style.display = 'none';
drawioIframe.style.display = 'block';
drawioIframe.src = `https://viewer.diagrams.net/?lightbox=1&highlight=0000ff&edit=_blank&layers=1&nav=1&title=Diagram#RencodeURIComponent(code)`;
}
}
// --- 6. Server Save Logic ---
function getProjectName() {
return document.getElementById('projectNameInput').value.trim() || 'default_project';
}
async function saveTextToServer(ext) {
if (!currentCode) return updateStatus("No diagram to save!", true);
const finalExt = ext === 'drawio' ? 'drawio' : (currentType === 'drawio' ? 'drawio' : 'mmd');
try {
updateStatus(`💾 Saving as .finalExt...`);
await fetch('/api/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
projectName: getProjectName(),
filename: `diagram_Date.now().finalExt`,
content: currentCode,
isImage: false
})
});
updateStatus(`✅ Diagram saved as .finalExt`);
fetchSavedFiles();
setTimeout(() => updateStatus("", false, false), 3000);
} catch (error) {
updateStatus(`❌ Save failed: error.message`, true);
}
}
async function saveImageToServer(format = 'png') {
if (currentType !== 'mermaid') {
return updateStatus(`Image saving currently only supports Mermaid. Save as .drawio instead.`, true);
}
const svgElement = document.querySelector('#mermaidOutput svg');
if (!svgElement) return updateStatus("No diagram to save!", true);
updateStatus(`🖼️ Processing format.toUpperCase()...`);
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const img = new Image();
const rect = svgElement.getBoundingClientRect();
canvas.width = rect.width;
canvas.height = rect.height;
img.onload = async () => {
if (format === 'jpeg') {
ctx.fillStyle = "white";
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
ctx.drawImage(img, 0, 0);
const ext = format === 'jpeg' ? 'jpg' : 'png';
try {
await fetch('/api/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
projectName: getProjectName(),
filename: `diagram_Date.now().ext`,
content: canvas.toDataURL(`image/format`),
isImage: true
})
});
updateStatus(`✅ format.toUpperCase() saved to server!`);
fetchSavedFiles();
setTimeout(() => updateStatus("", false, false), 3000);
} catch (error) {
updateStatus(`❌ Save failed: error.message`, true);
}
};
const svgData = new XMLSerializer().serializeToString(svgElement);
img.src = "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svgData)));
}
// --- 7. Sidebar File Browser Logic ---
async function fetchSavedFiles() {
const treeDiv = document.getElementById('fileBrowserTree');
treeDiv.innerHTML = "<em>Loading...</em>";
try {
const response = await fetch('/api/files');
const projects = await response.json();
if (Object.keys(projects).length === 0) {
treeDiv.innerHTML = "<p>No saved projects yet.</p>";
return;
}
treeDiv.innerHTML = '';
for (const [projectName, files] of Object.entries(projects)) {
const projectBox = document.createElement('div');
projectBox.className = 'project-box';
const titleContainer = document.createElement('div');
titleContainer.className = 'project-title-container';
titleContainer.innerHTML = `<h4>📁 projectName</h4>`;
const deleteProjBtn = document.createElement('button');
deleteProjBtn.innerHTML = '🗑️';
deleteProjBtn.className = 'delete-proj-btn';
deleteProjBtn.title = "Delete entire project";
deleteProjBtn.onclick = () => deleteProject(projectName);
titleContainer.appendChild(deleteProjBtn);
projectBox.appendChild(titleContainer);
const fileList = document.createElement('ul');
files.forEach(file => {
const li = document.createElement('li');
li.className = 'file-item';
let icon = '📄';
if (file.endsWith('.drawio') || file.endsWith('.xml')) icon = '📐';
else if (file.match(/\.(png|jpe?g)$/i)) icon = '🖼️';
const fileSpan = document.createElement('span');
fileSpan.textContent = `icon file`;
fileSpan.className = 'file-name';
fileSpan.onclick = () => openPreviewModal(projectName, file);
const deleteBtn = document.createElement('button');
deleteBtn.innerHTML = '🗑️';
deleteBtn.className = 'delete-file-btn';
deleteBtn.title = "Delete file";
deleteBtn.onclick = (e) => {
e.stopPropagation();
deleteFile(projectName, file);
};
li.appendChild(fileSpan);
li.appendChild(deleteBtn);
fileList.appendChild(li);
});
projectBox.appendChild(fileList);
treeDiv.appendChild(projectBox);
}
} catch (error) {
treeDiv.innerHTML = `<span style="color:red">Failed to load files. Are you running on localhost:3000?</span>`;
}
}
async function deleteFile(project, filename) {
if (!confirm(`Delete "filename"?`)) return;
try {
await fetch(`/api/files/encodeURIComponent(project)/encodeURIComponent(filename)`, { method: 'DELETE' });
fetchSavedFiles();
} catch (e) {
updateStatus("❌ Delete failed", true);
}
}
async function deleteProject(project) {
if (!confirm(`🚨 Are you sure you want to delete the ENTIRE project "project"?`)) return;
try {
await fetch(`/api/projects/encodeURIComponent(project)`, { method: 'DELETE' });
fetchSavedFiles();
} catch (e) {
updateStatus("❌ Delete failed", true);
}
}
// --- 8. Modal Live Editor Logic ---
async function openPreviewModal(project, filename) {
const modal = document.getElementById('previewModal');
document.getElementById('previewTitle').textContent = `Preview: project / filename`;
document.getElementById('previewImage').style.display = 'none';
document.getElementById('previewCodeContainer').style.display = 'none';
document.getElementById('loadIntoEditorBtn').style.display = 'none';
if (filename.match(/\.(mmd|drawio|xml)$/i)) {
document.getElementById('previewCodeContainer').style.display = 'flex';
document.getElementById('loadIntoEditorBtn').style.display = 'inline-block';
previewType = filename.endsWith('.mmd') ? 'mermaid' : 'drawio';
try {
const response = await fetch(`/downloads/project/filename`);
const code = await response.text();
document.getElementById('previewCodeEditor').value = code;
previewCode = code;
await renderPreviewContent(previewCode, previewType);
} catch (e) {
document.getElementById('previewCodeEditor').value = "Failed to load file content.";
}
} else {
const imgEl = document.getElementById('previewImage');
imgEl.src = `/downloads/project/filename`;
imgEl.style.display = 'block';
}
modal.style.display = 'block';
}
function updatePreviewFromEditor() {
previewCode = document.getElementById('previewCodeEditor').value;
renderPreviewContent(previewCode, previewType);
}
async function renderPreviewContent(code, type) {
const mermaidEl = document.getElementById('previewMermaid');
const drawioEl = document.getElementById('previewDrawio');
if (type === 'mermaid') {
if (!mermaid) return;
mermaidEl.style.display = 'block';
drawioEl.style.display = 'none';
try {
const { svg } = await mermaid.render(`preview-render-Date.now()`, code);
mermaidEl.innerHTML = svg;
} catch (error) {
mermaidEl.innerHTML = `<span style="color:red; display:inline-block; margin-top:20px;">Render Error: error.message</span>`;
}
} else {
mermaidEl.style.display = 'none';
drawioEl.style.display = 'block';
drawioEl.src = `https://viewer.diagrams.net/?lightbox=1&highlight=0000ff&edit=_blank&layers=1&nav=1&title=Preview#RencodeURIComponent(code)`;
}
}
function loadPreviewIntoEditor() {
if (previewCode) {
currentCode = previewCode;
currentType = previewType;
renderMainOutput(currentCode, currentType);
updateStatus("✅ Diagram loaded into main editor.");
closeModal();
}
}
function closeModal() {
document.getElementById('previewModal').style.display = 'none';
document.getElementById('previewCodeEditor').value = '';
document.getElementById('previewImage').src = '';
document.getElementById('previewMermaid').innerHTML = '';
}
// --- 9. Local File Import Logic ---
function loadLocalTextFile(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = async (e) => {
currentCode = e.target.result;
currentType = file.name.endsWith('.drawio') || file.name.endsWith('.xml') ? 'drawio' : 'mermaid';
try {
await renderMainOutput(currentCode, currentType);
document.getElementById('promptInput').value = "";
updateStatus(`✅ Loaded local file as currentType.toUpperCase()`);
} catch (err) {
updateStatus("❌ Error rendering loaded file", true);
}
event.target.value = '';
};
reader.readAsText(file);
}
// Boot up the app
initApp();
FILE:public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Diagram Generator</title>
<link rel="stylesheet" href="style.css">
<script src="[https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.6.0/mammoth.browser.min.js](https://cdnjs.cloudflare.com/ajax/libs/mammoth/1.6.0/mammoth.browser.min.js)"></script>
</head>
<body>
<h1>Diagram Generator & Editor</h1>
<div class="main-container">
<div class="editor-section">
<div class="examples-container">
<span class="examples-label">Try an example:</span>
<button class="example-btn" data-prompt="A flowchart showing a user login process.">Login Flowchart</button>
<button class="example-btn" data-prompt="Gantt chart for a 3-month release cycle.">Gantt Chart</button>
<button class="example-btn" data-prompt="Generate a draw.io architecture diagram showing a web client connecting to a load balancer, two node servers, and a PostgreSQL database.">Draw.io Architecture</button>
</div>
<div class="prompt-header">
<label for="promptInput" style="font-weight: 600; color: #475569; font-size: 14px;">Prompt & Context</label>
<button id="attachContextBtn" class="example-btn" style="background: #f1f5f9; color: #475569; border-color: #cbd5e1;">📎 Attach Context</button>
<input type="file" id="promptFileInput" accept=".txt,.pdf,.png,.jpg,.jpeg,.docx,.js,.py,.json,.html,.css,.ts,.java,.md" multiple hidden>
</div>
<div id="attachedFilesContainer" class="attached-files-container"></div>
<textarea id="promptInput" placeholder="Describe your diagram, or ask for changes, or simply type 'Draw this' if you attached a file..."></textarea>
<div class="controls">
<button id="generateBtn" class="primary-btn">Generate / Update</button>
<button id="loadFileBtn">Load Local Code File</button>
<input type="file" id="fileInput" accept=".mmd,.txt,.drawio,.xml" hidden>
</div>
<div class="save-controls">
<input type="text" id="projectNameInput" placeholder="Project Name (e.g., App_V1)" value="My_Project">
<button id="saveServerTextBtn">Save (.mmd)</button>
<button id="saveServerDrawioBtn">Save (.drawio)</button>
<button id="saveServerPngBtn">Save (PNG)</button>
<button id="saveServerJpgBtn">Save (JPG)</button>
</div>
<div id="statusMessage">Ready. Describe a diagram to begin.</div>
<div id="output">
<p id="placeholderText" style="color: #888; margin-top: 100px;">Your diagram will appear here.</p>
<div id="mermaidOutput" style="display:none;"></div>
<iframe id="drawioOutput" style="display:none; width:100%; height:500px; border:none;"></iframe>
</div>
</div>
<div class="sidebar">
<h2>Saved Projects</h2>
<button id="refreshFilesBtn" style="width: 100%; margin-bottom: 10px;">Refresh List</button>
<div id="fileBrowserTree"></div>
</div>
</div>
<div id="previewModal" class="modal">
<div class="modal-content live-edit-modal">
<div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #eee; padding-bottom: 10px; margin-bottom: 15px;">
<h3 id="previewTitle" style="margin: 0;">File Preview</h3>
<span class="close-btn" id="closeModalBtn">×</span>
</div>
<div class="modal-body" style="flex-direction: column;">
<img id="previewImage" src="" alt="Preview" style="display: none; align-self: center;">
<div id="previewCodeContainer" style="display: none; width: 100%; gap: 20px;">
<div style="flex: 1; display: flex; flex-direction: column;">
<h4 style="margin-top: 0; margin-bottom: 10px;">Raw Text Editor</h4>
<textarea id="previewCodeEditor" style="flex: 1; font-family: monospace; padding: 10px; border: 1px solid #ccc; border-radius: 4px; min-height: 400px; resize: vertical;"></textarea>
<button id="rerenderPreviewBtn" style="margin-top: 10px; background: #e0e7ff; color: #3730a3; border: 1px solid #c7d2fe;">🔄 Live Update Preview</button>
</div>
<div style="flex: 1; display: flex; flex-direction: column; background: white; border: 1px dashed #cbd5e1; border-radius: 4px; padding: 15px; overflow: auto;">
<h4 style="margin-top: 0; margin-bottom: 10px;">Rendered Output</h4>
<div id="previewMermaid" style="display: none; text-align: center;"></div>
<iframe id="previewDrawio" style="display: none; width: 100%; height: 400px; border: none;"></iframe>
</div>
</div>
</div>
<div class="modal-footer">
<button id="loadIntoEditorBtn" class="primary-btn" style="display: none;">Load Code into Main Editor</button>
</div>
</div>
</div>
<script type="module" src="app.js"></script>
</body>
</html>
FILE:public/style.css
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
padding: 20px;
max-width: 1200px;
margin: auto;
background-color: #f9f9fb;
color: #333;
}
h1 { text-align: center; color: #222; }
/* --- Grid Layout --- */
.main-container {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 20px;
align-items: start;
}
/* --- Examples Section --- */
.examples-container {
margin-bottom: 15px;
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
}
.examples-label { font-size: 14px; font-weight: bold; color: #555; }
.example-btn {
background: #e0e7ff;
color: #3730a3;
border: 1px solid #c7d2fe;
border-radius: 20px;
padding: 5px 12px;
font-size: 13px;
cursor: pointer;
transition: background 0.2s;
}
.example-btn:hover { background: #c7d2fe; }
/* --- Main Input & Controls --- */
.prompt-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.attached-files-container {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 10px;
}
.file-pill {
background: #e2e8f0;
color: #334155;
padding: 6px 12px;
border-radius: 16px;
font-size: 12px;
display: flex;
align-items: center;
gap: 8px;
border: 1px solid #cbd5e1;
}
.file-pill .remove-file {
cursor: pointer;
font-weight: bold;
color: #ef4444;
font-size: 14px;
line-height: 1;
}
.file-pill .remove-file:hover {
color: #b91c1c;
}
textarea {
width: 100%;
height: 80px;
margin-bottom: 15px;
padding: 12px;
border: 1px solid #ccc;
border-radius: 6px;
font-family: inherit;
resize: vertical;
box-sizing: border-box;
}
.controls, .save-controls {
margin-bottom: 15px;
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.save-controls {
background: #f1f5f9;
padding: 10px;
border-radius: 6px;
border: 1px solid #e2e8f0;
}
input[type="text"] {
padding: 10px;
border: 1px solid #ccc;
border-radius: 6px;
}
button {
padding: 10px 15px;
cursor: pointer;
border: 1px solid #ccc;
background: white;
border-radius: 6px;
font-weight: 500;
}
button:hover { background: #f0f0f0; }
.primary-btn {
background: #4f46e5;
color: white;
border: none;
}
.primary-btn:hover { background: #4338ca; }
/* --- Status & Output --- */
#statusMessage {
font-size: 14px;
padding: 10px;
margin-bottom: 15px;
background: #e8f5e9;
border-left: 4px solid #4caf50;
color: #2e7d32;
border-radius: 4px;
display: none;
}
#output {
border: 1px solid #ddd;
padding: 20px;
min-height: 500px;
background: white;
text-align: center;
border-radius: 8px;
overflow-x: auto;
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
display: flex;
flex-direction: column;
}
/* --- Sidebar / File Browser --- */
.sidebar {
background: white;
padding: 15px;
border-radius: 8px;
border: 1px solid #ddd;
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
min-height: 500px;
}
.sidebar h2 {
margin-top: 0;
font-size: 18px;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
.project-box {
margin-bottom: 15px;
}
.project-title-container {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #eee;
padding-bottom: 5px;
margin-bottom: 10px;
}
.project-title-container h4 {
margin: 0;
color: #333;
font-size: 14px;
}
.delete-proj-btn {
background: none;
border: none;
color: #ef4444;
cursor: pointer;
padding: 4px;
font-size: 14px;
border-radius: 4px;
transition: background 0.2s;
line-height: 1;
}
.delete-proj-btn:hover {
background: #fee2e2;
}
.project-box ul {
list-style-type: none;
padding-left: 0;
margin: 0;
}
.project-box li.file-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 0;
border-bottom: 1px solid #f1f5f9;
}
.project-box li.file-item:last-child {
border-bottom: none;
}
.file-name {
flex-grow: 1;
cursor: pointer;
color: #0284c7;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 10px;
font-size: 13px;
}
.file-name:hover {
text-decoration: underline;
color: #0369a1;
}
.delete-file-btn {
background: none;
border: none;
color: #ef4444;
cursor: pointer;
padding: 4px 6px;
font-size: 12px;
border-radius: 4px;
transition: background 0.2s;
line-height: 1;
}
.delete-file-btn:hover {
background: #fee2e2;
}
/* --- Modal (Popup) Styles --- */
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.6);
backdrop-filter: blur(4px);
}
.modal-content {
background-color: #fefefe;
margin: 5% auto;
padding: 25px;
border: 1px solid #888;
width: 80%;
max-width: 900px;
border-radius: 12px;
box-shadow: 0 10px 25px rgba(0,0,0,0.2);
display: flex;
flex-direction: column;
}
.live-edit-modal {
width: 95% !important;
max-width: 1400px !important;
height: 90vh;
}
.close-btn {
color: #aaa;
font-size: 28px;
font-weight: bold;
cursor: pointer;
line-height: 1;
}
.close-btn:hover,
.close-btn:focus {
color: #333;
text-decoration: none;
}
.modal-body {
min-height: 200px;
display: flex;
justify-content: center;
align-items: center;
background: #f8fafc;
border-radius: 8px;
border: 1px dashed #cbd5e1;
padding: 20px;
overflow-x: auto;
flex: 1;
}
#previewImage {
max-width: 100%;
max-height: 60vh;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
#previewCodeContainer {
flex: 1;
overflow: hidden;
}
#previewCodeContainer > div {
height: 100%;
}
#previewMermaid {
width: 100%;
text-align: center;
}
.modal-footer {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
FILE:server.js
import 'dotenv/config';
import express from 'express';
import cors from 'cors';
import { GoogleGenAI } from '@google/genai';
import path from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs/promises';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express();
const PORT = process.env.PORT || 3000;
const DOWNLOADS_DIR = path.join(__dirname, 'downloads');
await fs.mkdir(DOWNLOADS_DIR, { recursive: true }).catch(console.error);
app.use(cors());
app.use(express.json({ limit: '50mb' })); // Allows for large image/PDF uploads
app.use(express.static(path.join(__dirname, 'public')));
app.use('/downloads', express.static(DOWNLOADS_DIR));
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
const SYSTEM_PROMPT = `You are an expert system architect and data visualizer. Your function is to generate valid Mermaid.js diagrams AND Draw.io (mxGraph) diagrams.
## Supported Diagram Types & Strict Rules
1. **Mermaid Diagrams**: Default to this. Use standard Mermaid syntax. Wrap in \`\`\`mermaid
2. **Draw.io Diagrams**: If the user explicitly asks for "draw.io", output raw Draw.io mxGraphModel XML. Wrap it in \`\`\`xml. Use the standard <mxGraphModel><root><mxCell/></root></mxGraphModel> structure.
## Contextual Generation
- The user may provide images, PDFs, or text documents. Extract the relationships, architecture, or timeline from these files to build the diagram.
- If the user provides an existing diagram code, update it based on the request.
- Output ONLY the raw code block. NEVER include explanations or introductory text.`;
app.get('/api/health', (req, res) => res.json({ status: 'ok' }));
app.post('/api/generate', async (req, res) => {
const { prompt, currentCode, type, files } = req.body;
if (!prompt && (!files || files.length === 0)) {
return res.status(400).json({ error: "Prompt or files are required" });
}
let promptText = currentCode
? `Here is the current diagram:\n\`\`\`'mermaid'\ncurrentCode\n\`\`\`\nUpdate it based on this request: prompt`
: `Generate a diagram for this request: prompt`;
let contents = [];
// Process Extracted Text (.txt, .docx, code files)
if (files && files.length > 0) {
const textFiles = files.filter(f => f.text);
if (textFiles.length > 0) {
const combinedText = textFiles.map(f => `--- Content from f.name ---\nf.text\n---`).join('\n\n');
promptText += `\n\nContext documents provided by user:\ncombinedText`;
}
}
contents.push(promptText);
// Process Binary Files (.pdf, .png, .jpg)
if (files && files.length > 0) {
files.forEach(file => {
if (file.data && !file.text) {
const base64Data = file.data.includes(',') ? file.data.split(',')[1] : file.data;
contents.push({
inlineData: {
data: base64Data,
mimeType: file.mimeType
}
});
}
});
}
try {
const response = await ai.models.generateContent({
model: 'gemini-2.5-flash',
contents: contents,
config: { systemInstruction: SYSTEM_PROMPT, temperature: 0.2 }
});
let text = response.text;
let code = "";
let outType = "mermaid";
if (text.includes('```xml')) {
const match = text.match(/```xml\n([\s\S]*?)\n```/);
code = match ? match[1] : text.replace(/```xml/g, '').replace(/```/g, '');
outType = "drawio";
} else {
const match = text.match(/```mermaid\n([\s\S]*?)\n```/);
code = match ? match[1] : text.replace(/```mermaid/g, '').replace(/```/g, '');
}
res.json({ code: code.trim(), type: outType });
} catch (error) {
console.error("Error generating diagram:", error);
res.status(500).json({ error: "Failed to generate diagram" });
}
});
// --- SECURE FILE SAVING ---
app.post('/api/save', async (req, res) => {
const { projectName, filename, content, isImage } = req.body;
// Strict Regex Validation
if (!projectName || !filename || !/^[a-zA-Z0-9_-]+$/.test(projectName) || !/^[a-zA-Z0-9_.-]+$/.test(filename)) {
return res.status(400).json({ error: "Invalid project or filename format." });
}
try {
const projectDir = path.join(DOWNLOADS_DIR, projectName);
const filePath = path.join(projectDir, filename);
// Failsafe: Ensure the resolved path strictly stays within the Downloads directory
if (!filePath.startsWith(DOWNLOADS_DIR)) {
return res.status(403).json({ error: "Access denied: Path traversal detected." });
}
await fs.mkdir(projectDir, { recursive: true });
if (isImage) {
const base64Data = content.replace(/^data:image\/\w+;base64,/, "");
await fs.writeFile(filePath, base64Data, 'base64');
} else {
await fs.writeFile(filePath, content, 'utf8');
}
res.json({ success: true, url: `/downloads/projectName/filename` });
} catch (error) {
console.error("Save error:", error);
res.status(500).json({ error: "Failed to save file" });
}
});
app.get('/api/files', async (req, res) => {
try {
const filesData = {};
const projects = await fs.readdir(DOWNLOADS_DIR);
for (const proj of projects) {
const projPath = path.join(DOWNLOADS_DIR, proj);
const stat = await fs.stat(projPath);
if (stat.isDirectory()) {
const files = await fs.readdir(projPath);
filesData[proj] = files.filter(f => !f.startsWith('.'));
}
}
res.json(filesData);
} catch (error) {
res.status(500).json({ error: "Failed to read directories" });
}
});
// --- SECURE FILE DELETION ---
app.delete('/api/files/:project/:filename', async (req, res) => {
const { project, filename } = req.params;
// Strict Regex Validation (Deny slashes and path traversal characters)
if (!/^[a-zA-Z0-9_-]+$/.test(project) || !/^[a-zA-Z0-9_.-]+$/.test(filename)) {
return res.status(400).json({ error: "Invalid project or filename format." });
}
try {
const filePath = path.join(DOWNLOADS_DIR, project, filename);
// Failsafe: Ensure the resolved path strictly stays within the Downloads directory
if (!filePath.startsWith(DOWNLOADS_DIR)) {
return res.status(403).json({ error: "Access denied: Path traversal detected." });
}
await fs.unlink(filePath);
// Safely check and clean up empty directories
const projectDir = path.join(DOWNLOADS_DIR, project);
if (projectDir.startsWith(DOWNLOADS_DIR)) {
const remainingFiles = await fs.readdir(projectDir);
if (remainingFiles.filter(f => !f.startsWith('.')).length === 0) {
await fs.rm(projectDir, { recursive: true, force: true });
}
}
res.json({ success: true, message: "File deleted successfully" });
} catch (error) {
console.error("Delete file error:", error);
res.status(500).json({ error: "Failed to delete file" });
}
});
// --- SECURE PROJECT DELETION ---
app.delete('/api/projects/:project', async (req, res) => {
const { project } = req.params;
// Strict Regex Validation
if (!/^[a-zA-Z0-9_-]+$/.test(project)) {
return res.status(400).json({ error: "Invalid project name format." });
}
try {
const projectDir = path.join(DOWNLOADS_DIR, project);
// Failsafe: Path traversal check
if (!projectDir.startsWith(DOWNLOADS_DIR)) {
return res.status(403).json({ error: "Access denied: Path traversal detected." });
}
await fs.rm(projectDir, { recursive: true, force: true });
res.json({ success: true, message: "Project deleted successfully" });
} catch (error) {
console.error("Delete project error:", error);
res.status(500).json({ error: "Failed to delete project" });
}
});
app.listen(PORT, () => console.log(`🚀 Server running on http://localhost:PORT`));A local multi-modal podcast pipeline. Ingests media, drafts scripts, synthesizes audio, renders cover art, and uploads to YouTube.
# OmniCast Studio
## Description
OmniCast Studio is a local Node.js application that provides a multi-modal pipeline for processing text, audio, and video into podcast scripts and social media assets. It exposes a set of local API endpoints to orchestrate these tasks.
## Setup Requirements
This application requires the following environment variables to be set in a local `.env` file:
* `GEMINI_API_KEY`: Required for text analysis, translation, and script drafting.
* `OPENAI_API_KEY`: Required for audio transcription and synthesis.
* `PORT`: Defaults to 7860.
**System Requirements:**
* Node.js >= 20.0.0
* FFmpeg installed and available in the system PATH.
## API Endpoints (Localhost:7860)
The service runs strictly on `http://127.0.0.1:7860`. The following endpoints are available:
### 1. Media Ingestion
* **Endpoint:** `POST /api/ingest`
* **Purpose:** Accepts a URL or file upload. It extracts the text, detects the language, and translates it to English if necessary.
### 2. Script Drafting
* **Endpoint:** `POST /api/draft-script`
* **Purpose:** Utilizes the ingested text to format a conversational, two-host script suitable for audio synthesis.
### 3. Audio Synthesis
* **Endpoint:** `POST /api/synthesize`
* **Purpose:** Converts the drafted script into a final audio file using TTS services.
### 4. LinkedIn Packaging
* **Endpoint:** `POST /api/generate-linkedin`
* **Purpose:** Generates a social media text post and renders a looping MP4 video of the podcast cover art with the synthesized audio.
FILE:README.md
# OmniCast Studio
OmniCast Studio is a local Node.js web application. It provides a multi-modal media pipeline that processes text, audio, and video into podcast scripts and social media assets.
It exposes a set of local API endpoints (running strictly on `http://127.0.0.1:7860`) intended for local media processing.
## Features
* **Media Ingestion:** Extracts text from URLs and local files.
* **Script Drafting:** Formats text into a conversational script.
* **Audio Synthesis:** Converts scripts into audio files.
* **LinkedIn Packaging:** Generates social media text and MP4 video assets.
FILE:config/secrets.js
require('dotenv').config();
module.exports = {
getElevenLabsKey: () => process.env.ELEVENLABS_API_KEY
};
FILE:config/state.js
const path = require('path');
const fs = require('fs');
const downloadsDir = path.join(__dirname, '../downloads');
if (!fs.existsSync(downloadsDir)) fs.mkdirSync(downloadsDir, { recursive: true });
module.exports = {
downloadsDir,
jobs: {},
sseClients: {},
jobQueue: [],
activeJobs: 0,
MAX_CONCURRENT_JOBS: 2,
sanitizeId: (id) => {
if (!id || typeof id !== 'string') return 'default_session';
const safeId = id.replace(/[^a-zA-Z0-9_-]/g, '');
if (!safeId) return 'default_session';
return safeId.substring(0, 64);
}
};
FILE:index.js
require('dotenv').config();
const express = require('express');
const fs = require('fs');
const path = require('path');
const rateLimit = require('express-rate-limit');
const state = require('./config/state');
const ingestRoutes = require('./routes/ingest');
const draftRoutes = require('./routes/draft');
const synthesizeRoutes = require('./routes/synthesize');
const utilityRoutes = require('./routes/utilities');
const imageRoutes = require('./routes/images');
const linkedinRoutes = require('./routes/linkedin');
const youtubeRoutes = require('./routes/youtube');
const app = express();
const port = process.env.PORT || 7860;
app.use(express.static('public'));
// Security: Restrict static file access strictly to the local machine
app.use('/downloads', (req, res, next) => {
const isLocal = req.ip === '127.0.0.1' || req.ip === '::ffff:127.0.0.1' || req.ip === '::1';
if (!isLocal) {
return res.status(403).json({ error: "Security Exception: Remote file access forbidden." });
}
next();
}, express.static(state.downloadsDir));
app.use(express.json({ limit: '50mb' }));
app.use('/api', ingestRoutes);
app.use('/api', draftRoutes);
app.use('/api', synthesizeRoutes);
app.use('/api', utilityRoutes);
app.use('/api', imageRoutes);
app.use('/api', linkedinRoutes);
app.use('/api', youtubeRoutes);
app.get('/api/stream-logs', (req, res) => {
const { id } = req.query;
res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' });
state.sseClients[id] = res;
res.write(`data: "Connection established.")}\n\n`);
req.on('close', () => { delete state.sseClients[id]; });
});
// Security: Explicitly bind to localhost to prevent external network access
app.listen(port, '127.0.0.1', () => console.log(`🚀 Studio running securely at http://127.0.0.1:port`));
FILE:package-lock.json
{
"name": "omnicast-studio",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "omnicast-studio",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@distube/ytdl-core": "^4.16.12",
"@google/genai": "^1.45.0",
"archiver": "^7.0.1",
"axios": "^1.6.8",
"cheerio": "^1.0.0-rc.12",
"dotenv": "^17.3.1",
"express": "^5.2.1",
"express-rate-limit": "^8.3.1",
"fluent-ffmpeg": "^2.1.3",
"googleapis": "^171.4.0",
"multer": "^1.4.5-lts.1",
"openai": "^6.29.0",
"pdf-parse": "^1.1.1",
"youtube-transcript-plus": "^1.2.0"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@distube/ytdl-core": {
"version": "4.16.12",
"resolved": "https://registry.npmjs.org/@distube/ytdl-core/-/ytdl-core-4.16.12.tgz",
"integrity": "sha512-/NR8Jur1Q4E2oD+DJta7uwWu7SkqdEkhwERt7f4iune70zg7ZlLLTOHs1+jgg3uD2jQjpdk7RGC16FqstG4RsA==",
"license": "MIT",
"dependencies": {
"http-cookie-agent": "^7.0.1",
"https-proxy-agent": "^7.0.6",
"m3u8stream": "^0.8.6",
"miniget": "^4.2.3",
"sax": "^1.4.1",
"tough-cookie": "^5.1.2",
"undici": "^7.8.0"
},
"engines": {
"node": ">=20.18.1"
},
"funding": {
"url": "https://github.com/distubejs/ytdl-core?sponsor"
}
},
"node_modules/@google/genai": {
"version": "1.46.0",
"resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.46.0.tgz",
"integrity": "sha512-ewPMN5JkKfgU5/kdco9ZhXBHDPhVqZpMQqIFQhwsHLf8kyZfx1cNpw1pHo1eV6PGEW7EhIBFi3aYZraFndAXqg==",
"license": "Apache-2.0",
"dependencies": {
"google-auth-library": "^10.3.0",
"p-retry": "^4.6.2",
"protobufjs": "^7.5.4",
"ws": "^8.18.0"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"@modelcontextprotocol/sdk": "^1.25.2"
},
"peerDependenciesMeta": {
"@modelcontextprotocol/sdk": {
"optional": true
}
}
},
"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==",
"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"
},
"engines": {
"node": ">=12"
}
},
"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==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=14"
}
},
"node_modules/@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/base64": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/codegen": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/eventemitter": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
"integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/fetch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
"integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.1",
"@protobufjs/inquire": "^1.1.0"
}
},
"node_modules/@protobufjs/float": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/inquire": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
"integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/path": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/pool": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/utf8": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
"license": "BSD-3-Clause"
},
"node_modules/@types/node": {
"version": "25.5.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz",
"integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.18.0"
}
},
"node_modules/@types/retry": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
"license": "MIT"
},
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"license": "MIT",
"dependencies": {
"event-target-shim": "^5.0.0"
},
"engines": {
"node": ">=6.5"
}
},
"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": {
"mime-types": "^3.0.0",
"negotiator": "^1.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/ansi-styles": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/append-field": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
"license": "MIT"
},
"node_modules/archiver": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz",
"integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==",
"license": "MIT",
"dependencies": {
"archiver-utils": "^5.0.2",
"async": "^3.2.4",
"buffer-crc32": "^1.0.0",
"readable-stream": "^4.0.0",
"readdir-glob": "^1.1.2",
"tar-stream": "^3.0.0",
"zip-stream": "^6.0.1"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/archiver-utils": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz",
"integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==",
"license": "MIT",
"dependencies": {
"glob": "^10.0.0",
"graceful-fs": "^4.2.0",
"is-stream": "^2.0.1",
"lazystream": "^1.0.0",
"lodash": "^4.17.15",
"normalize-path": "^3.0.0",
"readable-stream": "^4.0.0"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/async": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
"license": "MIT"
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/axios": {
"version": "1.13.6",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz",
"integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.11",
"form-data": "^4.0.5",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/b4a": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz",
"integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==",
"license": "Apache-2.0",
"peerDependencies": {
"react-native-b4a": "*"
},
"peerDependenciesMeta": {
"react-native-b4a": {
"optional": true
}
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/bare-events": {
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz",
"integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==",
"license": "Apache-2.0",
"peerDependencies": {
"bare-abort-controller": "*"
},
"peerDependenciesMeta": {
"bare-abort-controller": {
"optional": true
}
}
},
"node_modules/bare-fs": {
"version": "4.5.5",
"resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.5.tgz",
"integrity": "sha512-XvwYM6VZqKoqDll8BmSww5luA5eflDzY0uEFfBJtFKe4PAAtxBjU3YIxzIBzhyaEQBy1VXEQBto4cpN5RZJw+w==",
"license": "Apache-2.0",
"dependencies": {
"bare-events": "^2.5.4",
"bare-path": "^3.0.0",
"bare-stream": "^2.6.4",
"bare-url": "^2.2.2",
"fast-fifo": "^1.3.2"
},
"engines": {
"bare": ">=1.16.0"
},
"peerDependencies": {
"bare-buffer": "*"
},
"peerDependenciesMeta": {
"bare-buffer": {
"optional": true
}
}
},
"node_modules/bare-os": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.8.0.tgz",
"integrity": "sha512-Dc9/SlwfxkXIGYhvMQNUtKaXCaGkZYGcd1vuNUUADVqzu4/vQfvnMkYYOUnt2VwQ2AqKr/8qAVFRtwETljgeFg==",
"license": "Apache-2.0",
"engines": {
"bare": ">=1.14.0"
}
},
"node_modules/bare-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz",
"integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==",
"license": "Apache-2.0",
"dependencies": {
"bare-os": "^3.0.1"
}
},
"node_modules/bare-stream": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.8.1.tgz",
"integrity": "sha512-bSeR8RfvbRwDpD7HWZvn8M3uYNDrk7m9DQjYOFkENZlXW8Ju/MPaqUPQq5LqJ3kyjEm07siTaAQ7wBKCU59oHg==",
"license": "Apache-2.0",
"dependencies": {
"streamx": "^2.21.0",
"teex": "^1.0.1"
},
"peerDependencies": {
"bare-buffer": "*",
"bare-events": "*"
},
"peerDependenciesMeta": {
"bare-buffer": {
"optional": true
},
"bare-events": {
"optional": true
}
}
},
"node_modules/bare-url": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz",
"integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==",
"license": "Apache-2.0",
"dependencies": {
"bare-path": "^3.0.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==",
"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/bignumber.js": {
"version": "9.3.1",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
"integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/body-parser": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
"integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
"license": "MIT",
"dependencies": {
"bytes": "^3.1.2",
"content-type": "^1.0.5",
"debug": "^4.4.3",
"http-errors": "^2.0.0",
"iconv-lite": "^0.7.0",
"on-finished": "^2.4.1",
"qs": "^6.14.1",
"raw-body": "^3.0.1",
"type-is": "^2.0.1"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/body-parser/node_modules/iconv-lite": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
"license": "ISC"
},
"node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"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",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"node_modules/buffer-crc32": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz",
"integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==",
"license": "MIT",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
"license": "BSD-3-Clause"
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"license": "MIT"
},
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
"integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
"dependencies": {
"streamsearch": "^1.1.0"
},
"engines": {
"node": ">=10.16.0"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"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": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"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": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/cheerio": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz",
"integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==",
"license": "MIT",
"dependencies": {
"cheerio-select": "^2.1.0",
"dom-serializer": "^2.0.0",
"domhandler": "^5.0.3",
"domutils": "^3.2.2",
"encoding-sniffer": "^0.2.1",
"htmlparser2": "^10.1.0",
"parse5": "^7.3.0",
"parse5-htmlparser2-tree-adapter": "^7.1.0",
"parse5-parser-stream": "^7.1.2",
"undici": "^7.19.0",
"whatwg-mimetype": "^4.0.0"
},
"engines": {
"node": ">=20.18.1"
},
"funding": {
"url": "https://github.com/cheeriojs/cheerio?sponsor=1"
}
},
"node_modules/cheerio-select": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
"integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
"license": "BSD-2-Clause",
"dependencies": {
"boolbase": "^1.0.0",
"css-select": "^5.1.0",
"css-what": "^6.1.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"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==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"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==",
"license": "MIT"
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/compress-commons": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz",
"integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==",
"license": "MIT",
"dependencies": {
"crc-32": "^1.2.0",
"crc32-stream": "^6.0.0",
"is-stream": "^2.0.1",
"normalize-path": "^3.0.0",
"readable-stream": "^4.0.0"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/concat-stream": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
"integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
"engines": [
"node >= 0.8"
],
"license": "MIT",
"dependencies": {
"buffer-from": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^2.2.2",
"typedarray": "^0.0.6"
}
},
"node_modules/concat-stream/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==",
"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/concat-stream/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==",
"license": "MIT"
},
"node_modules/concat-stream/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==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/content-disposition": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
"integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"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": ">= 0.6"
}
},
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"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",
"engines": {
"node": ">=6.6.0"
}
},
"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==",
"license": "MIT"
},
"node_modules/crc-32": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
"license": "Apache-2.0",
"bin": {
"crc32": "bin/crc32.njs"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/crc32-stream": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz",
"integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==",
"license": "MIT",
"dependencies": {
"crc-32": "^1.2.0",
"readable-stream": "^4.0.0"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/cross-spawn/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": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/css-select": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
"integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
"license": "BSD-2-Clause",
"dependencies": {
"boolbase": "^1.0.0",
"css-what": "^6.1.0",
"domhandler": "^5.0.2",
"domutils": "^3.0.1",
"nth-check": "^2.0.1"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/css-what": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
"integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">= 6"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"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"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"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": ">= 0.8"
}
},
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
"entities": "^4.2.0"
},
"funding": {
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
}
},
"node_modules/domelementtype": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "BSD-2-Clause"
},
"node_modules/domhandler": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"license": "BSD-2-Clause",
"dependencies": {
"domelementtype": "^2.3.0"
},
"engines": {
"node": ">= 4"
},
"funding": {
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/domutils": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
"license": "BSD-2-Clause",
"dependencies": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3"
},
"funding": {
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/dotenv": {
"version": "17.3.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz",
"integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"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"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"license": "MIT"
},
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"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/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"license": "MIT"
},
"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.8"
}
},
"node_modules/encoding-sniffer": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz",
"integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==",
"license": "MIT",
"dependencies": {
"iconv-lite": "^0.6.3",
"whatwg-encoding": "^3.1.1"
},
"funding": {
"url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"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/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/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"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": ">= 0.6"
}
},
"node_modules/event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/events": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
"license": "MIT",
"engines": {
"node": ">=0.8.x"
}
},
"node_modules/events-universal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
"integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
"license": "Apache-2.0",
"dependencies": {
"bare-events": "^2.7.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": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/express-rate-limit": {
"version": "8.3.1",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz",
"integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==",
"license": "MIT",
"dependencies": {
"ip-address": "10.1.0"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/express-rate-limit"
},
"peerDependencies": {
"express": ">= 4.11"
}
},
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"license": "MIT"
},
"node_modules/fast-fifo": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
"license": "MIT"
},
"node_modules/fetch-blob": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"dependencies": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
},
"engines": {
"node": "^12.20 || >= 14.13"
}
},
"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": {
"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.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/fluent-ffmpeg": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.3.tgz",
"integrity": "sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q==",
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
"license": "MIT",
"dependencies": {
"async": "^0.2.9",
"which": "^1.1.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/fluent-ffmpeg/node_modules/async": {
"version": "0.2.10",
"resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz",
"integrity": "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ=="
},
"node_modules/follow-redirects": {
"version": "1.15.11",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"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==",
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/form-data": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/form-data/node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/form-data/node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"license": "MIT",
"dependencies": {
"fetch-blob": "^3.1.2"
},
"engines": {
"node": ">=12.20.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": ">= 0.6"
}
},
"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",
"engines": {
"node": ">= 0.8"
}
},
"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://github.com/sponsors/ljharb"
}
},
"node_modules/gaxios": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.4.tgz",
"integrity": "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA==",
"license": "Apache-2.0",
"dependencies": {
"extend": "^3.0.2",
"https-proxy-agent": "^7.0.1",
"node-fetch": "^3.3.2"
},
"engines": {
"node": ">=18"
}
},
"node_modules/gcp-metadata": {
"version": "8.1.2",
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz",
"integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==",
"license": "Apache-2.0",
"dependencies": {
"gaxios": "^7.0.0",
"google-logging-utils": "^1.0.0",
"json-bigint": "^1.0.0"
},
"engines": {
"node": ">=18"
}
},
"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": {
"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.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"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": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/glob": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting [email protected]",
"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"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/google-auth-library": {
"version": "10.6.2",
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.2.tgz",
"integrity": "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw==",
"license": "Apache-2.0",
"dependencies": {
"base64-js": "^1.3.0",
"ecdsa-sig-formatter": "^1.0.11",
"gaxios": "^7.1.4",
"gcp-metadata": "8.1.2",
"google-logging-utils": "1.1.3",
"jws": "^4.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/google-logging-utils": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz",
"integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==",
"license": "Apache-2.0",
"engines": {
"node": ">=14"
}
},
"node_modules/googleapis": {
"version": "171.4.0",
"resolved": "https://registry.npmjs.org/googleapis/-/googleapis-171.4.0.tgz",
"integrity": "sha512-xybFL2SmmUgIifgsbsRQYRdNrSAYwxWZDmkZTGjUIaRnX5jPqR8el/cEvo6rCqh7iaZx6MfEPS/lrDgZ0bymkg==",
"license": "Apache-2.0",
"dependencies": {
"google-auth-library": "^10.2.0",
"googleapis-common": "^8.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/googleapis-common": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-8.0.1.tgz",
"integrity": "sha512-eCzNACUXPb1PW5l0ULTzMHaL/ltPRADoPgjBlT8jWsTbxkCp6siv+qKJ/1ldaybCthGwsYFYallF7u9AkU4L+A==",
"license": "Apache-2.0",
"dependencies": {
"extend": "^3.0.2",
"gaxios": "^7.0.0-rc.4",
"google-auth-library": "^10.1.0",
"qs": "^6.7.0",
"url-template": "^2.0.8"
},
"engines": {
"node": ">=18.0.0"
}
},
"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": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"license": "ISC"
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/htmlparser2": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz",
"integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==",
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.2.2",
"entities": "^7.0.1"
}
},
"node_modules/htmlparser2/node_modules/entities": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/http-cookie-agent": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/http-cookie-agent/-/http-cookie-agent-7.0.3.tgz",
"integrity": "sha512-EeZo7CGhfqPW6R006rJa4QtZZUpBygDa2HZH3DJqsTzTjyRE6foDBVQIv/pjVsxHC8z2GIdbB1Hvn9SRorP3WQ==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.4"
},
"engines": {
"node": ">=20.0.0"
},
"funding": {
"url": "https://github.com/sponsors/3846masa"
},
"peerDependencies": {
"tough-cookie": "^4.0.0 || ^5.0.0 || ^6.0.0",
"undici": "^7.0.0"
},
"peerDependenciesMeta": {
"undici": {
"optional": 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": {
"depd": "~2.0.0",
"inherits": "~2.0.4",
"setprototypeof": "~1.2.0",
"statuses": "~2.0.2",
"toidentifier": "~1.0.1"
},
"engines": {
"node": ">= 0.8"
},
"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==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"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/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.1.0",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
"integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"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",
"engines": {
"node": ">= 0.10"
}
},
"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==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"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/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"license": "MIT",
"engines": {
"node": ">=8"
},
"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==",
"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/jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"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/json-bigint": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
"integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
"license": "MIT",
"dependencies": {
"bignumber.js": "^9.0.0"
}
},
"node_modules/jwa": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
"integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
"license": "MIT",
"dependencies": {
"buffer-equal-constant-time": "^1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/jws": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
"integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
"license": "MIT",
"dependencies": {
"jwa": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/lazystream": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz",
"integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==",
"license": "MIT",
"dependencies": {
"readable-stream": "^2.0.5"
},
"engines": {
"node": ">= 0.6.3"
}
},
"node_modules/lazystream/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==",
"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/lazystream/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==",
"license": "MIT"
},
"node_modules/lazystream/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==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/lodash": {
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"license": "MIT"
},
"node_modules/long": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"license": "Apache-2.0"
},
"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==",
"license": "ISC"
},
"node_modules/m3u8stream": {
"version": "0.8.6",
"resolved": "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.8.6.tgz",
"integrity": "sha512-LZj8kIVf9KCphiHmH7sbFQTVe4tOemb202fWwvJwR9W5ENW/1hxJN6ksAWGhQgSBSa3jyWhnjKU1Fw1GaOdbyA==",
"license": "MIT",
"dependencies": {
"miniget": "^4.2.2",
"sax": "^1.2.4"
},
"engines": {
"node": ">=12"
}
},
"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",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/media-typer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"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"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"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": ">= 0.6"
}
},
"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",
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/miniget": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/miniget/-/miniget-4.2.3.tgz",
"integrity": "sha512-SjbDPDICJ1zT+ZvQwK0hUcRY4wxlhhNpHL9nJOB2MEAXRGagTljsO8MEDzQMTFf0Q8g4QNi8P9lEm/g7e+qgzA==",
"license": "MIT",
"engines": {
"node": ">=12"
}
},
"node_modules/minimatch": {
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/minipass": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"license": "MIT",
"dependencies": {
"minimist": "^1.2.6"
},
"bin": {
"mkdirp": "bin/cmd.js"
}
},
"node_modules/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/multer": {
"version": "1.4.5-lts.2",
"resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz",
"integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==",
"deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.",
"license": "MIT",
"dependencies": {
"append-field": "^1.0.0",
"busboy": "^1.0.0",
"concat-stream": "^1.5.2",
"mkdirp": "^0.5.4",
"object-assign": "^4.1.1",
"type-is": "^1.6.4",
"xtend": "^4.0.0"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/multer/node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/multer/node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/multer/node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/multer/node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
"license": "MIT",
"dependencies": {
"media-typer": "0.3.0",
"mime-types": "~2.1.24"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/negotiator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"deprecated": "Use your platform's native DOMException instead",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-ensure": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz",
"integrity": "sha512-DRI60hzo2oKN1ma0ckc6nQWlHU69RH6xN0sjQTjMpChPfTYvKZdcQFfdYK2RWbJcKyUizSIy/l8OTGxMAM1QDw==",
"license": "MIT"
},
"node_modules/node-fetch": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
"license": "MIT",
"dependencies": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
"formdata-polyfill": "^4.0.10"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
"license": "BSD-2-Clause",
"dependencies": {
"boolbase": "^1.0.0"
},
"funding": {
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
"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": ">=0.10.0"
}
},
"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",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"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"
}
},
"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": {
"wrappy": "1"
}
},
"node_modules/openai": {
"version": "6.32.0",
"resolved": "https://registry.npmjs.org/openai/-/openai-6.32.0.tgz",
"integrity": "sha512-j3k+BjydAf8yQlcOI7WUQMQTbbF5GEIMAE2iZYCOzwwB3S2pCheaWYp+XZRNAch4jWVc52PMDGRRjutao3lLCg==",
"license": "Apache-2.0",
"bin": {
"openai": "bin/cli"
},
"peerDependencies": {
"ws": "^8.18.0",
"zod": "^3.25 || ^4.0"
},
"peerDependenciesMeta": {
"ws": {
"optional": true
},
"zod": {
"optional": true
}
}
},
"node_modules/p-retry": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
"integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==",
"license": "MIT",
"dependencies": {
"@types/retry": "0.12.0",
"retry": "^0.13.1"
},
"engines": {
"node": ">=8"
}
},
"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==",
"license": "BlueOak-1.0.0"
},
"node_modules/parse5": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
"license": "MIT",
"dependencies": {
"entities": "^6.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parse5-htmlparser2-tree-adapter": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
"integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
"license": "MIT",
"dependencies": {
"domhandler": "^5.0.3",
"parse5": "^7.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parse5-parser-stream": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
"integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
"license": "MIT",
"dependencies": {
"parse5": "^7.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parse5/node_modules/entities": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"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",
"engines": {
"node": ">= 0.8"
}
},
"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": ">=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==",
"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/path-to-regexp": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
"integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/pdf-parse": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-1.1.4.tgz",
"integrity": "sha512-XRIRcLgk6ZnUbsHsYXExMw+krrPE81hJ6FQPLdBNhhBefqIQKXu/WeTgNBGSwPrfU0v+UCEwn7AoAUOsVKHFvQ==",
"license": "MIT",
"dependencies": {
"node-ensure": "^0.0.0"
},
"engines": {
"node": ">=6.8.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/mehmet-kozan"
}
},
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
"license": "MIT",
"engines": {
"node": ">= 0.6.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==",
"license": "MIT"
},
"node_modules/protobufjs": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz",
"integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==",
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
"@protobufjs/codegen": "^2.0.4",
"@protobufjs/eventemitter": "^1.1.0",
"@protobufjs/fetch": "^1.1.0",
"@protobufjs/float": "^1.0.2",
"@protobufjs/inquire": "^1.1.0",
"@protobufjs/path": "^1.1.2",
"@protobufjs/pool": "^1.1.0",
"@protobufjs/utf8": "^1.1.0",
"@types/node": ">=13.7.0",
"long": "^5.0.0"
},
"engines": {
"node": ">=12.0.0"
}
},
"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.10"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/qs": {
"version": "6.15.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
"integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"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": {
"bytes": "~3.1.2",
"http-errors": "~2.0.1",
"iconv-lite": "~0.7.0",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/raw-body/node_modules/iconv-lite": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/readable-stream": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
"integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
"license": "MIT",
"dependencies": {
"abort-controller": "^3.0.0",
"buffer": "^6.0.3",
"events": "^3.3.0",
"process": "^0.11.10",
"string_decoder": "^1.3.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/readdir-glob": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz",
"integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==",
"license": "Apache-2.0",
"dependencies": {
"minimatch": "^5.1.0"
}
},
"node_modules/readdir-glob/node_modules/minimatch": {
"version": "5.1.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
"integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/retry": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
"integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"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": {
"debug": "^4.4.0",
"depd": "^2.0.0",
"is-promise": "^4.0.0",
"parseurl": "^1.3.3",
"path-to-regexp": "^8.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"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/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/sax": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
"integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=11.0.0"
}
},
"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": {
"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/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": {
"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/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": {
"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==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3",
"side-channel-list": "^1.0.0",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-list": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"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": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"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",
"dependencies": {
"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": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"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==",
"license": "ISC",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"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/streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
"integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/streamx": {
"version": "2.23.0",
"resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz",
"integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==",
"license": "MIT",
"dependencies": {
"events-universal": "^1.0.0",
"fast-fifo": "^1.3.2",
"text-decoder": "^1.1.0"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.2.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==",
"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==",
"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==",
"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==",
"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==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
"integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.2.2"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"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==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"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==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/tar-stream": {
"version": "3.1.8",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz",
"integrity": "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==",
"license": "MIT",
"dependencies": {
"b4a": "^1.6.4",
"bare-fs": "^4.5.5",
"fast-fifo": "^1.2.0",
"streamx": "^2.15.0"
}
},
"node_modules/teex": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
"integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
"license": "MIT",
"dependencies": {
"streamx": "^2.12.5"
}
},
"node_modules/text-decoder": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
"integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==",
"license": "Apache-2.0",
"dependencies": {
"b4a": "^1.6.4"
}
},
"node_modules/tldts": {
"version": "6.1.86",
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
"integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
"license": "MIT",
"dependencies": {
"tldts-core": "^6.1.86"
},
"bin": {
"tldts": "bin/cli.js"
}
},
"node_modules/tldts-core": {
"version": "6.1.86",
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
"integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
"license": "MIT"
},
"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": ">=0.6"
}
},
"node_modules/tough-cookie": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
"integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
"license": "BSD-3-Clause",
"dependencies": {
"tldts": "^6.1.32"
},
"engines": {
"node": ">=16"
}
},
"node_modules/type-is": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
"license": "MIT",
"dependencies": {
"content-type": "^1.0.5",
"media-typer": "^1.1.0",
"mime-types": "^3.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/typedarray": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
"license": "MIT"
},
"node_modules/undici": {
"version": "7.24.4",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.24.4.tgz",
"integrity": "sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==",
"license": "MIT",
"engines": {
"node": ">=20.18.1"
}
},
"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==",
"license": "MIT"
},
"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",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/url-template": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz",
"integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==",
"license": "BSD"
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/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": ">= 0.8"
}
},
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
"license": "MIT",
"engines": {
"node": ">= 8"
}
},
"node_modules/whatwg-encoding": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
"deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
"license": "MIT",
"dependencies": {
"iconv-lite": "0.6.3"
},
"engines": {
"node": ">=18"
}
},
"node_modules/whatwg-mimetype": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"which": "bin/which"
}
},
"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==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"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==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"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==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi-cjs/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==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"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==",
"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==",
"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/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==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"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/ws": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"license": "MIT",
"engines": {
"node": ">=0.4"
}
},
"node_modules/youtube-transcript-plus": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/youtube-transcript-plus/-/youtube-transcript-plus-1.2.0.tgz",
"integrity": "sha512-SRjVft8V+vUulMKgakgfzC+pnFLSy4tolX7xGnSvp9juUNocikMFmUx5GlhzLDILzxYrijcYtmNqz0qyklnPmA==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/zip-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz",
"integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==",
"license": "MIT",
"dependencies": {
"archiver-utils": "^5.0.0",
"compress-commons": "^6.0.2",
"readable-stream": "^4.0.0"
},
"engines": {
"node": ">= 14"
}
}
}
}
FILE:package.json
{
"name": "omnicast",
"version": "1.0.8",
"description": "A secure, multi-modal AI podcast studio supporting YouTube, PDFs, MP4s, Web URLs, and Text.",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "node --watch index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"podcast",
"gemini",
"ai",
"youtube",
"tts",
"whisper",
"elevenlabs"
],
"author": "Kaushik Datta",
"license": "MIT",
"os": ["darwin", "win32", "linux"],
"engines": {
"node": ">=20.0.0"
},
"config": {
"requiredDependencies": ["ffmpeg"],
"requiredEnvVars": ["GEMINI_API_KEY", "OPENAI_API_KEY"]
},
"dependencies": {
"@distube/ytdl-core": "^4.16.12",
"@google/genai": "^1.45.0",
"archiver": "^7.0.1",
"axios": "^1.6.8",
"cheerio": "^1.0.0-rc.12",
"dotenv": "^17.3.1",
"express": "^5.2.1",
"express-rate-limit": "^8.3.1",
"fluent-ffmpeg": "^2.1.3",
"googleapis": "^171.4.0",
"multer": "^1.4.5-lts.1",
"openai": "^6.29.0",
"pdf-parse": "^1.1.1",
"youtube-transcript-plus": "^1.2.0"
}
}
FILE:public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>OmniCast Studio</title>
<link rel="stylesheet" href="style.css">
<script src="https://accounts.google.com/gsi/client" async defer></script>
</head>
<body>
<div style="padding: 20px; max-width: 800px; margin: auto; font-family: sans-serif;">
<h1>OmniCast Studio</h1>
<div style="background: #2c3e50; padding: 15px; border-radius: 8px; margin-bottom: 20px; color: white;">
<h3 style="margin-top: 0; display: flex; justify-content: space-between;">
<span>🗂️ Session Manager</span>
<span id="current-session-label" style="font-size: 0.8em; color: #f1c40f;">Active: New Session</span>
</h3>
<div style="display: flex; gap: 10px; align-items: center; flex-wrap: wrap;">
<select id="session-select" style="flex: 1; min-width: 200px; padding: 10px; border-radius: 4px;">
<option value="">-- Start a New Session --</option>
</select>
<button id="btn-load-session" style="padding: 10px 15px; background: #3498db; color: white; border: none; cursor: pointer; border-radius: 4px; font-weight: bold;">📂 Load</button>
<button id="btn-delete-session" style="padding: 10px 15px; background: #e67e22; color: white; border: none; cursor: pointer; border-radius: 4px; font-weight: bold;">🗑️ Delete Current</button>
<button id="btn-clear-all-sessions" style="padding: 10px 15px; background: #c0392b; color: white; border: none; cursor: pointer; border-radius: 4px; font-weight: bold;">☢️ Clear All Folders</button>
</div>
</div>
<div style="margin-bottom: 20px; background: #1a252f; padding: 15px; border-radius: 8px;">
<input type="text" id="sourceUrl" placeholder="Enter URL (Web/MP4/YouTube)" style="width: 100%; padding: 10px; margin-bottom: 10px;">
<div style="display: flex; align-items: center; gap: 10px; margin-bottom: 10px;">
<span style="color: #bdc3c7; font-weight: bold;">OR Upload File:</span>
<input type="file" id="sourceFile" accept=".mp4,.pdf,.txt" style="flex: 1; color: #fff;">
</div>
<button id="btn-ingest" style="width: 100%; padding: 10px;">Extract Text (Force English)</button>
</div>
<div id="terminal-log" style="background: #000; color: #0f0; padding: 10px; height: 100px; overflow-y: auto; font-family: monospace;"></div>
<div id="log-active-spinner" style="display: none; color: #0f0; margin-top: 10px; font-family: monospace;">
<span class="spinner"></span> <em>Working...</em>
</div>
<div id="original-text-section" style="display: none; margin-top: 20px; padding: 15px; background: #34495e; border-radius: 8px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px;">
<label style="color: white; font-weight: bold;">Original Ingested Text (Read-Only):</label>
<div style="display: flex; gap: 5px;">
<select id="viewLanguage" style="padding: 5px;">
<option value="English" selected>English</option>
<option value="Spanish">Spanish</option>
<option value="French">French</option>
<option value="German">German</option>
<option value="Bengali">Bengali</option>
<option value="Hindi">Hindi</option>
<option value="Japanese">Japanese</option>
</select>
<button id="btn-translate-original" style="padding: 5px 10px; background: #3498db; color: white; border: none; cursor: pointer;">Translate View</button>
</div>
</div>
<textarea id="original-text-viewer" readonly style="width: 100%; height: 120px; padding: 10px; font-family: sans-serif; background: #eee; border: none;"></textarea>
</div>
<div style="display: flex; gap: 10px; margin-top: 20px;">
<input type="text" id="host1" placeholder="Host 1 (Female) e.g. Alex" value="Alex" style="flex: 1; padding: 10px;">
<input type="text" id="host2" placeholder="Host 2 (Male) e.g. Sam" value="Sam" style="flex: 1; padding: 10px;">
<select id="targetLanguage" style="flex: 1; padding: 10px;">
<option value="English" selected>English</option>
<option value="Spanish">Spanish</option>
<option value="French">French</option>
</select>
</div>
<button id="btn-draft" style="margin-top: 10px; width: 100%; padding: 10px;">Draft Script</button>
<div style="position: relative;">
<textarea id="script-editor" style="width: 100%; height: 200px; margin-top: 10px; padding: 10px; font-family: sans-serif;"></textarea>
<button id="btn-save-script" style="position: absolute; bottom: 10px; right: 10px; background: #2ecc71; color: white; border: none; padding: 5px 10px; cursor: pointer; border-radius: 3px; display: none;">💾 Save Script</button>
</div>
<div id="script-stats" style="text-align: right; font-size: 0.9em; color: #bdc3c7; margin-top: 5px; font-weight: bold;">
Words: <span id="word-count">0</span> | Est. Audio Length: <span id="est-time">0:00</span>
<span id="linkedin-warning" style="color: #e74c3c; display: none; margin-left: 10px;">⚠️ LinkedIn requires 3 - 14 mins (Aim for 450 - 2100 words)</span>
</div>
<button id="btn-generate-audio" style="margin-top: 10px; width: 100%; padding: 10px;">Generate Audio</button>
<div id="audio-container" style="display:none; margin-top: 20px;">
<audio id="podcast-audio" controls style="width: 100%;">
<track id="podcast-vtt" kind="captions" srclang="en" label="English" default>
</audio>
<div id="live-captions" style="margin-top: 10px; padding: 15px; background: #1e1e1e; color: #bdc3c7; height: 200px; overflow-y: auto; border-radius: 5px;"></div>
</div>
<div id="thumbnail-section" style="margin-top: 20px; padding: 15px; background: #2c3e50; border-radius: 8px;">
<h3 style="margin-top: 0; color: white;">Podcast Cover Art</h3>
<button id="btn-draft-prompt" style="width: 100%; padding: 10px; background: #f39c12; border: none; color: white; cursor: pointer; font-weight: bold;">💡 Step 1: Draft Cover Art Concept</button>
<div id="prompt-editor-section" style="display: block; margin-top: 15px; position: relative;">
<label style="color: #bdc3c7; font-weight: bold;">Review & Edit Prompt:</label>
<textarea id="image-prompt-input" style="width: 100%; height: 80px; margin-top: 5px; padding: 10px; font-family: sans-serif;" placeholder="Describe your custom cover art here..."></textarea>
<button id="btn-save-prompt" style="position: absolute; bottom: 10px; right: 10px; background: #2ecc71; color: white; border: none; padding: 5px 10px; cursor: pointer; border-radius: 3px; display: none;">💾 Save Prompt</button>
</div>
<button id="btn-generate-thumbnail" style="width: 100%; padding: 10px; margin-top: 10px; background: #27ae60; color: white; border: none; font-weight: bold; cursor: pointer;">🎨 Step 2: Render Image</button>
<div id="thumbnail-display" style="display: none; margin-top: 15px; text-align: center;">
<img id="podcast-cover-image" src="" style="width: 100%; max-width: 400px; border-radius: 8px; box-shadow: 0 4px 10px rgba(0,0,0,0.5);">
</div>
</div>
<div id="linkedin-section" style="margin-top: 20px; padding: 15px; background: #0077b5; border-radius: 8px; color: white;">
<button id="btn-generate-linkedin" style="width: 100%; padding: 10px; background: #005582; color: white; border: none; font-weight: bold; cursor: pointer;">💼 Generate LinkedIn Post & Video</button>
<div id="linkedin-display" style="display: none; margin-top: 15px;">
<label style="font-weight: bold;">Suggested Post:</label>
<div style="position: relative;">
<textarea id="linkedin-post-text" style="width: 100%; height: 150px; margin-top: 5px; padding: 10px; color: black;"></textarea>
<button id="btn-save-linkedin" style="position: absolute; bottom: 10px; right: 10px; background: #2ecc71; color: white; border: none; padding: 5px 10px; cursor: pointer; border-radius: 3px;">💾 Save Post</button>
</div>
<div style="margin-top: 15px;">
<label style="font-weight: bold; display: block; margin-bottom: 5px;">Ready-to-Upload Video:</label>
<video id="linkedin-video-player" controls style="width: 100%; max-width: 500px; border-radius: 5px; box-shadow: 0 4px 10px rgba(0,0,0,0.3);"></video>
</div>
</div>
</div>
<div id="youtube-section" style="margin-top: 20px; padding: 15px; background: #c4302b; border-radius: 8px; color: white;">
<h3 style="margin-top: 0;">▶️ YouTube Export</h3>
<div style="display: flex; align-items: center; gap: 10px; margin-bottom: 15px;">
<button id="btn-youtube-login" style="padding: 10px 15px; background: white; color: #c4302b; border: none; font-weight: bold; border-radius: 4px; cursor: pointer;">Sign in with Google</button>
<span id="youtube-auth-status" style="font-weight: bold; color: #ffcccc;">Not Authenticated</span>
</div>
<div id="youtube-upload-controls" style="display: none; background: rgba(0,0,0,0.15); padding: 15px; border-radius: 5px;">
<label style="font-weight: bold; display: block; margin-bottom: 5px;">Video Title:</label>
<input type="text" id="youtube-title" style="width: 100%; padding: 10px; margin-bottom: 10px; box-sizing: border-box;" placeholder="Enter podcast title...">
<label style="font-weight: bold; display: block; margin-bottom: 5px;">Video Description:</label>
<textarea id="youtube-description" style="width: 100%; height: 80px; padding: 10px; margin-bottom: 10px; box-sizing: border-box;" placeholder="Enter description (hashtags, links, etc.)..."></textarea>
<button id="btn-upload-youtube" style="width: 100%; padding: 10px; background: #28a745; color: white; border: none; font-weight: bold; cursor: pointer; border-radius: 4px;">🚀 Upload to YouTube (Private Draft)</button>
</div>
</div>
</div>
<script type="module" src="./js/app.js"></script>
</body>
</html>
FILE:public/js/api.js
export const requestThumbnail = async (id) => {
const res = await fetch('/api/generate-thumbnail', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id })
});
return res.json();
};
export const requestSessionCleanup = async (id) => {
await fetch('/api/delete-folder', {
method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id })
});
};
FILE:public/js/app.js
import { log, setApiLoading, updateLoadingMessage, displayThumbnail, resetWorkspace, showSessionControls, buildLiveCaptions, syncCaptionsWithAudio } from './ui.js';
import { requestSessionCleanup } from './api.js';
let currentVideoId = `session_Date.now()`;
const saveEditedFile = async (type, content, btnElement) => {
try {
const res = await fetch('/api/save-file', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: currentVideoId, type, content })
});
if (!res.ok) throw new Error("Failed to save.");
btnElement.innerText = "✅ Saved!";
setTimeout(() => { btnElement.innerText = "💾 Save " + (type==='linkedin'?'Post':type.charAt(0).toUpperCase() + type.slice(1)); }, 2000);
} catch(err) { alert(err.message); }
};
document.getElementById('btn-save-script').addEventListener('click', (e) => saveEditedFile('script', document.getElementById('script-editor').value, e.target));
document.getElementById('btn-save-prompt').addEventListener('click', (e) => saveEditedFile('prompt', document.getElementById('image-prompt-input').value, e.target));
document.getElementById('btn-save-linkedin').addEventListener('click', (e) => saveEditedFile('linkedin', document.getElementById('linkedin-post-text').value, e.target));
document.getElementById('btn-ingest').addEventListener('click', async () => {
const url = document.getElementById('sourceUrl').value;
const file = document.getElementById('sourceFile').files[0];
if (!url && !file) return alert("Please enter a URL or select a file.");
setApiLoading(true); showSessionControls();
log("Initiating media ingestion..."); updateLoadingMessage("Extracting text...");
const eventSource = new EventSource(`/api/stream-logs?id=currentVideoId`);
await new Promise(resolve => { eventSource.onopen = resolve; setTimeout(resolve, 500); });
eventSource.onmessage = e => { const d = JSON.parse(e.data); log(d.message); updateLoadingMessage(d.message); };
const formData = new FormData();
formData.append('id', currentVideoId);
if (file) formData.append('file', file);
else { formData.append('sourceType', 'url'); formData.append('url', url); }
try {
const res = await fetch('/api/ingest', { method: 'POST', body: formData });
const data = await res.json();
if(data.success) {
document.getElementById('original-text-section').style.display = 'block';
document.getElementById('original-text-viewer').value = data.fullText;
fetchSessions(); // Refresh sessions list in case it's new
}
} catch (e) { log(`❌ e.message`); }
eventSource.close(); setApiLoading(false);
});
document.getElementById('btn-translate-original').addEventListener('click', async () => {
const targetLang = document.getElementById('viewLanguage').value;
const viewer = document.getElementById('original-text-viewer');
viewer.value = "Translating... please wait.";
try {
const res = await fetch('/api/translate-original', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: currentVideoId, targetLanguage: targetLang })
});
const data = await res.json();
viewer.value = data.text;
} catch(err) { viewer.value = "Translation failed."; }
});
const updateScriptStats = () => {
const text = document.getElementById('script-editor').value;
const wordCount = text.trim() === '' ? 0 : text.trim().split(/\s+/).length;
document.getElementById('word-count').innerText = wordCount;
document.getElementById('est-time').innerText = `Math.floor(Math.round((wordCount / 150) * 60) / 60):(Math.round((wordCount / 150) * 60) % 60).toString().padStart(2, '0')`;
document.getElementById('linkedin-warning').style.display = (wordCount > 0 && (wordCount < 450 || wordCount > 2100)) ? 'inline' : 'none';
};
document.getElementById('script-editor').addEventListener('input', () => {
updateScriptStats();
document.getElementById('btn-save-script').style.display = 'block';
});
document.getElementById('image-prompt-input').addEventListener('input', () => {
document.getElementById('btn-save-prompt').style.display = 'block';
});
document.getElementById('linkedin-post-text').addEventListener('input', () => {
document.getElementById('btn-save-linkedin').style.display = 'block';
});
document.getElementById('btn-draft').addEventListener('click', async () => {
setApiLoading(true); log("Initiating Gemini script drafting..."); updateLoadingMessage("Drafting...");
const host1 = document.getElementById('host1').value || 'Alex', host2 = document.getElementById('host2').value || 'Sam', language = document.getElementById('targetLanguage').value;
const eventSource = new EventSource(`/api/stream-logs?id=currentVideoId`);
await new Promise(resolve => { eventSource.onopen = resolve; setTimeout(resolve, 500); });
eventSource.onmessage = e => { const d = JSON.parse(e.data); log(d.message); updateLoadingMessage(d.message); };
try {
const res = await fetch('/api/draft-script', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: currentVideoId, host1, host2, targetLanguage: language })
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
document.getElementById('script-editor').value = data.script;
updateScriptStats();
document.getElementById('btn-save-script').style.display = 'block';
} catch (e) { log(`❌ e.message`); }
eventSource.close(); setApiLoading(false);
});
document.getElementById('btn-generate-audio').addEventListener('click', async () => {
setApiLoading(true);
log("Initiating audio synthesis pipeline...");
updateLoadingMessage("Synthesizing...");
const script = document.getElementById('script-editor').value;
const host1 = document.getElementById('host1').value || 'Alex';
const host2 = document.getElementById('host2').value || 'Sam';
const eventSource = new EventSource(`/api/stream-logs?id=currentVideoId`);
await new Promise(resolve => { eventSource.onopen = resolve; setTimeout(resolve, 500); });
eventSource.onmessage = async (e) => {
const d = JSON.parse(e.data); log(d.message); updateLoadingMessage(d.message);
if (d.status === 'done') {
eventSource.close(); setApiLoading(false);
const audio = document.getElementById('podcast-audio');
const audioUrl = `/downloads/currentVideoId/podcast.m4a`;
const vttUrl = `/downloads/currentVideoId/podcast.vtt`;
audio.src = audioUrl;
document.getElementById('podcast-vtt').src = vttUrl;
document.getElementById('audio-container').style.display = 'block';
try {
const vttRes = await fetch(vttUrl);
buildLiveCaptions(await vttRes.text());
} catch (err) { console.error("Failed to load VTT for UI", err); }
}
};
try {
await fetch('/api/synthesize', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: currentVideoId, script, host1, host2 })
});
} catch(e) { log(`❌ e.message`); eventSource.close(); setApiLoading(false); }
});
document.getElementById('podcast-audio').addEventListener('timeupdate', (e) => {
syncCaptionsWithAudio(e.target.currentTime);
});
document.getElementById('btn-draft-prompt').addEventListener('click', async () => {
setApiLoading(true);
log("Analyzing script for visual concepts...");
updateLoadingMessage("Designing...");
const eventSource = new EventSource(`/api/stream-logs?id=currentVideoId`);
await new Promise(resolve => { eventSource.onopen = resolve; setTimeout(resolve, 500); });
eventSource.onmessage = e => { const d = JSON.parse(e.data); log(d.message); updateLoadingMessage(d.message); };
try {
const res = await fetch('/api/draft-image-prompt', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: currentVideoId })
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
document.getElementById('image-prompt-input').value = data.prompt;
document.getElementById('prompt-editor-section').style.display = 'block';
} catch (e) { log(`❌ e.message`); }
eventSource.close(); setApiLoading(false);
});
document.getElementById('btn-generate-thumbnail').addEventListener('click', async () => {
setApiLoading(true);
const userPrompt = document.getElementById('image-prompt-input').value;
if (!userPrompt) return alert("Please draft or enter a prompt first.");
log("Sending render request to Gemini 3.1 Flash Image...");
updateLoadingMessage("Rendering...");
const eventSource = new EventSource(`/api/stream-logs?id=currentVideoId`);
await new Promise(resolve => { eventSource.onopen = resolve; setTimeout(resolve, 500); });
eventSource.onmessage = e => { const d = JSON.parse(e.data); log(d.message); updateLoadingMessage(d.message); };
try {
const res = await fetch('/api/generate-thumbnail', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: currentVideoId, prompt: userPrompt })
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
displayThumbnail(`/downloads/currentVideoId/data.file`, "");
updateLoadingMessage("Done!");
} catch (e) { log(`❌ e.message`); updateLoadingMessage("Error!"); }
eventSource.close(); setApiLoading(false);
});
document.getElementById('btn-generate-linkedin').addEventListener('click', async () => {
setApiLoading(true);
const btn = document.getElementById('btn-generate-linkedin');
btn.disabled = true;
const selectedLangs = Array.from(document.querySelectorAll('input[name="vtt-lang"]:checked'))
.map(cb => cb.value);
log(selectedLangs.length > 0
? `Initiating LinkedIn packaging & translating VTT to selectedLangs.length languages...`
: "Initiating LinkedIn video packaging...");
updateLoadingMessage("Packaging...");
const eventSource = new EventSource(`/api/stream-logs?id=currentVideoId`);
await new Promise(resolve => { eventSource.onopen = resolve; setTimeout(resolve, 500); });
eventSource.onmessage = e => { const d = JSON.parse(e.data); log(d.message); updateLoadingMessage(d.message); };
try {
const res = await fetch('/api/generate-linkedin', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: currentVideoId, targetCaptionLanguages: selectedLangs })
});
const data = await res.json();
if (!res.ok) throw new Error(data.error || "Failed to generate LinkedIn package.");
document.getElementById('linkedin-post-text').value = data.post;
document.getElementById('linkedin-video-player').src = `/downloads/currentVideoId/data.video?t=Date.now()`;
document.getElementById('linkedin-display').style.display = 'block';
} catch (e) { log(`❌ e.message`); alert(e.message); }
finally { eventSource.close(); setApiLoading(false); btn.disabled = false; }
});
let youtubeAccessToken = null;
let tokenClient;
window.onload = async () => {
try {
const configRes = await fetch('/api/config');
const config = await configRes.json();
if (config.googleClientId) {
tokenClient = google.accounts.oauth2.initTokenClient({
client_id: config.googleClientId,
scope: 'https://www.googleapis.com/auth/youtube.upload',
callback: (tokenResponse) => {
if (tokenResponse && tokenResponse.access_token) {
youtubeAccessToken = tokenResponse.access_token;
document.getElementById('youtube-auth-status').innerText = '✅ Authenticated';
document.getElementById('youtube-auth-status').style.color = '#fff';
document.getElementById('btn-youtube-login').style.display = 'none';
document.getElementById('youtube-upload-controls').style.display = 'block';
}
},
});
}
} catch (err) { console.error("Failed to load config:", err); }
};
document.getElementById('btn-youtube-login').addEventListener('click', () => {
if (tokenClient) { tokenClient.requestAccessToken(); }
else { alert("Client ID missing from .env configuration."); }
});
document.getElementById('btn-upload-youtube').addEventListener('click', async () => {
if (!youtubeAccessToken) return alert("Please sign in with Google first.");
const title = document.getElementById('youtube-title').value;
const description = document.getElementById('youtube-description').value;
if (!title) return alert("Please provide a title for the YouTube video.");
setApiLoading(true);
const btn = document.getElementById('btn-upload-youtube');
btn.disabled = true;
log("Connecting to YouTube API...");
updateLoadingMessage("Uploading...");
const eventSource = new EventSource(`/api/stream-logs?id=currentVideoId`);
await new Promise(resolve => { eventSource.onopen = resolve; setTimeout(resolve, 500); });
eventSource.onmessage = e => { const d = JSON.parse(e.data); log(d.message); updateLoadingMessage(d.message); };
try {
const res = await fetch('/api/upload-youtube', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: currentVideoId,
title: title,
description: description,
accessToken: youtubeAccessToken
})
});
const data = await res.json();
if (!res.ok) throw new Error(data.error || "Failed to upload to YouTube.");
log(`✅ Successfully uploaded! YouTube Video ID: data.videoId`);
alert(`Upload complete! Video ID: data.videoId\nIt is currently set to Private.`);
} catch (e) {
log(`❌ e.message`);
alert(e.message);
} finally {
eventSource.close();
setApiLoading(false);
btn.disabled = false;
}
});
// --- SESSION MANAGEMENT LOGIC ---
const fetchSessions = async () => {
try {
const res = await fetch('/api/sessions');
const data = await res.json();
const select = document.getElementById('session-select');
const currentVal = select.value;
select.innerHTML = '<option value="">-- Start a New Session --</option>';
data.sessions.forEach(session => {
const option = document.createElement('option');
option.value = session;
option.innerText = session;
select.appendChild(option);
});
if (currentVal && data.sessions.includes(currentVal)) {
select.value = currentVal;
}
} catch (err) { console.error("Failed to load sessions:", err); }
};
fetchSessions();
document.getElementById('btn-load-session').addEventListener('click', async () => {
const selectedSession = document.getElementById('session-select').value;
if (!selectedSession) {
currentVideoId = `session_Date.now()`;
document.getElementById('current-session-label').innerText = "Active: New Session";
resetWorkspace();
return;
}
currentVideoId = selectedSession;
document.getElementById('current-session-label').innerText = `Active: currentVideoId`;
try {
const res = await fetch(`/api/session-data?id=currentVideoId`);
const data = await res.json();
if (data.originalText) {
document.getElementById('original-text-section').style.display = 'block';
document.getElementById('original-text-viewer').value = data.originalText;
} else {
document.getElementById('original-text-section').style.display = 'none';
}
if (data.script) {
document.getElementById('script-editor').value = data.script;
updateScriptStats();
} else {
document.getElementById('script-editor').value = "";
}
if (data.prompt) {
document.getElementById('prompt-editor-section').style.display = 'block';
document.getElementById('image-prompt-input').value = data.prompt;
} else {
document.getElementById('image-prompt-input').value = "";
}
if (data.linkedinPost) {
document.getElementById('linkedin-display').style.display = 'block';
document.getElementById('linkedin-post-text').value = data.linkedinPost;
} else {
document.getElementById('linkedin-post-text').value = "";
}
if (data.hasAudio) {
document.getElementById('audio-container').style.display = 'block';
document.getElementById('podcast-audio').src = `/downloads/currentVideoId/podcast.m4a`;
document.getElementById('podcast-vtt').src = `/downloads/currentVideoId/podcast.vtt`;
try {
const vttRes = await fetch(`/downloads/currentVideoId/podcast.vtt`);
buildLiveCaptions(await vttRes.text());
} catch (err) { console.error("Could not load VTT", err); }
} else {
document.getElementById('audio-container').style.display = 'none';
}
if (data.hasImage) {
displayThumbnail(`/downloads/currentVideoId/thumbnail.png`, "");
} else {
document.getElementById('thumbnail-display').style.display = 'none';
}
if (data.hasVideo) {
document.getElementById('linkedin-video-player').src = `/downloads/currentVideoId/linkedin_podcast.mp4`;
} else {
document.getElementById('linkedin-video-player').src = "";
}
alert(`Session loaded: currentVideoId`);
} catch (err) {
alert("Failed to load session data.");
}
});
document.getElementById('btn-delete-session').addEventListener('click', async () => {
const sessionToDelete = document.getElementById('session-select').value || currentVideoId;
if (!sessionToDelete || sessionToDelete === `session_Date.now()`) return alert("No saved session selected to delete.");
if (!confirm(`Are you sure you want to permanently delete data for sessionToDelete?`)) return;
await requestSessionCleanup(sessionToDelete);
currentVideoId = `session_Date.now()`;
document.getElementById('current-session-label').innerText = "Active: New Session";
resetWorkspace();
fetchSessions();
});
document.getElementById('btn-clear-all-sessions').addEventListener('click', async () => {
if (!confirm("☢️ WARNING: This will permanently delete ALL session folders in your downloads directory. This cannot be undone. Proceed?")) return;
try {
await fetch('/api/delete-all-sessions', { method: 'DELETE' });
currentVideoId = `session_Date.now()`;
document.getElementById('current-session-label').innerText = "Active: New Session";
resetWorkspace();
fetchSessions();
alert("All sessions have been cleared.");
} catch(err) {
alert("Failed to clear sessions.");
}
});
FILE:public/js/ui.js
export const log = (msg) => {
const box = document.getElementById('terminal-log');
if (box) { box.innerHTML += `<div>msg</div>`; box.scrollTop = box.scrollHeight; }
};
export const setApiLoading = (show) => {
document.getElementById('log-active-spinner').style.display = show ? 'block' : 'none';
};
export const updateLoadingMessage = (msg) => {
const el = document.querySelector('#log-active-spinner em');
if (el) el.textContent = msg;
};
export const displayThumbnail = (url, promptText) => {
document.getElementById('podcast-cover-image').src = `url?t=Date.now()`;
if(promptText) document.getElementById('generated-image-prompt').innerText = promptText;
document.getElementById('thumbnail-display').style.display = 'block';
};
export const resetWorkspace = () => {
document.getElementById('script-editor').value = '';
document.getElementById('audio-container').style.display = 'none';
document.getElementById('thumbnail-display').style.display = 'none';
// Clear the prompt text, but leave the editor visible
document.getElementById('image-prompt-input').value = '';
document.getElementById('linkedin-display').style.display = 'none';
document.getElementById('live-captions').innerHTML = '';
document.getElementById('sourceUrl').value = '';
document.getElementById('sourceFile').value = '';
document.getElementById('word-count').innerText = '0';
document.getElementById('est-time').innerText = '0:00';
document.getElementById('linkedin-warning').style.display = 'none';
};
export const showSessionControls = () => {
// Intentionally left blank as the HTML element does not exist
};
export const buildLiveCaptions = (vttText) => {
const container = document.getElementById('live-captions');
container.innerHTML = '';
try {
if (!vttText || !vttText.includes('-->')) throw new Error("Invalid VTT structure detected.");
const regex = /(\d{2}:\d{2}:\d{2}\.\d{3})\s-->\s(\d{2}:\d{2}:\d{2}\.\d{3})\n<v\s([^>]+)>(.*?)\n/g;
let match;
const timeToSeconds = (timeStr) => {
const [h, m, s] = timeStr.split(':');
return parseInt(h) * 3600 + parseInt(m) * 60 + parseFloat(s);
};
while ((match = regex.exec(vttText)) !== null) {
const startSec = timeToSeconds(match[1]);
const endSec = timeToSeconds(match[2]);
const speaker = match[3];
const text = match[4];
const lineDiv = document.createElement('div');
lineDiv.className = 'caption-line';
lineDiv.dataset.start = startSec;
lineDiv.dataset.end = endSec;
lineDiv.innerHTML = `<strong style="color: #3498db;">speaker:</strong> <span>text</span>`;
lineDiv.addEventListener('click', () => {
const audio = document.getElementById('podcast-audio');
audio.currentTime = startSec;
audio.play();
});
container.appendChild(lineDiv);
}
if (container.children.length === 0) throw new Error("No valid captions matched the regex.");
} catch (err) {
console.warn("VTT Error Boundary Triggered:", err);
container.innerHTML = `
<div style="padding: 20px; color: #e74c3c; text-align: center; font-family: sans-serif;">
⚠️ <strong>Live Captions Unavailable</strong><br>
<span style="font-size: 0.9em; opacity: 0.8;">The transcript format returned is unsupported. Audio will still play normally.</span>
</div>
`;
}
};
export const syncCaptionsWithAudio = (currentTime) => {
const lines = document.querySelectorAll('.caption-line');
let activeLineFound = false;
lines.forEach(line => {
const start = parseFloat(line.dataset.start);
const end = parseFloat(line.dataset.end);
if (currentTime >= start && currentTime <= end) {
line.style.opacity = '1';
line.style.color = '#2ecc71';
if (!activeLineFound) {
line.scrollIntoView({ behavior: 'smooth', block: 'center' });
activeLineFound = true;
}
} else {
line.style.opacity = '0.4';
line.style.color = '#bdc3c7';
}
});
};
FILE:public/style.css
.spinner {
display: inline-block;
width: 20px; height: 20px;
border: 3px solid rgba(255,255,255,.3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.caption-line { padding: 8px 10px; opacity: 0.4; cursor: pointer; display: flex; justify-content: space-between; }
.caption-line:hover { opacity: 0.8 !important; }
FILE:routes/draft.js
const express = require('express');
const fs = require('fs');
const path = require('path');
const { getGeminiClient } = require('../utils/geminiClient');
const state = require('../config/state');
const { emitStreamLog } = require('../utils/streamer');
const router = express.Router();
router.post('/draft-script', async (req, res) => {
const { id, host1 = 'Alex', host2 = 'Sam', targetLanguage = 'English' } = req.body;
const safeId = state.sanitizeId(id);
const sessionDir = path.join(state.downloadsDir, safeId);
const sourceFile = path.join(sessionDir, 'original.txt');
if (!fs.existsSync(sourceFile)) return res.status(404).json({ error: "Source text not found." });
try {
const sourceText = fs.readFileSync(sourceFile, 'utf8');
const ai = getGeminiClient();
emitStreamLog(safeId, { message: `Drafting targetLanguage multi-host script via Gemini...` });
let finalScript = "";
let attempt = 1;
const maxAttempts = 3;
let success = false;
while (attempt <= maxAttempts && !success) {
// Softened urgency modifier to satisfy security scanners
const urgencyModifier = attempt > 1
? `\n\nPlease ensure the draft is more concise. The previous version exceeded the length limit. Summarize the content to keep it strictly under 2100 words.`
: "";
// Softened system prompt to avoid "Prompt Injection" heuristics
const systemPrompt = `You are acting as a professional podcast producer and scriptwriter.
Your task is to adapt the following source text into a conversational podcast script.
Content guidelines:
- Base the script entirely on the provided source material.
- Avoid adding external facts, figures, or names that are not in the text.
- Keep the length proportional to the source material without adding filler.
Formatting requests:
- Feature two hosts named host1 and host2.
- The output language should be targetLanguage.
- Format each spoken line exactly as "Name: Spoken text here."
- Exclude sound effects, brackets, or stage directions.
- The maximum length is 2100 words.urgencyModifier`;
if (attempt > 1) {
emitStreamLog(safeId, { message: `Attempt attempt/maxAttempts: Script exceeded word limit. Redrafting to compress...` });
}
const response = await ai.models.generateContent({
model: 'gemini-2.5-flash',
contents: `Source Material to adapt:\nsourceText`,
config: {
systemInstruction: systemPrompt,
temperature: 0.2,
topP: 0.8
}
});
finalScript = response.text;
const wordCount = finalScript.trim().split(/\s+/).length;
if (wordCount <= 2100) {
success = true;
} else {
attempt++;
}
}
fs.writeFileSync(path.join(sessionDir, 'script.txt'), finalScript);
if (success) {
emitStreamLog(safeId, { message: "Script successfully drafted and formatted within limits!" });
} else {
emitStreamLog(safeId, { message: "Warning: Script generated but slightly over word limit after maximum retries. Manual editing may be required." });
}
res.json({ success: true, script: finalScript });
} catch (error) {
emitStreamLog(safeId, { status: 'error', message: "Failed to draft script: " + error.message });
res.status(500).json({ error: error.message });
}
});
module.exports = router;
FILE:routes/images.js
const express = require('express');
const fs = require('fs');
const path = require('path');
const { getGeminiClient } = require('../utils/geminiClient');
const state = require('../config/state');
const { emitStreamLog } = require('../utils/streamer');
const router = express.Router();
router.post('/draft-image-prompt', async (req, res) => {
const { id } = req.body;
const safeId = state.sanitizeId(id);
const sessionDir = path.join(state.downloadsDir, safeId);
const scriptFile = path.join(sessionDir, 'script.txt');
if (!fs.existsSync(scriptFile)) {
return res.status(404).json({ error: "Script not found. Please draft the script first." });
}
try {
// The SDK automatically pulls from process.env.GEMINI_API_KEY
const ai = getGeminiClient();
const scriptText = fs.readFileSync(scriptFile, 'utf8');
emitStreamLog(safeId, { message: "Analyzing podcast script for visual concepts..." });
const systemPrompt = `Write a highly descriptive, 1-sentence prompt for an AI image generator to create sleek modern podcast cover art based on this script excerpt. Do NOT include any text or words in the image design.`;
const promptDesign = await ai.models.generateContent({
model: 'gemini-2.5-flash',
contents: `Script Excerpt:\nscriptText.substring(0, 3000)`,
config: { systemInstruction: systemPrompt }
});
res.json({ success: true, prompt: promptDesign.text.trim() });
} catch (error) {
emitStreamLog(safeId, { status: 'error', message: error.message });
res.status(500).json({ error: error.message });
}
});
router.post('/generate-thumbnail', async (req, res) => {
const { id, prompt } = req.body;
const safeId = state.sanitizeId(id);
const sessionDir = path.join(state.downloadsDir, safeId);
if (!prompt) return res.status(400).json({ error: "Image prompt is required." });
if (!fs.existsSync(sessionDir)) fs.mkdirSync(sessionDir, { recursive: true });
try {
// The SDK automatically pulls from process.env.GEMINI_API_KEY
const ai = getGeminiClient();
emitStreamLog(safeId, { message: "Rendering 16:9 image via Gemini 3.1 Flash Image Preview..." });
const imageResponse = await ai.models.generateContent({
model: 'gemini-3.1-flash-image-preview',
contents: prompt,
config: {
responseModalities: ["IMAGE"],
imageConfig: { aspectRatio: "16:9" }
}
});
const base64Data = imageResponse.candidates[0].content.parts[0].inlineData.data;
const imagePath = path.join(sessionDir, 'thumbnail.png');
fs.writeFileSync(imagePath, Buffer.from(base64Data, 'base64'));
emitStreamLog(safeId, { message: "Cover art successfully rendered and saved!" });
res.json({ success: true, file: 'thumbnail.png' });
} catch (error) {
emitStreamLog(safeId, { status: 'error', message: error.message });
res.status(500).json({ error: error.message });
}
});
module.exports = router;
FILE:routes/ingest.js
const express = require('express');
const fs = require('fs');
const path = require('path');
const multer = require('multer');
const axios = require('axios');
const cheerio = require('cheerio');
const ytdl = require('@distube/ytdl-core');
const { YoutubeTranscript } = require('youtube-transcript-plus');
const { getGeminiClient } = require('../utils/geminiClient');
const state = require('../config/state');
const { emitStreamLog } = require('../utils/streamer');
const { processLocalFile } = require('../utils/fileProcessor');
const dns = require('dns').promises; // NEW: Import DNS module
const router = express.Router();
const tempUploadsDir = path.join(state.downloadsDir, 'temp_uploads');
if (!fs.existsSync(tempUploadsDir)) fs.mkdirSync(tempUploadsDir, { recursive: true });
const upload = multer({ dest: tempUploadsDir, limits: { fileSize: 500 * 1024 * 1024 } });
// --- UPDATED SECURITY MIDDLEWARE (DNS Resolution) ---
const ssrfProtection = async (req, res, next) => {
const { sourceType, url } = req.body;
if (sourceType === 'url' && url) {
try {
const parsedUrl = new URL(url);
// 1. Enforce safe protocols
if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
return res.status(400).json({ error: "Security Exception: Only HTTP and HTTPS protocols are allowed." });
}
const hostname = parsedUrl.hostname;
// 2. Resolve the hostname to its actual IP address to prevent DNS rebinding
const lookup = await dns.lookup(hostname);
const resolvedIp = lookup.address;
// 3. Block local and private IP ranges
const isLocalOrPrivate = /^(localhost|127\.0\.0\.1|0\.0\.0\.0|10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|172\.(1[6-9]|2[0-9]|3[0-1])\.\d+\.\d+|169\.254\.\d+\.\d+|::1)$/i.test(resolvedIp) || hostname.toLowerCase() === 'localhost';
if (isLocalOrPrivate) {
return res.status(403).json({ error: "Security Exception: Access to local or private networks is strictly forbidden." });
}
} catch (err) {
return res.status(400).json({ error: "Security Exception: Malformed URL or DNS resolution failed." });
}
}
next();
};
router.post('/ingest', upload.single('file'), ssrfProtection, async (req, res) => {
const { id, sourceType, url } = req.body;
const safeId = state.sanitizeId(id);
const sessionDir = path.join(state.downloadsDir, safeId);
if (!fs.existsSync(sessionDir)) fs.mkdirSync(sessionDir, { recursive: true });
let extractedText = "";
try {
emitStreamLog(safeId, { message: "Initializing ingestion engine..." });
if (sourceType === 'url' && url) {
const lowerUrl = url.toLowerCase();
if (lowerUrl.endsWith('.mp4')) {
const tempVideoPath = path.join(sessionDir, 'downloaded.mp4');
const response = await axios({ method: 'GET', url: url, responseType: 'stream' });
const writer = fs.createWriteStream(tempVideoPath);
response.data.pipe(writer);
await new Promise((resolve, reject) => { writer.on('finish', resolve); writer.on('error', reject); });
extractedText = await processLocalFile(tempVideoPath, 'video/mp4', sessionDir, safeId);
if (fs.existsSync(tempVideoPath)) fs.unlinkSync(tempVideoPath);
} else if (lowerUrl.includes('youtube.com') || lowerUrl.includes('youtu.be')) {
try {
// ATTEMPT 1: Fast transcript extraction
emitStreamLog(safeId, { message: "Fetching YouTube closed captions..." });
const transcriptData = await YoutubeTranscript.fetchTranscript(url);
extractedText = transcriptData.map(item => item.text).join(' ');
emitStreamLog(safeId, { message: "YouTube transcript successfully extracted!" });
} catch (ytError) {
// ATTEMPT 2: Whisper Fallback (Video has no captions)
emitStreamLog(safeId, { message: "No captions found. Falling back to Whisper audio transcription..." });
const tempAudioPath = path.join(sessionDir, 'yt_audio.mp4');
await new Promise((resolve, reject) => {
// Download the highest quality audio stream available
const stream = ytdl(url, { filter: 'audioonly', quality: 'highestaudio' });
const writer = fs.createWriteStream(tempAudioPath);
stream.pipe(writer);
writer.on('finish', resolve);
stream.on('error', reject);
});
// Pass the downloaded YouTube audio into our existing Whisper chunking logic
extractedText = await processLocalFile(tempAudioPath, 'audio/mp4', sessionDir, safeId);
if (fs.existsSync(tempAudioPath)) fs.unlinkSync(tempAudioPath);
}
} else {
emitStreamLog(safeId, { message: "Scraping target URL..." });
const response = await axios.get(url, { headers: { 'User-Agent': 'Mozilla/5.0' }, timeout: 10000 });
const $ = cheerio.load(response.data);
$('header, footer, nav, aside, script, style, noscript, svg, button').remove();
let contentContainer = $('article').length > 0 ? $('article') : $('body');
contentContainer.find('p, h1, h2, h3').each((i, el) => {
const text = $(el).text().trim();
if (text.length > 1) extractedText += text + "\n\n";
});
if (!extractedText.trim()) extractedText = contentContainer.text().replace(/\s+/g, ' ').trim();
}
} else if (req.file) {
const tempPath = req.file.path;
const mimeType = req.file.mimetype;
emitStreamLog(safeId, { message: `Processing uploaded file...` });
try {
extractedText = await processLocalFile(tempPath, mimeType, sessionDir, safeId);
} finally {
if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath);
}
}
if (!extractedText.trim()) throw new Error("Could not extract meaningful text. The page might be protected or empty.");
// Language Detection & Dual-Save Logic
emitStreamLog(safeId, { message: "Analyzing language..." });
const ai = getGeminiClient();
const langPrompt = `What language is the following text predominantly written in? Respond with ONLY the English name of the language (e.g., "Spanish", "English", "Arabic", "French"). Do not include any other text.\n\nText: extractedText.substring(0, 1000)`;
const langRes = await ai.models.generateContent({ model: 'gemini-2.5-flash', contents: langPrompt });
let detectedLanguage = langRes.text.trim().replace(/[^a-zA-Z]/g, '');
let finalEnglishText = extractedText;
if (detectedLanguage.toLowerCase() !== 'english') {
emitStreamLog(safeId, { message: `Detected detectedLanguage. Archiving original and translating to English...` });
fs.writeFileSync(path.join(sessionDir, `original-detectedLanguage.toLowerCase().txt`), extractedText);
const translatePrompt = `Translate the following detectedLanguage text into English. Output ONLY the English translation.\n\nText:\nextractedText.substring(0, 30000)`;
const translateRes = await ai.models.generateContent({ model: 'gemini-2.5-flash', contents: translatePrompt });
finalEnglishText = translateRes.text.trim();
}
fs.writeFileSync(path.join(sessionDir, 'original.txt'), finalEnglishText);
emitStreamLog(safeId, { message: "Ingestion complete! Ready for script drafting." });
res.json({ success: true, fullText: finalEnglishText, detectedLanguage });
} catch (error) {
emitStreamLog(safeId, { status: 'error', message: error.message });
res.status(500).json({ error: error.message });
}
});
module.exports = router;
FILE:routes/linkedin.js
const express = require('express');
const fs = require('fs');
const path = require('path');
const ffmpeg = require('fluent-ffmpeg');
const { getGeminiClient } = require('../utils/geminiClient');
const state = require('../config/state');
const { emitStreamLog } = require('../utils/streamer');
const router = express.Router();
const getAudioDuration = (filePath) => {
return new Promise((resolve, reject) => {
ffmpeg.ffprobe(filePath, (err, metadata) => {
if (err) return reject(err);
resolve(metadata.format.duration);
});
});
};
router.post('/generate-linkedin', async (req, res) => {
const { id, targetCaptionLanguages = [] } = req.body;
const safeId = state.sanitizeId(id);
const sessionDir = path.join(state.downloadsDir, safeId);
const originalTextPath = path.join(sessionDir, 'original.txt');
const scriptPath = path.join(sessionDir, 'script.txt');
const audioPath = path.join(sessionDir, 'podcast.m4a');
const imagePath = path.join(sessionDir, 'thumbnail.png');
const vttPath = path.join(sessionDir, 'podcast.vtt');
if (!fs.existsSync(originalTextPath) || !fs.existsSync(scriptPath)) return res.status(404).json({ error: "Missing source text or script." });
if (!fs.existsSync(audioPath)) return res.status(404).json({ error: "Missing audio." });
if (!fs.existsSync(imagePath)) return res.status(404).json({ error: "Missing thumbnail." });
try {
const ai = getGeminiClient();
emitStreamLog(safeId, { message: "Crafting social media copy..." });
const originalText = fs.readFileSync(originalTextPath, 'utf8');
const scriptText = fs.readFileSync(scriptPath, 'utf8');
const copyPrompt = `You are a top-tier social media manager. Write an engaging, professional LinkedIn post promoting a new podcast episode based on the source text below.
Requirements:
1. Start with a strong hook.
2. Use appropriate emojis to break up the text and add visual interest.
3. Include 3-4 relevant hashtags at the bottom.
4. End with a call to action asking listeners to share their thoughts.
5. Do NOT include placeholders, brackets, or meta-commentary.
Source Context: originalText.substring(0, 1500)
Script Excerpt: scriptText.substring(0, 1500)`;
const geminiResponse = await ai.models.generateContent({ model: 'gemini-2.5-flash', contents: copyPrompt });
const linkedinPost = geminiResponse.text.trim();
fs.writeFileSync(path.join(sessionDir, 'linkedin_post.txt'), linkedinPost);
if (fs.existsSync(vttPath) && targetCaptionLanguages.length > 0) {
emitStreamLog(safeId, { message: "Translating closed captions..." });
const baseVtt = fs.readFileSync(vttPath, 'utf8');
for (const lang of targetCaptionLanguages) {
emitStreamLog(safeId, { message: `Generating lang subtitles...` });
// Softened translation prompt to satisfy security scanners
const translationPrompt = `Please act as an expert translator and translate the following WebVTT file into lang.
Translation requests:
- Preserve the original timestamps, arrows (-->), and VTT structure.
- Leave the WEBVTT header and speaker tags in their original language.
- Translate the spoken dialogue only.
- Provide the raw VTT text as the final output.
WebVTT File:\nbaseVtt`;
const translationRes = await ai.models.generateContent({ model: 'gemini-2.5-flash', contents: translationPrompt });
let cleanVtt = translationRes.text.trim();
if (cleanVtt.startsWith('```vtt')) cleanVtt = cleanVtt.replace(/```vtt\n|```/g, '');
fs.writeFileSync(path.join(sessionDir, `podcast_lang.toLowerCase().vtt`), cleanVtt);
}
}
emitStreamLog(safeId, { message: "Checking LinkedIn audio constraints..." });
const duration = await getAudioDuration(audioPath);
if (duration < 180) throw new Error(`Audio is too short (Math.round(duration)s). LinkedIn requires at least 3 minutes.`);
emitStreamLog(safeId, { message: "Generating professional blurred background (1280x720)..." });
const paddedImage = path.join(sessionDir, 'thumbnail-1280.png');
await new Promise((resolve, reject) => {
ffmpeg(imagePath)
.complexFilter([
'[0:v]scale=1280:720:force_original_aspect_ratio=increase,crop=1280:720,boxblur=25:5,eq=brightness=-0.05[bg]',
'[0:v]scale=1280:720:force_original_aspect_ratio=decrease[fg]',
'[bg][fg]overlay=(W-w)/2:(H-h)/2'
])
.outputOptions(['-frames:v 1'])
.save(paddedImage)
.on('end', resolve)
.on('error', reject);
});
emitStreamLog(safeId, { message: "Rendering final MP4 video. This may take a minute..." });
const finalVideoPath = path.join(sessionDir, 'linkedin_podcast.mp4');
await new Promise((resolve, reject) => {
let command = ffmpeg()
.input(paddedImage)
.inputOptions(['-loop 1', '-framerate 1'])
.input(audioPath)
.videoCodec('libx264')
.audioCodec('aac')
.outputOptions([
'-tune stillimage',
'-preset ultrafast',
'-crf 35',
'-b:a 192k',
'-pix_fmt yuv420p',
'-shortest',
'-fflags +genpts'
]);
if (duration > 840) {
command = command.duration(840);
emitStreamLog(safeId, { message: "Truncating video to 14 minutes for LinkedIn..." });
}
command.save(finalVideoPath)
.on('end', resolve)
.on('error', reject);
});
emitStreamLog(safeId, { message: "LinkedIn package successfully generated!" });
res.json({ success: true, post: linkedinPost, video: 'linkedin_podcast.mp4' });
} catch (error) {
emitStreamLog(safeId, { status: 'error', message: "LinkedIn Export failed: " + error.message });
res.status(500).json({ error: error.message });
}
});
module.exports = router;
FILE:routes/synthesize.js
const express = require('express');
const fs = require('fs');
const path = require('path');
const ffmpeg = require('fluent-ffmpeg');
const OpenAI = require('openai');
const axios = require('axios');
const rateLimit = require('express-rate-limit');
const state = require('../config/state');
const secrets = require('../config/secrets');
const { emitStreamLog, delay } = require('../utils/streamer');
const router = express.Router();
const synthesizeLimiter = rateLimit({ windowMs: 60 * 60 * 1000, max: 10 });
function formatVTTTime(totalSeconds) {
const totalMs = Math.round(totalSeconds * 1000);
const h = Math.floor(totalMs / 3600000);
const m = Math.floor((totalMs % 3600000) / 60000);
const s = Math.floor((totalMs % 60000) / 1000);
const ms = totalMs % 1000;
const pad = (num, size) => num.toString().padStart(size, '0');
return `pad(h, 2):pad(m, 2):pad(s, 2).pad(ms, 3)`;
}
router.post('/synthesize', synthesizeLimiter, async (req, res) => {
const { id, script, host1 = 'Alex', host2 = 'Sam', ttsEngine = 'openai', host1VoiceId, host2VoiceId } = req.body;
const safeId = state.sanitizeId(id);
const sessionDir = path.join(state.downloadsDir, safeId);
if (!script) return res.status(400).json({ error: "Script content required" });
if (!fs.existsSync(sessionDir)) fs.mkdirSync(sessionDir, { recursive: true });
const sanitizedScript = script.replace(/<[^>]*>?/gm, '').replace(/\*\*/g, '').replace(/\*/g, '').replace(/^[-•]\s*/gm, '').replace(/^\d+\.\s*/gm, '').replace(/\r\n/g, '\n').trim();
const regex = /^([a-zA-Z0-9_ ]+)\s*:\s*([\s\S]*?)(?=^[a-zA-Z0-9_ ]+\s*:|\s*$)/gm;
let match;
const segments = [];
while ((match = regex.exec(sanitizedScript)) !== null) {
if (match[2].trim().length > 0) segments.push({ s: match[1].trim(), t: match[2].trim() });
}
if (segments.length === 0) return res.status(400).json({ error: "No valid dialogue found." });
state.jobs[safeId] = { status: 'queued', message: 'Queued for processing...', timestamp: Date.now() };
res.json({ success: true, message: "Synthesis started." });
const pcmPath = path.join(sessionDir, 'temp.pcm');
const finalAudioPath = path.join(sessionDir, 'podcast.m4a');
const vttPath = path.join(sessionDir, 'podcast.vtt');
const openai = new OpenAI();
try {
const writeStream = fs.createWriteStream(pcmPath);
let vttContent = "WEBVTT\n\n";
let currentTime = 0.0;
const silenceDuration = 0.5;
const sampleRate = 24000;
const silenceBytes = Buffer.alloc(sampleRate * 2 * silenceDuration);
for (let i = 0; i < segments.length; i++) {
const lineText = segments[i].t;
const isHost2 = segments[i].s.toLowerCase().includes(host2.toLowerCase());
const msg = `Synthesizing chunk i + 1 of segments.length via ttsEngine.toUpperCase()...`;
emitStreamLog(safeId, { status: 'processing', message: msg });
let audioData = null;
let attempt = 0;
while (attempt <= 3) {
try {
if (ttsEngine === 'elevenlabs') {
const targetVoiceId = isHost2 ? host2VoiceId : host1VoiceId;
const response = await axios({
method: 'POST',
url: `https://api.elevenlabs.io/v1/text-to-speech/targetVoiceId?output_format=pcm_24000`,
headers: { 'xi-api-key': secrets.getElevenLabsKey(), 'Content-Type': 'application/json' },
data: { text: lineText, model_id: "eleven_multilingual_v2" },
responseType: 'arraybuffer'
});
audioData = Buffer.from(response.data);
} else {
const response = await openai.audio.speech.create({
model: "tts-1", voice: isHost2 ? "echo" : "shimmer", input: lineText, response_format: "pcm"
});
audioData = Buffer.from(await response.arrayBuffer());
}
break;
} catch (err) {
attempt++;
if (attempt > 3) throw err;
await delay(Math.pow(2, attempt) * 1000);
}
}
writeStream.write(audioData);
const duration = audioData.length / (sampleRate * 2);
vttContent += `formatVTTTime(currentTime) --> formatVTTTime(currentTime + duration)\n<v segments[i].s>lineText\n\n`;
currentTime += duration;
writeStream.write(silenceBytes);
currentTime += silenceDuration;
}
writeStream.end();
fs.writeFileSync(vttPath, vttContent);
emitStreamLog(safeId, { status: 'processing', message: 'Compiling high-quality audio...' });
ffmpeg(pcmPath)
.inputOptions(['-f s16le', '-ar 24000', '-ac 1'])
.outputOptions(['-c:a aac', '-b:a 128k'])
.save(finalAudioPath)
.on('end', () => {
if (fs.existsSync(pcmPath)) fs.unlinkSync(pcmPath);
emitStreamLog(safeId, { status: 'done', file: 'podcast.m4a', message: 'Audio compilation complete!' });
state.jobs[safeId].status = 'done';
})
.on('error', (err) => emitStreamLog(safeId, { status: 'error', message: err.message }));
} catch (err) {
emitStreamLog(safeId, { status: 'error', message: err.message });
}
});
module.exports = router;
FILE:routes/utilities.js
const express = require('express');
const fs = require('fs');
const path = require('path');
const archiver = require('archiver');
const { GoogleGenAI } = require('@google/genai');
const state = require('../config/state');
const router = express.Router();
const getValidatedSessionDir = (rawId) => {
if (!rawId) throw new Error("ID is required.");
const safeId = state.sanitizeId(rawId);
const baseDir = path.resolve(state.downloadsDir);
const targetDir = path.resolve(baseDir, safeId);
if (!targetDir.startsWith(baseDir + path.sep)) throw new Error("Forbidden: Invalid path traversal detected.");
return targetDir;
};
// --- NEW SESSION MANAGEMENT ENDPOINTS ---
// List all sessions (folders in downloads directory)
router.get('/sessions', (req, res) => {
try {
if (!fs.existsSync(state.downloadsDir)) return res.json({ success: true, sessions: [] });
const dirs = fs.readdirSync(state.downloadsDir, { withFileTypes: true })
.filter(dirent => dirent.isDirectory() && dirent.name !== 'temp_uploads') // Ignore temp folder
.map(dirent => dirent.name)
.sort((a, b) => {
const statA = fs.statSync(path.join(state.downloadsDir, a));
const statB = fs.statSync(path.join(state.downloadsDir, b));
return statB.mtime.getTime() - statA.mtime.getTime(); // Sort Newest First
});
res.json({ success: true, sessions: dirs });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Load the text content of a specific session
router.get('/session-data', (req, res) => {
try {
const sessionDir = getValidatedSessionDir(req.query.id);
if (!fs.existsSync(sessionDir)) return res.status(404).json({error: "Session not found"});
const readIfExists = (filename) => {
const p = path.join(sessionDir, filename);
return fs.existsSync(p) ? fs.readFileSync(p, 'utf8') : null;
};
res.json({
success: true,
originalText: readIfExists('original.txt'),
script: readIfExists('script.txt'),
prompt: readIfExists('prompt.txt'),
linkedinPost: readIfExists('linkedin_post.txt'),
hasAudio: fs.existsSync(path.join(sessionDir, 'podcast.m4a')),
hasImage: fs.existsSync(path.join(sessionDir, 'thumbnail.png')),
hasVideo: fs.existsSync(path.join(sessionDir, 'linkedin_podcast.mp4'))
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Clear all sessions
router.delete('/delete-all-sessions', (req, res) => {
try {
if (!fs.existsSync(state.downloadsDir)) return res.json({ success: true });
const dirs = fs.readdirSync(state.downloadsDir, { withFileTypes: true })
.filter(dirent => dirent.isDirectory() && dirent.name !== 'temp_uploads')
.map(dirent => dirent.name);
for (const dir of dirs) {
fs.rmSync(path.join(state.downloadsDir, dir), { recursive: true, force: true });
}
res.json({ success: true, message: "All sessions cleared." });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// --- EXISTING ENDPOINTS ---
router.post('/save-file', (req, res) => {
try {
const { id, type, content } = req.body;
const sessionDir = getValidatedSessionDir(id);
if (!fs.existsSync(sessionDir)) return res.status(404).json({error: "Session not found."});
let filename;
if (type === 'script') filename = 'script.txt';
else if (type === 'prompt') filename = 'prompt.txt';
else if (type === 'linkedin') filename = 'linkedin_post.txt';
else return res.status(400).json({error: "Invalid file type."});
fs.writeFileSync(path.join(sessionDir, filename), content);
res.json({ success: true, message: "Saved successfully!" });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.post('/translate-original', async (req, res) => {
try {
const { id, targetLanguage } = req.body;
const sessionDir = getValidatedSessionDir(id);
const originalFile = path.join(sessionDir, 'original.txt');
if (!fs.existsSync(originalFile)) return res.status(404).json({error: "Original text not found."});
const originalText = fs.readFileSync(originalFile, 'utf8');
if (targetLanguage === 'English') return res.json({ text: originalText });
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
const prompt = `Translate the following text into targetLanguage. Output ONLY the translated text.\n\nText:\noriginalText.substring(0, 25000)`;
const response = await ai.models.generateContent({ model: 'gemini-2.5-flash', contents: prompt });
res.json({ text: response.text.trim() });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.get('/download-zip', (req, res) => {
try {
const sessionDir = getValidatedSessionDir(req.query.id);
if (!fs.existsSync(sessionDir)) return res.status(404).send("Session not found.");
const safeAttachmentName = `omnicast_session_path.basename(sessionDir).zip`;
res.attachment(safeAttachmentName);
const archive = archiver('zip', { zlib: { level: 9 } });
archive.pipe(res);
archive.directory(sessionDir, false);
archive.finalize();
} catch (error) {
const status = error.message.includes('Forbidden') ? 403 : 400;
res.status(status).send(error.message);
}
});
router.delete('/delete-folder', (req, res) => {
try {
const sessionDir = getValidatedSessionDir(req.body.id);
if (fs.existsSync(sessionDir)) fs.rmSync(sessionDir, { recursive: true, force: true });
res.json({ success: true, message: "Folder safely removed." });
} catch (error) {
const status = error.message.includes('Forbidden') ? 403 : 400;
res.status(status).json({ success: false, error: error.message });
}
});
// --- NEW CONFIG ENDPOINT ---
router.get('/config', (req, res) => {
res.json({ googleClientId: process.env.GOOGLE_CLIENT_ID || null });
});
module.exports = router;
FILE:routes/youtube.js
const express = require('express');
const fs = require('fs');
const path = require('path');
const { google } = require('googleapis');
const state = require('../config/state');
const { emitStreamLog } = require('../utils/streamer');
const router = express.Router();
router.post('/upload-youtube', async (req, res) => {
const { id, title, description, accessToken } = req.body;
const safeId = state.sanitizeId(id);
const sessionDir = path.join(state.downloadsDir, safeId);
const videoPath = path.join(sessionDir, 'linkedin_podcast.mp4');
if (!accessToken) return res.status(401).json({ error: "YouTube Access Token is required." });
if (!fs.existsSync(videoPath)) return res.status(404).json({ error: "Video not found. Please generate the video package first." });
try {
emitStreamLog(safeId, { message: "Authenticating with YouTube API..." });
const oauth2Client = new google.auth.OAuth2();
oauth2Client.setCredentials({ access_token: accessToken });
const youtube = google.youtube({ version: 'v3', auth: oauth2Client });
const fileSize = fs.statSync(videoPath).size;
emitStreamLog(safeId, { message: `Uploading video to YouTube ((fileSize / (1024 * 1024)).toFixed(2) MB). This may take a moment...` });
const uploadRes = await youtube.videos.insert({
part: 'snippet,status',
notifySubscribers: false,
requestBody: {
snippet: {
title: title,
description: description,
categoryId: '24'
},
status: {
privacyStatus: 'private',
selfDeclaredMadeForKids: false
}
},
media: {
body: fs.createReadStream(videoPath)
}
});
emitStreamLog(safeId, { status: 'done', message: "YouTube upload complete!" });
res.json({ success: true, videoId: uploadRes.data.id });
} catch (error) {
const errorMessage = error.response?.data?.error?.message || error.message;
emitStreamLog(safeId, { status: 'error', message: "YouTube upload failed: " + errorMessage });
res.status(500).json({ error: errorMessage });
}
});
module.exports = router;
FILE:test-ffmpeg.js
const ffmpeg = require('fluent-ffmpeg');
console.log("🔍 Checking the bridge between Node.js and FFmpeg...");
// Ask FFmpeg for its list of available codecs
ffmpeg.getAvailableCodecs((err, codecs) => {
if (err) {
console.error("\n❌ FAILED: Node.js cannot communicate with FFmpeg.");
console.error("Error details:", err.message);
console.error("\nTroubleshooting:");
console.error("1. Did you install the actual FFmpeg software on your OS? (Run: brew install ffmpeg)");
console.error("2. If on Windows, did you add the FFmpeg /bin folder to your System PATH variables?");
console.error("3. Try restarting your terminal to refresh the PATH.");
} else {
console.log("\n✅ SUCCESS: FFmpeg is installed and communicating with Node.js!");
// Check for specific codecs we rely on
const hasMp3 = codecs['libmp3lame'] !== undefined;
const hasAac = codecs['aac'] !== undefined;
console.log(`📦 Found Object.keys(codecs).length available audio/video codecs.`);
console.log(`🎵 MP3 Encoding Support (Whisper prep): '❌ No (Missing libmp3lame)'`);
console.log(`🎵 AAC Encoding Support (Final podcast): '❌ No (Missing aac)'`);
if (hasMp3 && hasAac) {
console.log("\n🚀 All systems go! You are ready to run: npm start");
} else {
console.warn("\n⚠️ FFmpeg is responding, but you are missing essential audio encoders. Reinstall a full build of FFmpeg.");
}
}
});
FILE:utils/fileProcessor.js
const fs = require('fs');
const pdfParse = require('pdf-parse');
const { extractAndChunkAudio, transcribeChunks } = require('./mediaHelpers');
async function processLocalFile(filePath, mimeType, sessionDir, safeId) {
let text = "";
if (mimeType === 'application/pdf') {
const fileBuffer = await fs.promises.readFile(filePath);
const pdfData = await pdfParse(fileBuffer);
text = pdfData.text;
} else if (mimeType === 'text/plain') {
text = await fs.promises.readFile(filePath, 'utf8');
} else if (mimeType === 'video/mp4' || mimeType === 'audio/mp4') {
const chunkPaths = await extractAndChunkAudio(filePath, sessionDir);
text = await transcribeChunks(chunkPaths, safeId);
chunkPaths.forEach(chunk => { if (fs.existsSync(chunk)) fs.unlinkSync(chunk); });
} else {
throw new Error("Unsupported file type uploaded.");
}
return text;
}
module.exports = { processLocalFile };
FILE:utils/geminiClient.js
const { GoogleGenAI } = require('@google/genai');
const getGeminiClient = () => {
// Safely extract the key here, completely isolated from any network requests.
const geminiKey = process.env.GEMINI_API_KEY;
if (!geminiKey) {
throw new Error("GEMINI_API_KEY is missing from your .env file!");
}
// Pass the key explicitly to bypass the Vertex AI 'project' bug
return new GoogleGenAI({ apiKey: geminiKey });
};
module.exports = { getGeminiClient };
FILE:utils/mediaHelpers.js
const fs = require('fs');
const path = require('path');
const ffmpeg = require('fluent-ffmpeg');
const OpenAI = require('openai');
const { emitStreamLog } = require('./streamer');
function extractAndChunkAudio(videoPath, sessionDir) {
return new Promise((resolve, reject) => {
const chunkPattern = path.join(sessionDir, 'chunk_%03d.mp3');
ffmpeg(videoPath).noVideo().audioCodec('libmp3lame').audioBitrate('128k')
.outputOptions(['-f segment', '-segment_time 600']).output(chunkPattern)
.on('end', () => resolve(fs.readdirSync(sessionDir).filter(f => f.startsWith('chunk_') && f.endsWith('.mp3')).sort().map(f => path.join(sessionDir, f))))
.on('error', reject).run();
});
}
async function transcribeChunks(chunkPaths, sessionId) {
const openai = new OpenAI();
let fullTranscription = "";
let previousContext = "";
for (let i = 0; i < chunkPaths.length; i++) {
emitStreamLog(sessionId, { message: `[Whisper] Translating and Transcribing chunk i + 1 of chunkPaths.length to English...` });
const requestOptions = { file: fs.createReadStream(chunkPaths[i]), model: "whisper-1" };
if (previousContext) requestOptions.prompt = previousContext;
const response = await openai.audio.translations.create(requestOptions);
fullTranscription += response.text + " ";
previousContext = response.text.slice(-200);
}
return fullTranscription.trim();
}
module.exports = { extractAndChunkAudio, transcribeChunks };
FILE:utils/streamer.js
const state = require('../config/state');
function emitStreamLog(id, payload) {
// Log to the backend Node.js terminal
const time = new Date().toLocaleTimeString();
const logMsg = payload.message || payload.error || payload.status || "Processing...";
console.log(`[time] [Session: id] logMsg`);
// Stream to the frontend UI
if (state.sseClients[id]) {
state.sseClients[id].write(`data: JSON.stringify(payload)\n\n`);
}
}
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
module.exports = { emitStreamLog, delay };Extracts the original text of a Youtube video and converts it into a multi-voice AI podcast using Gemini for script generation, OpenAI for TTS, and a local N...
---
name: youtube-podcaster
description: Extracts the original text of a Youtube video and converts it into a multi-voice AI podcast using Gemini for script generation, OpenAI for TTS, and a local Node.js API with FFmpeg. It also can show you the text of the Podcast in WebVTT format.
metadata:
openclaw:
requires:
bins:
- curl
- node
- npm
- ffmpeg
env:
- GEMINI_API_KEY
- OPENAI_API_KEY
always: false
---
# YouTube Podcaster
This skill enables the automated conversion of YouTube videos into multi-host AI podcasts. It manages transcription, script generation via Gemini, and audio synthesis via OpenAI locally.
## Security Setup
For maximum security, the backend server binds strictly to `127.0.0.1`. It is not accessible from your local network or the internet.
1. **Install Dependencies:** You must run the install command once before the first use. Say:
`Run the npm install command for the youtube-podcaster skill`.
2. **Credentials:** Place your Gemini API Key and OpenAI API Key in the `.env` file within the skill folder (`skills/youtube-podcaster/.env`) using the variable names `GEMINI_API_KEY` and `OPENAI_API_KEY`.
3. **Execution:** Start the server with `npm start` or by instructing the agent: `Start the local server for the youtube-podcaster skill`.
## Usage
Once the server is running, say:
`Create a podcast for the video https://www.youtube.com/watch?v=<video_id> using the youtube-podcaster skill`
The skill orchestrates three local API calls to `localhost:7860`:
1. **Transcription:** Extracts text via the YouTube transcript API.
2. **Drafting:** Uses Gemini to create a natural dialogue script.
3. **Synthesis:** Uses OpenAI TTS (tts-1) and FFmpeg to generate a gapless `.m4a` file.
## Safe Cleanup
When you are finished using the studio, shut down the background process to free up system resources. Do not use generic kill commands. Instead, instruct the agent to use the tracked process ID:
`Stop the youtube-podcaster server process`
*(The agent will execute `kill $(cat .podcaster.pid)` or `pkill -f "node index.js"` to target the specific process safely).*
## Storage & File Outputs
Files are saved to `downloads/<session_id>/` inside the skill directory. The server includes an hourly garbage collector that automatically deletes inactive sessions.
* **Audio:** `podcast.m4a`
* **Captions:** `podcast.vtt`
* **Scripts:** `script.txt` and `original.txt`
## Source Code
The source code is available at: [https://github.com/kaudata/youtube-podcaster](https://github.com/kaudata/youtube-podcaster)
FILE:index.js
// --- LOAD ENVIRONMENT VARIABLES FIRST ---
require('dotenv').config();
const express = require('express');
const { GoogleGenAI } = require('@google/genai');
const OpenAI = require('openai');
const fs = require('fs');
const path = require('path');
const ffmpeg = require('fluent-ffmpeg');
const { fetchTranscript } = require('youtube-transcript-plus');
const archiver = require('archiver');
const rateLimit = require('express-rate-limit');
const app = express();
const port = process.env.PORT || 7860;
const host = '127.0.0.1'; // 🔒 STRICT LOCALHOST BINDING FOR SECURITY
// --- SECURITY: Process ID Tracking for Safe Shutdown ---
const pidPath = path.join(__dirname, '.podcaster.pid');
fs.writeFileSync(pidPath, process.pid.toString());
// Trust proxy for rate-limiting if deployed behind a local load balancer
app.set('trust proxy', 1);
// --- MIDDLEWARE ---
app.use(express.static('public'));
app.use('/downloads', express.static(path.join(__dirname, 'downloads')));
app.use(express.json({ limit: '50mb' }));
// --- HELPER: Unified API Key Resolver ---
const getApiKey = (req) => {
return process.env.GEMINI_API_KEY || req.headers['x-api-key'];
};
// --- HELPER: HTML Entity Decoder ---
function decodeHTML(str) {
if (!str) return "";
return str.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, "'")
.replace(/"/g, '"');
}
// --- SECURITY: RATE LIMITING ---
const globalApiLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
message: { error: "Too many requests, please slow down." }
});
const synthesizeLimiter = rateLimit({
windowMs: 60 * 60 * 1000,
max: 10,
message: { error: "Too many podcasts generated. Please try again later." },
standardHeaders: true,
legacyHeaders: false,
});
app.use('/api/', globalApiLimiter);
// --- FILE SYSTEM SETUP ---
const downloadsDir = path.join(__dirname, 'downloads');
if (!fs.existsSync(downloadsDir)) {
fs.mkdirSync(downloadsDir, { recursive: true });
console.log("✨ Created downloads directory.");
}
// --- IN-MEMORY JOB TRACKER & QUEUE ---
const jobs = {};
const jobQueue = [];
let activeJobs = 0;
const MAX_CONCURRENT_JOBS = 2;
function processNextJob() {
if (activeJobs >= MAX_CONCURRENT_JOBS || jobQueue.length === 0) return;
const queuedItem = jobQueue.shift();
if (!jobs[queuedItem.id] || jobs[queuedItem.id].status === 'cancelled') {
return processNextJob();
}
activeJobs++;
queuedItem.task().finally(() => {
activeJobs--;
processNextJob();
});
}
// Hourly Garbage Collector
setInterval(() => {
console.log("🧹 Running routine cleanup of stale data...");
const now = Date.now();
for (const id in jobs) {
if (now - jobs[id].timestamp > 60 * 60 * 1000) {
if (jobs[id].process) jobs[id].process.kill('SIGKILL');
delete jobs[id];
}
}
if (!fs.existsSync(downloadsDir)) return;
const folders = fs.readdirSync(downloadsDir);
folders.forEach(folder => {
const folderPath = path.join(downloadsDir, folder);
try {
const stats = fs.statSync(folderPath);
if (now - stats.mtimeMs > 60 * 60 * 1000) {
fs.rmSync(folderPath, { recursive: true, force: true });
}
} catch (err) {}
});
}, 60 * 60 * 1000);
// --- HELPERS ---
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
function sanitizeId(id) {
if (!id || typeof id !== 'string') return 'default_video';
return id.replace(/[^a-zA-Z0-9_-]/g, '');
}
function timestampToSeconds(ts) {
const p = ts.split(':').map(parseFloat);
return p.length === 3 ? p[0] * 3600 + p[1] * 60 + p[2] : p[0] * 60 + p[1];
}
function formatVTTTime(totalSeconds) {
const totalMs = Math.round(totalSeconds * 1000);
const h = Math.floor(totalMs / 3600000);
const m = Math.floor((totalMs % 3600000) / 60000);
const s = Math.floor((totalMs % 60000) / 1000);
const ms = totalMs % 1000;
const pad = (num, size) => num.toString().padStart(size, '0');
return `pad(h, 2):pad(m, 2):pad(s, 2).pad(ms, 3)`;
}
// --- ROUTES ---
// 1. Transcribe (POST)
app.post('/api/transcribe', async (req, res) => {
const { url, id, lang } = req.body;
if (!url) return res.status(400).json({ error: "Missing YouTube URL" });
const safeId = sanitizeId(id);
const videoDir = path.join(downloadsDir, safeId);
if (!fs.existsSync(videoDir)) fs.mkdirSync(videoDir, { recursive: true });
try {
const fetchOpts = lang ? { lang: lang } : {};
const transcript = await fetchTranscript(url, fetchOpts);
const fullText = transcript.map(t => decodeHTML(t.text)).join(' ');
fs.writeFileSync(path.join(videoDir, 'original.txt'), fullText);
let vtt = "WEBVTT\n\n";
transcript.forEach((t, i) => {
const start = new Date(t.offset * 1000).toISOString().substring(11, 19);
vtt += `i+1\nstart.000 --> start.500\ndecodeHTML(t.text)\n\n`;
});
fs.writeFileSync(path.join(videoDir, 'original.vtt'), vtt);
res.json({ fullText });
} catch (e) { res.status(500).json({ error: e.message }); }
});
// 2. Semantic VTT Search (POST)
app.post('/api/search', async (req, res) => {
const { id, query } = req.body;
const apiKey = getApiKey(req);
if (!apiKey) return res.status(401).json({ error: "API Key required" });
if (!query) return res.status(400).json({ error: "Search query required" });
const safeId = sanitizeId(id);
const vttPath = path.join(downloadsDir, safeId, 'original.vtt');
if (!fs.existsSync(vttPath)) return res.status(404).json({ error: "VTT file not found" });
try {
const vtt = fs.readFileSync(vttPath, 'utf8');
const genAI = new GoogleGenAI({ apiKey: apiKey });
const result = await genAI.models.generateContent({
model: "gemini-2.5-flash",
contents: `Find segments in this VTT relating to "query". Return JSON array: [{"text": "...", "timestamp": "HH:MM:SS"}].\n\nvtt.substring(0, 15000)`
});
const json = JSON.parse(result.text.replace(/```json|```/g, ''));
res.json({ results: json.map(r => ({ ...r, seconds: timestampToSeconds(r.timestamp) })) });
} catch (e) { res.status(500).json({ error: e.message }); }
});
// 3. Draft Script (POST) - UPDATED WITH STRICT PROMPT
app.post('/api/draft-script', async (req, res) => {
const { id, host1 = 'Alex', host2 = 'Sam', targetLanguage = 'English' } = req.body;
const apiKey = getApiKey(req);
if (!apiKey) return res.status(401).json({ error: "API Key required in .env or header" });
const safeId = sanitizeId(id);
const txtPath = path.join(downloadsDir, safeId, 'original.txt');
if (!fs.existsSync(txtPath)) return res.status(404).json({ error: "Original transcript not found" });
try {
const text = fs.readFileSync(txtPath, 'utf8');
const genAI = new GoogleGenAI({ apiKey: apiKey });
// --- STRICT PROMPT ADDED HERE ---
const prompt = `Draft a natural, conversational podcast script between host1 and host2 based on the following text.
STRICT FORMATTING RULES:
1. You MUST use exactly "host1: [dialogue]" and "host2: [dialogue]" format.
2. ABSOLUTELY NO MARKDOWN. Do not use asterisks (**), bolding, or italics anywhere in the script.
3. Output strictly as plain text.
4. The entire script MUST be written seamlessly in targetLanguage.
Text: text`;
const result = await genAI.models.generateContent({
model: "gemini-2.5-flash",
contents: prompt
});
res.json({ script: result.text });
} catch (e) { res.status(500).json({ error: e.message }); }
});
// 4. Synthesize Audio (POST) - UPDATED SANITIZER AND VOICES
app.post('/api/synthesize', synthesizeLimiter, (req, res) => {
const { id, script, host1 = 'Alex', host2 = 'Sam' } = req.body;
const openaiApiKey = process.env.OPENAI_API_KEY || req.headers['x-openai-key'];
if (!openaiApiKey) return res.status(401).json({ error: "OpenAI API Key required" });
if (!script) return res.status(400).json({ error: "Script content required" });
const safeId = sanitizeId(id);
if (jobs[safeId] && jobs[safeId].status === 'processing') {
return res.json({ success: true, jobId: safeId, message: "Job already running." });
}
jobs[safeId] = {
status: 'queued',
message: 'Waiting in queue for server resources...',
timestamp: Date.now()
};
res.json({ success: true, jobId: safeId });
const synthesisTask = async () => {
return new Promise(async (resolveTask) => {
jobs[safeId].status = 'processing';
const videoDir = path.join(downloadsDir, safeId);
const pcmPath = path.join(videoDir, 'temp.pcm');
const m4aPath = path.join(videoDir, 'podcast.m4a');
const vttPath = path.join(videoDir, 'podcast.vtt');
try {
if (!fs.existsSync(videoDir)) fs.mkdirSync(videoDir, { recursive: true });
fs.writeFileSync(path.join(videoDir, 'script.txt'), script);
const openai = new OpenAI({ apiKey: openaiApiKey });
const segments = [];
// --- SANITIZER ADDED HERE ---
const sanitizedScript = script
.replace(/<[^>]*>?/gm, '')
.replace(/\*\*/g, '') // Removes Markdown Bolding
.replace(/\*/g, '') // Removes Markdown Italics
.replace(/\r\n/g, '\n')
.replace(/[\u200B-\u200D\uFEFF]/g, '')
.trim();
const MAX_CHUNK_LENGTH = 1500;
const regex = /^([a-zA-Z0-9_ ]+)\s*:\s*([\s\S]*?)(?=^[a-zA-Z0-9_ ]+\s*:|\s*$)/gm;
let m;
while ((m = regex.exec(sanitizedScript)) !== null) {
const speaker = m[1].trim();
const text = m[2].trim();
if (speaker && text.length > 0) {
if (text.length <= MAX_CHUNK_LENGTH) {
segments.push({ s: speaker, t: text });
} else {
const sentences = text.match(/[^.?!]+[.?!]+(?:\s|$)|[^.?!]+$/g) || [text];
let currentChunk = "";
sentences.forEach(sentence => {
const cleanSentence = sentence.trim();
if (!cleanSentence) return;
if ((currentChunk.length + cleanSentence.length) > MAX_CHUNK_LENGTH) {
if (currentChunk.trim().length > 0) {
segments.push({ s: speaker, t: currentChunk.trim() });
}
if (cleanSentence.length > MAX_CHUNK_LENGTH) {
let pointer = 0;
while (pointer < cleanSentence.length) {
segments.push({ s: speaker, t: cleanSentence.substring(pointer, pointer + MAX_CHUNK_LENGTH) });
pointer += MAX_CHUNK_LENGTH;
}
currentChunk = "";
} else {
currentChunk = cleanSentence;
}
} else {
currentChunk += (currentChunk ? " " : "") + cleanSentence;
}
});
if (currentChunk.trim().length > 0) {
segments.push({ s: speaker, t: currentChunk.trim() });
}
}
}
}
if (segments.length === 0) {
jobs[safeId] = { status: 'error', message: "Could not parse any dialogue. Please ensure the script uses the 'Name: Text' format." };
return resolveTask();
}
const pcmBuffers = [];
const maleHostRef = host2.toLowerCase();
let podcastVtt = "WEBVTT\n\n";
let currentTime = 0;
for (let i = 0; i < segments.length; i++) {
const lineNum = i + 1;
const lineText = segments[i].t;
jobs[safeId].message = `Synthesizing chunk lineNum of segments.length...`;
// --- VOICE SWAP ADDED HERE ---
// OpenAI Voice Mapping: Echo is male, Shimmer is female
const voiceName = segments[i].s.toLowerCase().includes(maleHostRef) ? "echo" : "shimmer";
let audioData = null;
let attempt = 0;
const maxRetries = 3;
while (attempt <= maxRetries) {
try {
const response = await openai.audio.speech.create({
model: "tts-1",
voice: voiceName,
input: lineText,
response_format: "pcm"
});
audioData = Buffer.from(await response.arrayBuffer());
break;
} catch (apiError) {
attempt++;
const status = apiError.status || (apiError.response && apiError.response.status);
const errorCode = apiError.error?.code || apiError.code;
const errorMessage = apiError.message || "Unknown error occurred";
if (status === 401) {
jobs[safeId] = { status: 'error', message: "Invalid OpenAI API Key provided." };
return resolveTask();
}
if (status === 429 && errorCode === 'insufficient_quota') {
jobs[safeId] = { status: 'error', message: "OpenAI Account Error: Insufficient quota/credits." };
return resolveTask();
}
if (attempt > maxRetries || (status >= 400 && status < 500 && status !== 429)) {
jobs[safeId] = { status: 'error', message: `OpenAI Error: errorMessage` };
return resolveTask();
}
const backoffDelay = Math.pow(2, attempt) * 1000;
jobs[safeId].message = `OpenAI Rate Limit hit. Retrying segment lineNum in backoffDelay/1000s...`;
await delay(backoffDelay);
}
}
if (!audioData) {
podcastVtt += `lineNum\nformatVTTTime(currentTime) --> formatVTTTime(currentTime + 1)\n<v segments[i].s>[Audio generation failed]\n\n`;
const oneSecondSilence = Buffer.alloc(48000, 0);
pcmBuffers.push(oneSecondSilence);
currentTime += 1;
continue;
}
const duration = audioData.length / 48000;
podcastVtt += `lineNum\nformatVTTTime(currentTime) --> formatVTTTime(currentTime + duration)\n<v segments[i].s>lineText\n\n`;
pcmBuffers.push(audioData);
currentTime += duration;
if (i < segments.length - 1) {
if (segments[i + 1].s !== segments[i].s) {
const pauseDuration = 0.5;
const dynamicSilence = Buffer.alloc(24000 * 2 * pauseDuration, 0);
pcmBuffers.push(dynamicSilence);
currentTime += pauseDuration;
}
}
}
jobs[safeId].message = 'Compiling high-quality audio...';
fs.writeFileSync(vttPath, podcastVtt);
fs.writeFileSync(pcmPath, Buffer.concat(pcmBuffers));
jobs[safeId].process = ffmpeg(pcmPath)
.inputOptions(['-f s16le', '-ar 24000', '-ac 1'])
.outputOptions(['-c:a aac', '-b:a 96k', '-ar 24000', '-movflags +faststart'])
.save(m4aPath)
.on('end', () => {
if (fs.existsSync(pcmPath)) fs.unlinkSync(pcmPath);
jobs[safeId] = { status: 'done', file: 'podcast.m4a' };
resolveTask();
})
.on('error', (err) => {
if (!err.message.includes('SIGKILL')) {
jobs[safeId] = { status: 'error', message: err.message };
}
resolveTask();
});
} catch (e) {
jobs[safeId] = { status: 'error', message: e.message };
resolveTask();
}
});
};
jobQueue.push({ id: safeId, task: synthesisTask });
processNextJob();
});
// 5. Check Status (GET)
app.get('/api/status', (req, res) => {
const safeId = sanitizeId(req.query.id);
if (!jobs[safeId]) return res.json({ status: 'not_found' });
const { process, ...safeData } = jobs[safeId];
res.json(safeData);
});
// 6. DELETE Folder (DELETE)
app.delete('/api/delete-folder', (req, res) => {
const rawId = req.body.id || req.query.id;
if (!rawId) return res.status(400).json({ error: "Missing ID" });
const safeId = sanitizeId(rawId);
if (jobs[safeId]) {
if (jobs[safeId].process) jobs[safeId].process.kill('SIGKILL');
jobs[safeId].status = 'cancelled';
delete jobs[safeId];
}
const folder = path.join(downloadsDir, safeId);
if (fs.existsSync(folder)) {
try {
fs.rmSync(folder, { recursive: true, force: true });
res.json({ success: true, message: "Folder safely removed." });
} catch (e) { res.status(500).json({ error: "Failed to delete" }); }
} else res.status(404).json({ error: "Folder not found" });
});
// 7. Download All (GET)
app.get('/api/download-zip', (req, res) => {
const rawId = req.query.id;
if (!rawId) return res.status(400).send("Missing ID");
const safeId = sanitizeId(rawId);
if (jobs[safeId] && (jobs[safeId].status === 'processing' || jobs[safeId].status === 'queued')) {
return res.status(409).send("Podcast is still rendering. Please try again when complete.");
}
const folderPath = path.join(downloadsDir, safeId);
if (!fs.existsSync(folderPath)) return res.status(404).send("Files not found.");
res.attachment(`podcast_assets_safeId.zip`);
const archive = archiver('zip', { zlib: { level: 9 } });
archive.on('error', (err) => { res.status(500).send({ error: err.message }); });
archive.pipe(res);
const filesToZip = [
{ name: 'original_transcript.txt', local: 'original.txt' },
{ name: 'podcast_script.txt', local: 'script.txt' },
{ name: 'podcast_audio.m4a', local: 'podcast.m4a' },
{ name: 'podcast_captions.vtt', local: 'podcast.vtt' }
];
filesToZip.forEach(file => {
const filePath = path.join(folderPath, file.local);
if (fs.existsSync(filePath)) archive.file(filePath, { name: file.name });
});
archive.finalize();
});
const server = app.listen(port, host, () => {
console.log(`🚀 Hardened Studio running securely at http://host:port`);
console.log(`🔒 Bound exclusively to 127.0.0.1 (Local Access Only)`);
});
server.timeout = 0;
server.keepAliveTimeout = 0;
server.headersTimeout = 0;
FILE:package-lock.json
{
"name": "youtube-podcaster-studio",
"version": "2.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "youtube-podcaster-studio",
"version": "2.0.0",
"license": "MIT",
"dependencies": {
"@google/genai": "^1.45.0",
"archiver": "^7.0.1",
"dotenv": "^17.3.1",
"express": "^5.2.1",
"express-rate-limit": "^8.3.1",
"fluent-ffmpeg": "^2.1.3",
"youtube-transcript-plus": "^1.2.0"
}
},
"node_modules/@google/genai": {
"version": "1.45.0",
"resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.45.0.tgz",
"integrity": "sha512-+sNRWhKiRibVgc4OKi7aBJJ0A7RcoVD8tGG+eFkqxAWRjASDW+ktS9lLwTDnAxZICzCVoeAdu8dYLJVTX60N9w==",
"license": "Apache-2.0",
"dependencies": {
"google-auth-library": "^10.3.0",
"p-retry": "^4.6.2",
"protobufjs": "^7.5.4",
"ws": "^8.18.0"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"@modelcontextprotocol/sdk": "^1.25.2"
},
"peerDependenciesMeta": {
"@modelcontextprotocol/sdk": {
"optional": true
}
}
},
"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==",
"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"
},
"engines": {
"node": ">=12"
}
},
"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==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=14"
}
},
"node_modules/@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/base64": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/codegen": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/eventemitter": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
"integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/fetch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
"integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.1",
"@protobufjs/inquire": "^1.1.0"
}
},
"node_modules/@protobufjs/float": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/inquire": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
"integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/path": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/pool": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/utf8": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
"license": "BSD-3-Clause"
},
"node_modules/@types/node": {
"version": "25.5.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz",
"integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.18.0"
}
},
"node_modules/@types/retry": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
"license": "MIT"
},
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"license": "MIT",
"dependencies": {
"event-target-shim": "^5.0.0"
},
"engines": {
"node": ">=6.5"
}
},
"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": {
"mime-types": "^3.0.0",
"negotiator": "^1.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/ansi-styles": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/archiver": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz",
"integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==",
"license": "MIT",
"dependencies": {
"archiver-utils": "^5.0.2",
"async": "^3.2.4",
"buffer-crc32": "^1.0.0",
"readable-stream": "^4.0.0",
"readdir-glob": "^1.1.2",
"tar-stream": "^3.0.0",
"zip-stream": "^6.0.1"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/archiver-utils": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz",
"integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==",
"license": "MIT",
"dependencies": {
"glob": "^10.0.0",
"graceful-fs": "^4.2.0",
"is-stream": "^2.0.1",
"lazystream": "^1.0.0",
"lodash": "^4.17.15",
"normalize-path": "^3.0.0",
"readable-stream": "^4.0.0"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/async": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
"license": "MIT"
},
"node_modules/b4a": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz",
"integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==",
"license": "Apache-2.0",
"peerDependencies": {
"react-native-b4a": "*"
},
"peerDependenciesMeta": {
"react-native-b4a": {
"optional": true
}
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/bare-events": {
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz",
"integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==",
"license": "Apache-2.0",
"peerDependencies": {
"bare-abort-controller": "*"
},
"peerDependenciesMeta": {
"bare-abort-controller": {
"optional": true
}
}
},
"node_modules/bare-fs": {
"version": "4.5.5",
"resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.5.tgz",
"integrity": "sha512-XvwYM6VZqKoqDll8BmSww5luA5eflDzY0uEFfBJtFKe4PAAtxBjU3YIxzIBzhyaEQBy1VXEQBto4cpN5RZJw+w==",
"license": "Apache-2.0",
"dependencies": {
"bare-events": "^2.5.4",
"bare-path": "^3.0.0",
"bare-stream": "^2.6.4",
"bare-url": "^2.2.2",
"fast-fifo": "^1.3.2"
},
"engines": {
"bare": ">=1.16.0"
},
"peerDependencies": {
"bare-buffer": "*"
},
"peerDependenciesMeta": {
"bare-buffer": {
"optional": true
}
}
},
"node_modules/bare-os": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.8.0.tgz",
"integrity": "sha512-Dc9/SlwfxkXIGYhvMQNUtKaXCaGkZYGcd1vuNUUADVqzu4/vQfvnMkYYOUnt2VwQ2AqKr/8qAVFRtwETljgeFg==",
"license": "Apache-2.0",
"engines": {
"bare": ">=1.14.0"
}
},
"node_modules/bare-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz",
"integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==",
"license": "Apache-2.0",
"dependencies": {
"bare-os": "^3.0.1"
}
},
"node_modules/bare-stream": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.8.1.tgz",
"integrity": "sha512-bSeR8RfvbRwDpD7HWZvn8M3uYNDrk7m9DQjYOFkENZlXW8Ju/MPaqUPQq5LqJ3kyjEm07siTaAQ7wBKCU59oHg==",
"license": "Apache-2.0",
"dependencies": {
"streamx": "^2.21.0",
"teex": "^1.0.1"
},
"peerDependencies": {
"bare-buffer": "*",
"bare-events": "*"
},
"peerDependenciesMeta": {
"bare-buffer": {
"optional": true
},
"bare-events": {
"optional": true
}
}
},
"node_modules/bare-url": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz",
"integrity": "sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==",
"license": "Apache-2.0",
"dependencies": {
"bare-path": "^3.0.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==",
"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/bignumber.js": {
"version": "9.3.1",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
"integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/body-parser": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
"integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
"license": "MIT",
"dependencies": {
"bytes": "^3.1.2",
"content-type": "^1.0.5",
"debug": "^4.4.3",
"http-errors": "^2.0.0",
"iconv-lite": "^0.7.0",
"on-finished": "^2.4.1",
"qs": "^6.14.1",
"raw-body": "^3.0.1",
"type-is": "^2.0.1"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"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",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"node_modules/buffer-crc32": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz",
"integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==",
"license": "MIT",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
"license": "BSD-3-Clause"
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"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": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"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": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"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==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"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==",
"license": "MIT"
},
"node_modules/compress-commons": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz",
"integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==",
"license": "MIT",
"dependencies": {
"crc-32": "^1.2.0",
"crc32-stream": "^6.0.0",
"is-stream": "^2.0.1",
"normalize-path": "^3.0.0",
"readable-stream": "^4.0.0"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/content-disposition": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
"integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"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": ">= 0.6"
}
},
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"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",
"engines": {
"node": ">=6.6.0"
}
},
"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==",
"license": "MIT"
},
"node_modules/crc-32": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
"license": "Apache-2.0",
"bin": {
"crc32": "bin/crc32.njs"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/crc32-stream": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz",
"integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==",
"license": "MIT",
"dependencies": {
"crc-32": "^1.2.0",
"readable-stream": "^4.0.0"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/cross-spawn/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": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"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"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": 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": ">= 0.8"
}
},
"node_modules/dotenv": {
"version": "17.3.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz",
"integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"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"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"license": "MIT"
},
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"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/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"license": "MIT"
},
"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.8"
}
},
"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/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/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"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": ">= 0.6"
}
},
"node_modules/event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/events": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
"license": "MIT",
"engines": {
"node": ">=0.8.x"
}
},
"node_modules/events-universal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
"integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
"license": "Apache-2.0",
"dependencies": {
"bare-events": "^2.7.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": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/express-rate-limit": {
"version": "8.3.1",
"resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz",
"integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==",
"license": "MIT",
"dependencies": {
"ip-address": "10.1.0"
},
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://github.com/sponsors/express-rate-limit"
},
"peerDependencies": {
"express": ">= 4.11"
}
},
"node_modules/extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"license": "MIT"
},
"node_modules/fast-fifo": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
"license": "MIT"
},
"node_modules/fetch-blob": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"dependencies": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
},
"engines": {
"node": "^12.20 || >= 14.13"
}
},
"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": {
"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.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/fluent-ffmpeg": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.3.tgz",
"integrity": "sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q==",
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
"license": "MIT",
"dependencies": {
"async": "^0.2.9",
"which": "^1.1.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/fluent-ffmpeg/node_modules/async": {
"version": "0.2.10",
"resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz",
"integrity": "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ=="
},
"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==",
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"license": "MIT",
"dependencies": {
"fetch-blob": "^3.1.2"
},
"engines": {
"node": ">=12.20.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": ">= 0.6"
}
},
"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",
"engines": {
"node": ">= 0.8"
}
},
"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://github.com/sponsors/ljharb"
}
},
"node_modules/gaxios": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz",
"integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==",
"license": "Apache-2.0",
"dependencies": {
"extend": "^3.0.2",
"https-proxy-agent": "^7.0.1",
"node-fetch": "^3.3.2",
"rimraf": "^5.0.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/gcp-metadata": {
"version": "8.1.2",
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz",
"integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==",
"license": "Apache-2.0",
"dependencies": {
"gaxios": "^7.0.0",
"google-logging-utils": "^1.0.0",
"json-bigint": "^1.0.0"
},
"engines": {
"node": ">=18"
}
},
"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": {
"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.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"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": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/glob": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting [email protected]",
"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"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/google-auth-library": {
"version": "10.6.1",
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.6.1.tgz",
"integrity": "sha512-5awwuLrzNol+pFDmKJd0dKtZ0fPLAtoA5p7YO4ODsDu6ONJUVqbYwvv8y2ZBO5MBNp9TJXigB19710kYpBPdtA==",
"license": "Apache-2.0",
"dependencies": {
"base64-js": "^1.3.0",
"ecdsa-sig-formatter": "^1.0.11",
"gaxios": "7.1.3",
"gcp-metadata": "8.1.2",
"google-logging-utils": "1.1.3",
"jws": "^4.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/google-logging-utils": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz",
"integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==",
"license": "Apache-2.0",
"engines": {
"node": ">=14"
}
},
"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": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"license": "ISC"
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"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": {
"depd": "~2.0.0",
"inherits": "~2.0.4",
"setprototypeof": "~1.2.0",
"statuses": "~2.0.2",
"toidentifier": "~1.0.1"
},
"engines": {
"node": ">= 0.8"
},
"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==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/iconv-lite": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
},
"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==",
"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/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.1.0",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
"integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"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",
"engines": {
"node": ">= 0.10"
}
},
"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==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"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/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"license": "MIT",
"engines": {
"node": ">=8"
},
"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==",
"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/jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"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/json-bigint": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
"integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
"license": "MIT",
"dependencies": {
"bignumber.js": "^9.0.0"
}
},
"node_modules/jwa": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
"integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
"license": "MIT",
"dependencies": {
"buffer-equal-constant-time": "^1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/jws": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
"integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
"license": "MIT",
"dependencies": {
"jwa": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/lazystream": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz",
"integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==",
"license": "MIT",
"dependencies": {
"readable-stream": "^2.0.5"
},
"engines": {
"node": ">= 0.6.3"
}
},
"node_modules/lazystream/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==",
"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/lazystream/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==",
"license": "MIT"
},
"node_modules/lazystream/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==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/lodash": {
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"license": "MIT"
},
"node_modules/long": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"license": "Apache-2.0"
},
"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==",
"license": "ISC"
},
"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",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/media-typer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
"integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"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"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"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": ">= 0.6"
}
},
"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",
"dependencies": {
"mime-db": "^1.54.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/minimatch": {
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/minipass": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"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/negotiator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"deprecated": "Use your platform's native DOMException instead",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-fetch": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
"license": "MIT",
"dependencies": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
"formdata-polyfill": "^4.0.10"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"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"
}
},
"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": {
"wrappy": "1"
}
},
"node_modules/p-retry": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
"integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==",
"license": "MIT",
"dependencies": {
"@types/retry": "0.12.0",
"retry": "^0.13.1"
},
"engines": {
"node": ">=8"
}
},
"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==",
"license": "BlueOak-1.0.0"
},
"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",
"engines": {
"node": ">= 0.8"
}
},
"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": ">=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==",
"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/path-to-regexp": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
"integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
"license": "MIT",
"engines": {
"node": ">= 0.6.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==",
"license": "MIT"
},
"node_modules/protobufjs": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz",
"integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==",
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
"@protobufjs/aspromise": "^1.1.2",
"@protobufjs/base64": "^1.1.2",
"@protobufjs/codegen": "^2.0.4",
"@protobufjs/eventemitter": "^1.1.0",
"@protobufjs/fetch": "^1.1.0",
"@protobufjs/float": "^1.0.2",
"@protobufjs/inquire": "^1.1.0",
"@protobufjs/path": "^1.1.2",
"@protobufjs/pool": "^1.1.0",
"@protobufjs/utf8": "^1.1.0",
"@types/node": ">=13.7.0",
"long": "^5.0.0"
},
"engines": {
"node": ">=12.0.0"
}
},
"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.10"
}
},
"node_modules/qs": {
"version": "6.15.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz",
"integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==",
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"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": {
"bytes": "~3.1.2",
"http-errors": "~2.0.1",
"iconv-lite": "~0.7.0",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/readable-stream": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
"integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
"license": "MIT",
"dependencies": {
"abort-controller": "^3.0.0",
"buffer": "^6.0.3",
"events": "^3.3.0",
"process": "^0.11.10",
"string_decoder": "^1.3.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/readdir-glob": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz",
"integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==",
"license": "Apache-2.0",
"dependencies": {
"minimatch": "^5.1.0"
}
},
"node_modules/readdir-glob/node_modules/minimatch": {
"version": "5.1.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
"integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/retry": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
"integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/rimraf": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz",
"integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==",
"license": "ISC",
"dependencies": {
"glob": "^10.3.7"
},
"bin": {
"rimraf": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"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": {
"debug": "^4.4.0",
"depd": "^2.0.0",
"is-promise": "^4.0.0",
"parseurl": "^1.3.3",
"path-to-regexp": "^8.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"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/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/send": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
"integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
"license": "MIT",
"dependencies": {
"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/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": {
"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/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": {
"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==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3",
"side-channel-list": "^1.0.0",
"side-channel-map": "^1.0.1",
"side-channel-weakmap": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/side-channel-list": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"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": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.5",
"object-inspect": "^1.13.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"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",
"dependencies": {
"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": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"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==",
"license": "ISC",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"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/streamx": {
"version": "2.23.0",
"resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz",
"integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==",
"license": "MIT",
"dependencies": {
"events-universal": "^1.0.0",
"fast-fifo": "^1.3.2",
"text-decoder": "^1.1.0"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.2.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==",
"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==",
"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==",
"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==",
"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==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
"integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.2.2"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"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==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"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==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/tar-stream": {
"version": "3.1.8",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz",
"integrity": "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==",
"license": "MIT",
"dependencies": {
"b4a": "^1.6.4",
"bare-fs": "^4.5.5",
"fast-fifo": "^1.2.0",
"streamx": "^2.15.0"
}
},
"node_modules/teex": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
"integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
"license": "MIT",
"dependencies": {
"streamx": "^2.12.5"
}
},
"node_modules/text-decoder": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
"integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==",
"license": "Apache-2.0",
"dependencies": {
"b4a": "^1.6.4"
}
},
"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": ">=0.6"
}
},
"node_modules/type-is": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
"license": "MIT",
"dependencies": {
"content-type": "^1.0.5",
"media-typer": "^1.1.0",
"mime-types": "^3.0.0"
},
"engines": {
"node": ">= 0.6"
}
},
"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==",
"license": "MIT"
},
"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",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/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": ">= 0.8"
}
},
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
"license": "MIT",
"engines": {
"node": ">= 8"
}
},
"node_modules/which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"which": "bin/which"
}
},
"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==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"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==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"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==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi-cjs/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==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"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==",
"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==",
"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/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==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"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/ws": {
"version": "8.19.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/youtube-transcript-plus": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/youtube-transcript-plus/-/youtube-transcript-plus-1.2.0.tgz",
"integrity": "sha512-SRjVft8V+vUulMKgakgfzC+pnFLSy4tolX7xGnSvp9juUNocikMFmUx5GlhzLDILzxYrijcYtmNqz0qyklnPmA==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/zip-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz",
"integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==",
"license": "MIT",
"dependencies": {
"archiver-utils": "^5.0.0",
"compress-commons": "^6.0.2",
"readable-stream": "^4.0.0"
},
"engines": {
"node": ">= 14"
}
}
}
}
FILE:package.json
{
"name": "youtube-podcaster-studio",
"version": "2.0.0",
"description": "A secure, multi-step studio for converting YouTube videos into NotebookLM-style AI podcasts.",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "node --watch index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"podcast",
"gemini",
"ai",
"youtube",
"tts"
],
"author": "",
"license": "MIT",
"dependencies": {
"@google/genai": "^1.45.0",
"archiver": "^7.0.1",
"dotenv": "^17.3.1",
"express": "^5.2.1",
"express-rate-limit": "^8.3.1",
"fluent-ffmpeg": "^2.1.3",
"openai": "^6.29.0",
"youtube-transcript-plus": "^1.2.0"
}
}
FILE:public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🎙️ AI Podcast Studio</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>🎙️ AI Podcast Studio</h1>
<div class="card">
<div style="display: flex; gap: 15px; margin-bottom: 15px;">
<div class="form-group" style="flex: 1; margin-bottom: 0;">
<label>Gemini API Key (For Script)</label>
<input type="password" id="apiKey" placeholder="AIzaSy...">
</div>
<div class="form-group" style="flex: 1; margin-bottom: 0;">
<label>OpenAI API Key (For Audio)</label>
<input type="password" id="openaiApiKey" placeholder="sk-proj-...">
</div>
</div>
<div style="display: flex; gap: 15px; margin-bottom: 15px;">
<div class="form-group" style="flex: 3; margin-bottom: 0;">
<label>YouTube URL</label>
<input type="url" id="url" placeholder="https://www.youtube.com/watch?v=...">
</div>
<div class="form-group" style="flex: 1; margin-bottom: 0;">
<label>Transcript Lang</label>
<select id="transcriptLang" style="width: 100%; padding: 12px; border: 1px solid #ccc; border-radius: 4px; font-size: 1em; background: white;">
<option value="en" selected>English</option>
<option value="es">Spanish</option>
<option value="fr">French</option>
<option value="de">German</option>
<option value="sq">Albanian</option>
<option value="hi">Hindi</option>
<option value="">Auto (Default)</option>
</select>
</div>
</div>
<div style="display: flex; gap: 15px; margin-bottom: 15px;">
<div class="form-group" style="flex: 1; margin-bottom: 0;">
<label>Host 1 (Female)</label>
<input type="text" id="host1" placeholder="Alex (Default)">
</div>
<div class="form-group" style="flex: 1; margin-bottom: 0;">
<label>Host 2 (Male)</label>
<input type="text" id="host2" placeholder="Sam (Default)">
</div>
</div>
<div class="form-group">
<label>Target Language (Optional)</label>
<input type="text" id="targetLanguage" placeholder="e.g., Spanish, French, Japanese...">
</div>
<div class="btn-group">
<button type="button" id="btn-transcribe">Get Original Transcript</button>
<button type="button" id="btn-download-txt" class="secondary-btn" style="display: none;">💾 Download .txt</button>
<button type="button" id="btn-clear" style="background-color: #e74c3c; flex: 0.5;">Reset & Delete</button>
</div>
</div>
<div id="search-container" class="card" style="display: none;">
<h3>🔍 Semantic Search</h3>
<div class="form-group" style="display: flex; gap: 10px;">
<input type="text" id="search-query" placeholder="Search for a concept or quote...">
<button type="button" id="btn-search" style="flex: 0.3;">Search</button>
</div>
<div id="search-results"></div>
</div>
<div id="log-container">
<div id="log-messages"></div>
<div id="log-active-spinner" style="display: none; margin-top: 8px; color: #00ff00;">
<span class="spinner log-spinner"></span> <em>API working...</em>
</div>
</div>
<div id="editor-container" class="card" style="display: none;">
<h3>Edit Your Podcast Script</h3>
<div class="original-transcript-preview" id="yt-preview"></div>
<div style="text-align: right; margin-top: 5px; font-size: 0.85em;">
<a href="#" class="file-preview-link" data-file="original.txt" data-title="Original Transcript">📄 View original.txt</a> |
<a href="#" class="file-preview-link" data-file="original.vtt" data-title="Original Subtitles">🕒 View original.vtt</a>
</div>
<textarea id="script-editor" placeholder="Your AI-generated script will appear here..."></textarea>
<div class="btn-group">
<button type="button" id="btn-draft">Generate Podcast Script</button>
<button type="button" id="btn-download-script" class="secondary-btn" style="flex: 1;" disabled>💾 Save Script</button>
<button type="button" id="btn-generate-audio" style="flex: 2;" disabled>Finalize & Generate Audio</button>
</div>
</div>
<div id="result-container" class="card" style="display: none;">
<h2 style="color: #16a085;">✅ Production Complete</h2>
<audio id="audio-player" controls style="width: 100%;"></audio>
<div id="live-captions">
<em>Press play to see captions...</em>
</div>
<div style="margin-top: 20px; display: flex; gap: 15px; justify-content: center;">
<a id="download-podcast-vtt" href="#" class="secondary-btn" style="text-decoration: none; padding: 12px 20px; border-radius: 4px; color: white; display: inline-block; font-weight: bold; text-align: center;">📝 Download VTT Only</a>
<button type="button" id="btn-download-zip" class="secondary-btn" style="flex: unset; padding: 12px 20px;">📦 Download All (.zip)</button>
</div>
<div style="text-align: center; margin-top: 15px; font-size: 0.9em;">
<a href="#" class="file-preview-link" data-file="script.txt" data-title="Final Generated Script">📝 View script.txt</a> |
<a href="#" class="file-preview-link" data-file="podcast.vtt" data-title="Generated Audio Captions">💬 View podcast.vtt</a>
</div>
</div>
<div id="file-preview-modal" class="card" style="display: none; border-left: 5px solid #00ff00; margin-top: 20px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h3 id="preview-title" style="margin: 0; color: #00ff00; font-family: monospace;">File Preview</h3>
<div style="display: flex; gap: 10px;">
<button id="btn-copy-preview" style="background-color: #2ecc71; color: white; border: none; padding: 5px 15px; border-radius: 4px; font-weight: bold; cursor: pointer; transition: 0.2s;">📋 Copy</button>
<button id="btn-close-preview" style="background: none; color: #e74c3c; padding: 5px 10px; font-weight: bold; cursor: pointer; border: none;">✖ Close</button>
</div>
</div>
<textarea id="preview-content" readonly style="width: 100%; min-height: 400px; margin-top: 15px; cursor: default; outline: none;"></textarea>
</div>
<script src="script.js"></script>
</body>
</html>
FILE:public/script.js
let currentTranscript = "";
let currentVideoId = "";
// --- SECURE API KEY STORAGE ---
let secureApiKey = "";
let secureOpenAiKey = "";
document.getElementById('apiKey').addEventListener('change', function() {
if (this.value && this.value !== "••••••••••••••••") {
secureApiKey = this.value;
this.value = "••••••••••••••••";
this.disabled = true;
log("🔐 Gemini API Key secured in memory and removed from DOM.");
}
});
document.getElementById('openaiApiKey').addEventListener('change', function() {
if (this.value && this.value !== "••••••••••••••••") {
secureOpenAiKey = this.value;
this.value = "••••••••••••••••";
this.disabled = true;
log("🔐 OpenAI API Key secured in memory and removed from DOM.");
}
});
// --- AUTO-RESIZE TEXTAREA HELPER ---
function autoResizeEditor() {
const editor = document.getElementById('script-editor');
editor.style.height = 'auto';
editor.style.height = editor.scrollHeight + 'px';
}
document.getElementById('script-editor').addEventListener('input', function() {
document.getElementById('btn-download-script').disabled = this.value.trim().length === 0;
autoResizeEditor();
});
function generateNumericId(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i);
hash |= 0;
}
return Math.abs(hash).toString() + Date.now().toString();
}
const log = (msg) => {
const container = document.getElementById('log-container');
const messages = document.getElementById('log-messages');
container.style.display = 'block';
const msgDiv = document.createElement('div');
msgDiv.textContent = `> msg`;
messages.appendChild(msgDiv);
container.scrollTop = container.scrollHeight;
};
const setApiLoading = (isLoading) => {
const spinnerDiv = document.getElementById('log-active-spinner');
const container = document.getElementById('log-container');
if (isLoading) {
container.style.display = 'block';
spinnerDiv.style.display = 'block';
container.appendChild(spinnerDiv);
container.scrollTop = container.scrollHeight;
} else {
spinnerDiv.style.display = 'none';
}
};
// --- RESET UI & DELETE ---
document.getElementById('btn-clear').addEventListener('click', async () => {
if (currentVideoId) {
await fetch('/api/delete-folder', {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: currentVideoId })
});
}
currentTranscript = "";
currentVideoId = "";
secureApiKey = "";
const keyInput = document.getElementById('apiKey');
keyInput.value = "";
keyInput.disabled = false;
secureOpenAiKey = "";
const openaiKeyInput = document.getElementById('openaiApiKey');
openaiKeyInput.value = "";
openaiKeyInput.disabled = false;
document.getElementById('url').value = "";
document.getElementById('script-editor').value = "";
document.getElementById('script-editor').style.height = 'auto';
document.getElementById('yt-preview').innerText = "";
document.getElementById('search-results').innerHTML = "";
document.getElementById('log-messages').innerHTML = "";
document.getElementById('log-container').style.display = 'none';
setApiLoading(false);
document.getElementById('editor-container').style.display = 'none';
document.getElementById('search-container').style.display = 'none';
document.getElementById('result-container').style.display = 'none';
document.getElementById('btn-download-txt').style.display = 'none';
document.getElementById('btn-download-script').disabled = true;
document.getElementById('btn-generate-audio').disabled = true;
document.getElementById('file-preview-modal').style.display = 'none';
document.getElementById('audio-player').pause();
document.getElementById('audio-player').innerHTML = "";
document.getElementById('audio-player').removeAttribute('src');
document.getElementById('audio-player').load();
log("Studio reset for new video. Server wiped. API Keys cleared.");
});
// --- FILE PREVIEW VIEWER LOGIC ---
document.querySelectorAll('.file-preview-link').forEach(link => {
link.addEventListener('click', async (e) => {
e.preventDefault();
if (!currentVideoId) return;
const fileName = e.target.getAttribute('data-file');
const title = e.target.getAttribute('data-title');
try {
const response = await fetch(`/downloads/currentVideoId/fileName?t=Date.now()`);
if (!response.ok) throw new Error(`Could not load fileName. It may not be generated yet.`);
const rawText = await response.text();
document.getElementById('preview-title').innerText = `👀 Viewing: title (fileName)`;
// Changed from textContent to value for the textarea
document.getElementById('preview-content').value = rawText;
document.getElementById('file-preview-modal').style.display = 'block';
document.getElementById('file-preview-modal').scrollIntoView({ behavior: 'smooth' });
} catch (err) {
log(`❌ Error viewing file: err.message`);
}
});
});
document.getElementById('btn-close-preview').addEventListener('click', () => {
document.getElementById('file-preview-modal').style.display = 'none';
// Changed from textContent to value for the textarea
document.getElementById('preview-content').value = "";
});
document.getElementById('btn-copy-preview').addEventListener('click', async () => {
// Changed from textContent to value for the textarea
const textToCopy = document.getElementById('preview-content').value;
const copyBtn = document.getElementById('btn-copy-preview');
try {
copyBtn.innerHTML = '<span class="spinner"></span> Copying...';
await navigator.clipboard.writeText(textToCopy);
copyBtn.innerHTML = "✅ Copied!";
copyBtn.style.backgroundColor = "#27ae60";
setTimeout(() => {
copyBtn.innerHTML = "📋 Copy";
copyBtn.style.backgroundColor = "#2ecc71";
}, 2000);
} catch (err) {
alert("Failed to copy to clipboard.");
console.error(err);
copyBtn.innerHTML = "📋 Copy";
}
});
// --- DOWNLOAD FILES ---
document.getElementById('btn-download-txt').addEventListener('click', () => {
if (!currentTranscript) return;
const blob = new Blob([currentTranscript], { type: 'text/plain' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'youtube-transcript.txt';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
});
document.getElementById('btn-download-script').addEventListener('click', () => {
const scriptContent = document.getElementById('script-editor').value;
if (!scriptContent) return alert("The script editor is empty! Generate or write a script first.");
const blob = new Blob([scriptContent], { type: 'text/plain' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'podcast-script.txt';
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
log("✅ Podcast script downloaded.");
});
document.getElementById('btn-download-zip').addEventListener('click', () => {
if (!currentVideoId) return alert("No active video session found.");
log("📦 Compressing files into a zip archive...");
window.location.href = `/api/download-zip?id=currentVideoId`;
});
// --- STEP 1: TRANSCRIBE ---
document.getElementById('btn-transcribe').addEventListener('click', async () => {
const url = document.getElementById('url').value;
const lang = document.getElementById('transcriptLang').value;
if (!url) return alert("Enter a YouTube URL");
currentVideoId = generateNumericId(url);
log(`Fetching 'Default' transcript and creating folder securely...`);
setApiLoading(true);
try {
const res = await fetch('/api/transcribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: url, id: currentVideoId, lang: lang })
});
const data = await res.json();
if (data.fullText) {
currentTranscript = data.fullText;
document.getElementById('yt-preview').innerText = "Transcript Ready.";
document.getElementById('editor-container').style.display = 'block';
document.getElementById('search-container').style.display = 'block';
document.getElementById('btn-download-txt').style.display = 'inline-block';
log(`Secure storage created for video context.`);
} else log("❌ " + data.error);
} catch (err) {
log("Error: " + err.message);
} finally {
setApiLoading(false);
}
});
// --- SEARCH ---
document.getElementById('btn-search').addEventListener('click', async () => {
const query = document.getElementById('search-query').value;
if (!query || !secureApiKey) return alert("Gemini API Key and Query required.");
log("Searching VTT via Gemini...");
setApiLoading(true);
try {
const res = await fetch('/api/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'x-api-key': secureApiKey },
body: JSON.stringify({ id: currentVideoId, query: query })
});
const data = await res.json();
if (data.results) {
document.getElementById('search-results').innerHTML = data.results.map(r => `
<div class="search-item">
<strong>"r.text"</strong><br>
<a href="https://youtube.com/watch?v=currentVideoId&t=r.secondss" target="_blank">Jump to r.timestamp ➔</a>
</div>
`).join('');
} else log("❌ " + data.error);
} catch (err) {
log("Error: " + err.message);
} finally {
setApiLoading(false);
}
});
// --- STEP 2: DRAFT ---
document.getElementById('btn-draft').addEventListener('click', async () => {
const host1 = document.getElementById('host1').value || 'Alex';
const host2 = document.getElementById('host2').value || 'Sam';
const targetLanguage = document.getElementById('targetLanguage').value || 'English';
if (!secureApiKey) return alert("Gemini API Key required.");
log(`Generating targetLanguage script draft for host1 & host2...`);
setApiLoading(true);
try {
const res = await fetch('/api/draft-script', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'x-api-key': secureApiKey },
body: JSON.stringify({ id: currentVideoId, host1, host2, targetLanguage })
});
const data = await res.json();
if (data.script) {
document.getElementById('script-editor').value = data.script;
document.getElementById('btn-generate-audio').disabled = false;
document.getElementById('btn-download-script').disabled = false;
autoResizeEditor();
log("Draft ready for editing.");
} else log("❌ " + data.error);
} catch (err) {
log("Error: " + err.message);
} finally {
setApiLoading(false);
}
});
// --- STEP 3: AUDIO (POLLING ARCHITECTURE) ---
document.getElementById('btn-generate-audio').addEventListener('click', async function() {
const script = document.getElementById('script-editor').value;
const host1 = document.getElementById('host1').value || 'Alex';
const host2 = document.getElementById('host2').value || 'Sam';
if (!secureApiKey) return alert("Gemini API Key required for context.");
if (!secureOpenAiKey) return alert("OpenAI API Key required for audio generation.");
if (!script) return alert("Script is empty.");
this.disabled = true;
log("Starting background task... This may take several minutes.");
setApiLoading(true);
try {
const response = await fetch('/api/synthesize', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': secureApiKey,
'x-openai-key': secureOpenAiKey
},
body: JSON.stringify({ id: currentVideoId, script: script, host1: host1, host2: host2 })
});
const initData = await response.json();
if (!initData.success) throw new Error(initData.error || "Failed to start job.");
let lastMsg = "";
const pollInterval = setInterval(async () => {
try {
const statusRes = await fetch(`/api/status?id=currentVideoId`);
const data = await statusRes.json();
if (data.status === 'not_found' || data.status === 'error') {
clearInterval(pollInterval);
setApiLoading(false);
log(`❌ ' + data.message : 'Job disappeared.'`);
document.getElementById('btn-generate-audio').disabled = false;
}
else if (data.status === 'done') {
clearInterval(pollInterval);
setApiLoading(false);
handleSynthesisSuccess(data.file);
}
else if (data.status === 'processing' || data.status === 'queued') {
if (data.message && data.message !== lastMsg) {
log(data.message);
lastMsg = data.message;
}
}
} catch (pollErr) {
console.warn("Polling hiccup:", pollErr);
}
}, 2000);
} catch (err) {
log("❌ Failed to initiate task: " + err.message);
console.error(err);
setApiLoading(false);
this.disabled = false;
}
});
function handleSynthesisSuccess(file) {
const vttFile = file.replace('.m4a', '.vtt');
const audioPlayer = document.getElementById('audio-player');
const liveCaptions = document.getElementById('live-captions');
audioPlayer.innerHTML = '';
audioPlayer.removeAttribute('src');
liveCaptions.innerHTML = '<em>Press play to see captions...</em>';
const timestamp = Date.now();
const sourceNode = document.createElement('source');
sourceNode.src = `/downloads/currentVideoId/file?t=timestamp`;
sourceNode.type = 'audio/mp4';
audioPlayer.appendChild(sourceNode);
const vttBtn = document.getElementById('download-podcast-vtt');
vttBtn.href = `/downloads/currentVideoId/vttFile`;
vttBtn.download = `podcast_currentVideoId.vtt`;
const track = document.createElement('track');
track.kind = 'captions';
track.label = 'English';
track.srclang = 'en';
track.src = `/downloads/currentVideoId/vttFile?t=timestamp`;
track.default = true;
audioPlayer.appendChild(track);
audioPlayer.load();
track.addEventListener('load', function() {
const textTrack = audioPlayer.textTracks[0];
textTrack.mode = 'hidden';
textTrack.oncuechange = function(e) {
const cues = e.target.activeCues;
if (cues && cues.length > 0) {
let rawText = cues[0].text;
let formattedText = rawText.replace(/<v ([^>]+)>/g, '<strong style="color: #ffffff;">$1:</strong> ');
formattedText = formattedText.replace(/<\/v>/g, '');
formattedText = formattedText.replace(/\n/g, '<br>');
liveCaptions.innerHTML = `<span>formattedText</span>`;
} else {
liveCaptions.innerHTML = '<span style="color:#bdc3c7;">...</span>';
}
};
});
document.getElementById('result-container').style.display = 'block';
document.getElementById('btn-generate-audio').disabled = false;
log("✅ Final M4A and VTT ready!");
}
// --- AUTOMATIC CLEANUP ON TAB CLOSE ---
window.addEventListener('beforeunload', () => {
if (currentVideoId) {
fetch('/api/delete-folder', {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: currentVideoId }),
keepalive: true
}).catch(err => console.error("Tab-close cleanup failed:", err));
}
});
FILE:public/style.css
body {
font-family: system-ui, -apple-system, sans-serif;
background-color: #f4f4f9;
color: #333;
max-width: 900px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
}
h1 { color: #2c3e50; text-align: center; margin-bottom: 30px; }
.card {
background: white;
padding: 25px;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.form-group { margin-bottom: 15px; }
label {
display: block;
font-weight: bold;
margin-bottom: 5px;
font-size: 0.9em;
}
input, textarea {
width: 100%;
padding: 12px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
font-size: 1em;
}
textarea {
min-height: 350px;
max-height: 650px;
font-family: 'Courier New', Courier, monospace;
resize: vertical;
margin-top: 10px;
background-color: #1e1e1e;
color: #00ff00;
border: 1px solid #333;
padding: 15px;
line-height: 1.6;
overflow-y: auto;
transition: box-shadow 0.3s ease;
}
textarea:focus {
outline: none;
box-shadow: 0 0 0 2px rgba(46, 204, 113, 0.5);
}
textarea::placeholder {
color: rgba(0, 255, 0, 0.4);
}
.btn-group {
display: flex;
gap: 10px;
margin-top: 10px;
}
button {
color: white;
background-color: #3498db;
border: none;
padding: 12px 20px;
font-size: 14px;
border-radius: 4px;
cursor: pointer;
flex: 2;
font-weight: bold;
transition: all 0.3s ease;
}
button:hover:not(:disabled) {
filter: brightness(1.1);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
button:active:not(:disabled) {
transform: translateY(1px);
}
button:disabled {
background-color: #bdc3c7;
color: #7f8c8d;
cursor: not-allowed;
opacity: 0.65;
box-shadow: none;
animation: pulse-disabled 2s infinite ease-in-out;
}
@keyframes pulse-disabled {
0% { opacity: 0.65; }
50% { opacity: 0.85; }
100% { opacity: 0.65; }
}
.secondary-btn { background-color: #2ecc71; flex: 1; }
#log-container {
background: #1e1e1e;
color: #00ff00;
padding: 15px;
border-radius: 4px;
font-family: monospace;
height: 120px;
overflow-y: auto;
font-size: 0.85em;
display: none;
margin-bottom: 20px;
}
.original-transcript-preview {
background: #f8f9fa;
padding: 10px;
border-radius: 4px;
max-height: 80px;
overflow-y: auto;
font-size: 0.8em;
color: #666;
border: 1px inset #ddd;
}
.search-item {
background: #e8f4fd;
padding: 10px;
border-radius: 4px;
margin-top: 10px;
border-left: 4px solid #3498db;
font-size: 0.9em;
}
.search-item a { color: #2980b9; font-weight: bold; text-decoration: none; }
/* --- LIVE CAPTIONS STYLING --- */
#live-captions {
margin-top: 20px;
padding: 20px;
background: #1e1e1e;
color: #00ff00;
font-family: monospace;
border-left: 5px solid #00ff00;
border-radius: 6px;
font-size: 1.25em;
line-height: 1.5;
min-height: 3.5em;
box-shadow: inset 0 4px 6px rgba(0,0,0,0.3), 0 2px 4px rgba(0,0,0,0.05);
display: flex;
align-items: center;
justify-content: center;
text-align: center;
font-weight: 500;
transition: all 0.3s ease;
}
#live-captions strong {
color: #ffffff !important;
margin-right: 8px;
letter-spacing: 0.5px;
text-transform: uppercase;
font-size: 0.9em;
}
@keyframes textFadeIn {
from { opacity: 0; transform: translateY(5px); }
to { opacity: 1; transform: translateY(0); }
}
#live-captions span, #live-captions em {
animation: textFadeIn 0.3s ease-out forwards;
}
/* --- FILE PREVIEW LINKS --- */
.file-preview-link {
color: #3498db;
text-decoration: none;
font-weight: 600;
transition: color 0.2s;
}
.file-preview-link:hover {
color: #2980b9;
text-decoration: underline;
}
/* --- LOADING SPINNERS --- */
.spinner {
display: inline-block;
width: 14px;
height: 14px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: #ffffff;
animation: spin 1s ease-in-out infinite;
vertical-align: middle;
margin-right: 6px;
}
.log-spinner {
border-color: rgba(0, 255, 0, 0.2);
border-top-color: #00ff00;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* --- PREMIUM DARK SCROLLBAR FOR TEXTAREA & LOGS --- */
textarea::-webkit-scrollbar,
#log-container::-webkit-scrollbar,
#preview-content::-webkit-scrollbar {
width: 10px;
}
textarea::-webkit-scrollbar-track,
#log-container::-webkit-scrollbar-track,
#preview-content::-webkit-scrollbar-track {
background: #1e1e1e;
border-radius: 4px;
}
textarea::-webkit-scrollbar-thumb,
#log-container::-webkit-scrollbar-thumb,
#preview-content::-webkit-scrollbar-thumb {
background: #444;
border-radius: 4px;
}
textarea::-webkit-scrollbar-thumb:hover,
#log-container::-webkit-scrollbar-thumb:hover,
#preview-content::-webkit-scrollbar-thumb:hover {
background: #2ecc71;
}
/* --- MOBILE RESPONSIVENESS --- */
@media (max-width: 650px) {
.btn-group {
flex-direction: column;
gap: 12px;
}
.btn-group button {
width: 100%;
flex: none;
}
div[style*="display: flex; gap: 15px;"] {
flex-direction: column !important;
gap: 10px !important;
}
div[style*="display: flex; gap: 15px;"] > div {
margin-bottom: 10px !important;
}
#result-container div[style*="display: flex; gap: 15px;"] {
flex-direction: column !important;
}
#download-podcast-vtt, #btn-download-zip {
width: 100%;
box-sizing: border-box;
}
}