diff --git a/.lagoon/Dockerfile b/.lagoon/Dockerfile
index ce423975f..3a1f34446 100644
--- a/.lagoon/Dockerfile
+++ b/.lagoon/Dockerfile
@@ -15,10 +15,10 @@ RUN npm install -g pnpm@8.15.9 && pnpm config set store-dir /tmp/cache/pnpm
COPY pnpm-lock.yaml .npmrc /app/
# COPY patches /app/patches
RUN --mount=type=cache,target=/tmp/cache pnpm fetch && \
- # There is a bug in pnpm: `pnpm fetch` creates _some_ node_modules folders
- # with _some_ packages. This can lead to an incomplete package installation.
- # So we remove them now.
- find . -name 'node_modules' -type d -prune -exec rm -rf '{}' +
+ # There is a bug in pnpm: `pnpm fetch` creates _some_ node_modules folders
+ # with _some_ packages. This can lead to an incomplete package installation.
+ # So we remove them now.
+ find . -name 'node_modules' -type d -prune -exec rm -rf '{}' +
# Install composer dependencies.
# They may contain directive definitions required by prep scripts.
@@ -43,9 +43,9 @@ ENV VITE_DECAP_BRANCH="$LAGOON_GIT_BRANCH"
# Copy the all package sources, install and prepare them.
COPY . /app
RUN --mount=type=cache,target=/tmp/cache pnpm i && \
- pnpm turbo:prep && \
- # Remove all node_modules to reduce the size of the image.
- find . -name 'node_modules' -type d -prune -exec rm -rf '{}' +
+ pnpm turbo:prep && \
+ # Remove all node_modules to reduce the size of the image.
+ find . -name 'node_modules' -type d -prune -exec rm -rf '{}' +
# Deploy apps.
RUN --mount=type=cache,target=/tmp/cache pnpm deploy --filter "@custom/cms" /tmp/.deploy/cms --prod
@@ -124,3 +124,13 @@ ARG LAGOON_GIT_BRANCH
ENV VITE_DECAP_BRANCH="$LAGOON_GIT_BRANCH"
CMD pnpm publisher
+
+# ====================================================================================================
+# CONVERTER IMAGE
+# ====================================================================================================
+
+FROM uselagoon/node-18 as convertmd
+
+RUN npm install -g pnpm@8.15.9
+COPY --from=builder /tmp/.deploy/converter /app
+CMD pnpm start
diff --git a/apps/converter/README.md b/apps/converter/README.md
new file mode 100644
index 000000000..108702c7a
--- /dev/null
+++ b/apps/converter/README.md
@@ -0,0 +1,107 @@
+# Silverback Converter
+
+The converter is a Node.js application designed to convert documents from
+various formats (DocX, PDF, and HTML) into Markdown.
+
+This tool is particularly useful for developers and content creators who need to
+transform documents into a format suitable for further processing, analysis, or
+integration with other systems.
+
+## Features
+
+- **DocX to Markdown**: Convert Word documents (`.docx`) to Markdown.
+- **PDF to Markdown**: Convert PDF files to Markdown.
+- **HTML to Markdown**: Extract main content from web pages and convert it to
+ Markdown.
+- **Jina AI Integration**: Fetch and convert content using the Jina AI API.
+ (ATTENTION: EXPERIMENTAL, DO NOT USE THIS)
+
+## Setup and Installation
+
+### Prerequisites
+
+- Node.js (version 18 or higher)
+- npm (Node Package Manager)
+
+### Installation
+
+1. **Install dependencies**:
+
+ ```bash
+ npm i
+ ```
+
+2. **Set up environment variables** (optional):
+ - Create a `.env` file in the root directory.
+ - Add your Jina AI API key if you plan to use the Jina AI integration:
+ ```env
+ JINA_AI_API_KEY=your_jina_ai_api_key
+ ```
+
+### Running the Application
+
+To start the application, run the following command:
+
+```bash
+npm start
+```
+
+The server will start on `http://localhost:3000`.
+
+## Usage
+
+### Endpoints
+
+- **Convert DocX to Markdown**:
+
+ ```
+ GET /convert?path=/path/to/your/document.docx
+ ```
+
+- **Convert PDF to Markdown**:
+
+ ```
+ GET /pdf-convert?path=/path/to/your/document.pdf
+ ```
+
+- **Convert HTML to Markdown**:
+
+ ```
+ GET /html-convert?path=https://example.com
+ ```
+
+- **Fetch and Convert Content with Jina AI**:
+ ```
+ GET /jina-convert?path=https://example.com
+ ```
+
+### Example
+
+To convert a Word document to Markdown, make a GET request to:
+
+```
+http://localhost:3000/convert?path=/path/to/your/document.docx
+```
+
+The response will include the converted Markdown content, the output directory,
+and any warnings generated during the conversion process.
+
+## Configuration
+
+- **Output Directory**: By default, converted files are saved in a directory
+ named after the input file's hash. You can customize the output directory by
+ modifying the `outputDir` variable in the respective conversion scripts.
+- **Image Handling**: Images extracted from documents are saved in an `images`
+ subdirectory within the output directory.
+
+## Dependencies
+
+The application relies on several npm packages, including:
+
+- `mammoth` for DocX conversion
+- `@opendocsg/pdf2md` for PDF conversion
+- `@extractus/article-extractor` for HTML content extraction
+- `turndown` for HTML to Markdown conversion
+- `express` for the server
+
+For a complete list of dependencies, refer to the `package.json` file.
diff --git a/apps/converter/htmlToMarkdown.js b/apps/converter/htmlToMarkdown.js
new file mode 100644
index 000000000..c9abd1fbe
--- /dev/null
+++ b/apps/converter/htmlToMarkdown.js
@@ -0,0 +1,216 @@
+import { extract } from '@extractus/article-extractor';
+import crypto from 'crypto';
+import fs from 'fs-extra';
+import imageType from 'image-type';
+import { JSDOM } from 'jsdom';
+import { applyFixes } from 'markdownlint';
+import { lint as lintSync } from 'markdownlint/sync';
+import fetch from 'node-fetch';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+import {
+ convertToMarkdown,
+ generateFolderName,
+ validateAndFixMarkdown,
+} from './utils/utils.js';
+
+/**
+ * Extracts images from markdown content while preserving their positions
+ * @param {string} markdown - Original markdown content
+ * @returns {{cleanMarkdown: string, extractedImages: Array<{alt: string, url: string, position: number, placeholder: string}>}}
+ */
+function extractImagesWithPositions(markdown) {
+ const imageRegex = /!\[(.*?)\]\((.*?)\)/g;
+ const extractedImages = [];
+ let match;
+ let cleanMarkdown = markdown;
+ let index = 0;
+
+ while ((match = imageRegex.exec(markdown)) !== null) {
+ const placeholder = `__IMAGE_PLACEHOLDER_${index}__`;
+ extractedImages.push({
+ alt: match[1] || '',
+ url: match[2],
+ position: match.index,
+ placeholder,
+ });
+ index++;
+ }
+
+ // Replace images with placeholders
+ extractedImages.forEach((image) => {
+ cleanMarkdown = cleanMarkdown.replace(
+ `![${image.alt}](${image.url})`,
+ image.placeholder,
+ );
+ });
+
+ return {
+ cleanMarkdown,
+ extractedImages,
+ };
+}
+
+/**
+ * Reinserts images just above their original link positions
+ * @param {string} markdown - Markdown content with placeholders
+ * @param {Array<{alt: string, url: string, placeholder: string}>} images - Extracted images
+ * @returns {string} - Markdown with images reinserted
+ */
+function reinsertImages(markdown, images) {
+ let result = markdown;
+
+ // Sort images by their position in reverse order to maintain correct positions
+ const sortedImages = [...images].sort((a, b) => b.position - a.position);
+
+ for (const image of sortedImages) {
+ const imageMarkdown = `![${image.alt}](${image.url})\n\n`;
+ const placeholderPosition = result.indexOf(image.placeholder);
+
+ if (placeholderPosition !== -1) {
+ // Find the start of the line containing the placeholder
+ let lineStart = result.lastIndexOf('\n', placeholderPosition);
+ lineStart = lineStart === -1 ? 0 : lineStart + 1;
+
+ // Insert the image above the line containing the placeholder
+ result =
+ result.slice(0, lineStart) + imageMarkdown + result.slice(lineStart);
+
+ // Remove the placeholder
+ result = result.replace(image.placeholder, '');
+ }
+ }
+
+ // Clean up any double blank lines created during the process
+ result = result.replace(/\n{3,}/g, '\n\n');
+
+ return result.trim();
+}
+
+// @todo Fix this to work locally and live
+const isLagoon = !!process.env.LAGOON;
+const __dirname = isLagoon
+ ? '/app/web/sites/default/files/converted'
+ : '/tmp/converted';
+
+async function extractMainContentFromUrl(url) {
+ try {
+ const mainContent = await extract(url);
+ return mainContent ? mainContent.content : '';
+ } catch (err) {
+ console.error(err);
+ }
+ return '';
+}
+
+async function getImageExtension(buffer) {
+ const type = await imageType(buffer);
+ return type ? `.${type.ext}` : '.png';
+}
+
+async function downloadImage(url) {
+ try {
+ const response = await fetch(url);
+ if (!response.ok)
+ throw new Error(`Failed to fetch image: ${response.statusText}`);
+ return Buffer.from(await response.arrayBuffer());
+ } catch (error) {
+ console.warn(
+ `Warning: Failed to download image from ${url}:`,
+ error.message,
+ );
+ return null;
+ }
+}
+
+function isValidUrl(string) {
+ try {
+ new URL(string);
+ return true;
+ } catch (_) {
+ return false;
+ }
+}
+
+export async function htmlToMarkdown(url) {
+ if (!isValidUrl(url)) {
+ throw new Error('Invalid URL provided: ' + url);
+ }
+
+ const html = await extractMainContentFromUrl(url);
+ // Generate folder name based on HTML content
+ const folderName = generateFolderName(url);
+ const outputDir = path.join(__dirname, folderName);
+ const imagesDir = path.join(outputDir, 'images');
+
+ await fs.ensureDir(outputDir);
+ await fs.ensureDir(imagesDir);
+
+ // Parse HTML using JSDOM
+ const dom = new JSDOM(html);
+ const document = dom.window.document;
+
+ // Process images before conversion
+ const images = document.querySelectorAll('img');
+ const imageMap = new Map();
+
+ for (const img of images) {
+ const srcAttribute = img.getAttribute('src');
+ if (!srcAttribute) continue;
+
+ // Resolve relative URLs to absolute URLs
+ const absoluteUrl = new URL(srcAttribute, url).href;
+
+ const imageBuffer = await downloadImage(absoluteUrl);
+ if (!imageBuffer) continue;
+
+ const extension = await getImageExtension(imageBuffer);
+ const filename = `image-${crypto.randomBytes(4).toString('hex')}${extension}`;
+ const imagePath = path.join(imagesDir, filename);
+
+ await fs.writeFile(imagePath, imageBuffer);
+ imageMap.set(srcAttribute, path.join('images', filename));
+ img.setAttribute('src', path.join('images', filename));
+ }
+
+ // Convert to Markdown
+ let markdown = convertToMarkdown(document.body);
+
+ // Clean up the markdown
+ markdown = markdown
+ .replace(/\n\s*\n\s*\n/g, '\n\n')
+ .replace(/!\[\]\(/g, '![image](')
+ .trim();
+
+ const results = lintSync({ strings: { content: markdown } });
+ const fixed = applyFixes(markdown, results.content);
+ const { markdown: fixedMarkdown, warnings } = validateAndFixMarkdown(fixed);
+
+ const { cleanMarkdown, extractedImages } =
+ extractImagesWithPositions(fixedMarkdown);
+ const correctedMarkdown = reinsertImages(cleanMarkdown, extractedImages);
+
+ const fixEmptyMarkdownLinks = (markdown) => {
+ // Regular expression to match markdown links with empty URL but with title
+ // Captures: []("title")
+ const emptyLinkRegex = /\[\]\(([^)]+)\s+"([^"]+)"\)/g;
+
+ // Replace empty links with their title text as link text
+ return markdown.replace(emptyLinkRegex, (match, url, title) => {
+ return `[${title}](${url} "${title}")`;
+ });
+ };
+
+ const fixedLinksMarkdown = fixEmptyMarkdownLinks(correctedMarkdown);
+
+ // Save markdown file
+ const mdPath = path.join(outputDir, 'content.md');
+ await fs.writeFile(mdPath, fixedLinksMarkdown);
+
+ return {
+ markdownPath: mdPath,
+ warnings: warnings,
+ outputDir,
+ };
+}
diff --git a/apps/converter/index.js b/apps/converter/index.js
new file mode 100644
index 000000000..abbfc285e
--- /dev/null
+++ b/apps/converter/index.js
@@ -0,0 +1,288 @@
+import { parse } from '@textlint/markdown-to-ast';
+import express from 'express';
+import { readFileSync } from 'fs';
+import { toHtml } from 'hast-util-to-html';
+import { fromMarkdown } from 'mdast-util-from-markdown';
+import { toHast } from 'mdast-util-to-hast';
+
+import { htmlToMarkdown } from './htmlToMarkdown.js';
+import { fetchContentJinaAi } from './jinaAi.js';
+import { pdfToMarkdown } from './pdfToMarkdown.js';
+import { wordToMarkdown } from './wordToMarkdown.js';
+
+const app = express();
+const PORT = 3000;
+
+async function enhanceMdastNodesRecursive(tree, outputDir) {
+ // Process a single node and its children
+ async function processNode(node) {
+ // First process all children recursively to ensure they have htmlValue
+ if (node.children && Array.isArray(node.children)) {
+ await Promise.all(node.children.map((child) => processNode(child)));
+ }
+
+ const hast = toHast(node, { allowDangerousHtml: true });
+ const html = toHtml(hast, { allowDangerousHtml: true });
+
+ const type = node.type;
+ node.type = type.charAt(0).toUpperCase() + type.slice(1);
+ node.outputDir = outputDir;
+
+ if (!node.htmlValue) {
+ node.htmlValue = html;
+ }
+
+ if (node.type == 'Table') {
+ node.htmlValue = markdownToHtmlTable(html);
+ }
+
+ if (node.type == 'Image') {
+ node.src = `${outputDir}/${node.url}`;
+ }
+
+ return node;
+ }
+
+ return processNode(tree);
+}
+
+function markdownToHtmlTable(markdownTable) {
+ // Split the markdown table into lines
+ const lines = markdownTable.trim().split('\n');
+
+ // Extract headers (first line)
+ const headers = lines[0]
+ .split('|')
+ .map((header) => header.trim())
+ .map((header) => header.replace(/^
/, '').replace(/<\/p>$/, ''))
+ .filter((header) => header !== '');
+
+ // Remove separator line (second line with ---)
+ const dataLines = lines.slice(2);
+
+ // Create HTML table
+ let htmlTable = '
\n\n';
+
+ // Add headers
+ headers.forEach((header) => {
+ htmlTable += `\n${header} | `;
+ });
+
+ htmlTable += '\n
\n\n';
+
+ // Add table rows
+ dataLines.forEach((line) => {
+ const cells = line
+ .split('|')
+ .map((cell) => cell.trim())
+ .map((cell) => cell.replace(/^/, '').replace(/<\/p>$/, ''))
+ .filter((cell) => cell !== '');
+
+ if (cells.length > 0) {
+ htmlTable += '\n
';
+ cells.forEach((cell) => {
+ htmlTable += `\n${cell} | `;
+ });
+ htmlTable += '\n
';
+ }
+ });
+ htmlTable += '\n\n
';
+
+ return htmlTable;
+}
+
+// Express endpoint
+app.get('/convert', async (req, res) => {
+ const filePath = req.query.path;
+
+ if (!filePath) {
+ return res.status(400).json({
+ error: "Please provide a Word document path as 'path' query parameter",
+ });
+ }
+
+ try {
+ // First convert Word to Markdown
+ const { markdownPath, warnings, outputDir } =
+ await wordToMarkdown(filePath);
+
+ // Then read and process the Markdown
+ const markdown = readFileSync(markdownPath, 'utf-8');
+ const mdast = fromMarkdown(markdown);
+ const md = readFileSync(markdownPath, 'utf-8');
+ const ast = parse(md);
+
+ // This is to correct some types
+ mdast.children.forEach(async (element, index) => {
+ const hast = toHast(element, { allowDangerousHtml: true });
+ const html = toHtml(hast, { allowDangerousHtml: true });
+ element.type = ast.children[index].type;
+ element.raw = ast.children[index].raw;
+ element.htmlValue = html;
+ });
+
+ const enhanced = await enhanceMdastNodesRecursive(mdast, outputDir);
+
+ // Return the processed content along with conversion info
+ res.json({
+ content: enhanced.children,
+ outputDirectory: outputDir,
+ warnings: warnings,
+ });
+ } catch (error) {
+ if (error.code === 'ENOENT') {
+ res.status(404).json({ error: `File not found: ${filePath}` });
+ } else {
+ res.status(500).json({
+ error: 'Error processing document',
+ details: error.message,
+ });
+ }
+ }
+});
+
+app.get('/html-convert', async (req, res) => {
+ const filePath = req.query.path;
+
+ if (!filePath) {
+ return res.status(400).json({
+ error: "Please provide a URLas 'path' query parameter",
+ });
+ }
+
+ try {
+ // First convert Word to Markdown
+ const { markdownPath, warnings, outputDir } =
+ await htmlToMarkdown(filePath);
+
+ // Then read and process the Markdown
+ const markdown = readFileSync(markdownPath, 'utf-8');
+ const mdast = fromMarkdown(markdown);
+
+ const md = readFileSync(markdownPath, 'utf-8');
+ const ast = parse(md);
+
+ mdast.children.forEach(async (element, index) => {
+ const hast = toHast(element, { allowDangerousHtml: true });
+ const html = toHtml(hast, { allowDangerousHtml: true });
+ element.type = ast.children[index].type;
+ element.raw = ast.children[index].raw;
+ element.htmlValue = html;
+ });
+
+ const enhanced = await enhanceMdastNodesRecursive(mdast, outputDir);
+ // Return the processed content along with conversion info
+ res.json({
+ content: enhanced.children,
+ outputDirectory: outputDir,
+ warnings: warnings,
+ });
+ } catch (error) {
+ if (error.code === 'ENOENT') {
+ res.status(404).json({ error: `File not found: ${filePath}` });
+ } else {
+ res.status(500).json({
+ error: 'Error processing document',
+ details: error.message,
+ });
+ }
+ }
+});
+
+app.get('/jina-convert', async (req, res) => {
+ const url = req.query.path;
+
+ if (!url) {
+ return res.status(400).json({
+ error: "Please provide a URLas 'path' query parameter",
+ });
+ }
+
+ try {
+ // First convert Word to Markdown
+ const { markdownPath, warnings, outputDir } = await fetchContentJinaAi(url);
+
+ // Then read and process the Markdown
+ const markdown = readFileSync(markdownPath, 'utf-8');
+ const mdast = fromMarkdown(markdown);
+
+ const md = readFileSync(markdownPath, 'utf-8');
+ const ast = parse(md);
+
+ mdast.children.forEach(async (element, index) => {
+ const hast = toHast(element, { allowDangerousHtml: true });
+ const html = toHtml(hast, { allowDangerousHtml: true });
+ element.type = ast.children[index].type;
+ element.raw = ast.children[index].raw;
+ element.htmlValue = html;
+ });
+
+ const enhanced = await enhanceMdastNodesRecursive(mdast, outputDir);
+ // Return the processed content along with conversion info
+ res.json({
+ content: enhanced.children,
+ outputDirectory: outputDir,
+ warnings: warnings,
+ });
+ } catch (error) {
+ if (error.code === 'ENOENT') {
+ res.status(404).json({ error: `File not found: ${url}` });
+ } else {
+ res.status(500).json({
+ error: 'Error processing document',
+ details: error.message,
+ });
+ }
+ }
+});
+
+app.get('/pdf-convert', async (req, res) => {
+ const filePath = req.query.path;
+
+ if (!filePath) {
+ return res.status(400).json({
+ error: "Please provide a URLas 'path' query parameter",
+ });
+ }
+
+ try {
+ // First convert Word to Markdown
+ const { markdownPath, warnings, outputDir } = await pdfToMarkdown(filePath);
+
+ // Then read and process the Markdown
+ const markdown = readFileSync(markdownPath, 'utf-8');
+ const mdast = fromMarkdown(markdown);
+
+ const md = readFileSync(markdownPath, 'utf-8');
+ const ast = parse(md);
+
+ mdast.children.forEach(async (element, index) => {
+ const hast = toHast(element, { allowDangerousHtml: true });
+ const html = toHtml(hast, { allowDangerousHtml: true });
+ element.type = ast.children[index].type;
+ element.raw = ast.children[index].raw;
+ element.htmlValue = html;
+ });
+
+ const enhanced = await enhanceMdastNodesRecursive(mdast, outputDir);
+ // Return the processed content along with conversion info
+ res.json({
+ content: enhanced.children,
+ outputDirectory: outputDir,
+ warnings: warnings,
+ });
+ } catch (error) {
+ if (error.code === 'ENOENT') {
+ res.status(404).json({ error: `File not found: ${filePath}` });
+ } else {
+ res.status(500).json({
+ error: 'Error processing document',
+ details: error.message,
+ });
+ }
+ }
+});
+
+app.listen(PORT, () => {
+ console.log(`Server running on http://localhost:${PORT}`);
+});
diff --git a/apps/converter/jinaAi.js b/apps/converter/jinaAi.js
new file mode 100644
index 000000000..371d9d223
--- /dev/null
+++ b/apps/converter/jinaAi.js
@@ -0,0 +1,86 @@
+import fs from 'fs-extra';
+import { applyFixes } from 'markdownlint';
+import { lint as lintSync } from 'markdownlint/sync';
+import fetch from 'node-fetch';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+import { generateFolderName, validateAndFixMarkdown } from './utils/utils.js';
+
+const isLagoon = !!process.env.LAGOON;
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = isLagoon
+ ? '/app/web/sites/default/files/converted'
+ : path.dirname(__filename);
+
+export async function fetchContentJinaAi(url) {
+ const apiKey =
+ process.env.JINA_AI_API_KEY ||
+ 'jina_c436e2d8a5474a71b232f4286de387d6n0MVWKn1aOY3BNfVGE0gJH300OI0';
+
+ try {
+ // Encode the URL to handle special characters
+ const encodedUrl = encodeURIComponent(url);
+
+ // Make the request to the Jina API
+ const response = await fetch(`https://r.jina.ai/${encodedUrl}`, {
+ method: 'GET',
+ headers: {
+ Authorization: `Bearer ${apiKey}`,
+ Accept: 'application/json',
+ },
+ });
+
+ // Check if the response is ok
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ // Parse and return the response
+ const fetchedContent = await response.json();
+
+ const folderName = generateFolderName(url);
+ const outputDir = path.join(__dirname, folderName);
+ const imagesDir = path.join(outputDir, 'images');
+
+ await fs.ensureDir(outputDir);
+ await fs.ensureDir(imagesDir);
+
+ let markdown = fetchedContent?.data.content || '';
+
+ // Clean up the markdown
+ markdown = markdown
+ .replace(/\n\s*\n\s*\n/g, '\n\n')
+ .replace(/!\[\]\(/g, '![image](')
+ .trim();
+
+ const results = lintSync({ strings: { content: markdown } });
+ const fixed = applyFixes(markdown, results.content);
+ const { markdown: fixedMarkdown, warnings } = validateAndFixMarkdown(fixed);
+
+ const fixEmptyMarkdownLinks = (markdown) => {
+ // Regular expression to match markdown links with empty URL but with title
+ // Captures: []("title")
+ const emptyLinkRegex = /\[\]\(([^)]+)\s+"([^"]+)"\)/g;
+
+ // Replace empty links with their title text as link text
+ return markdown.replace(emptyLinkRegex, (match, url, title) => {
+ return `[${title}](${url} "${title}")`;
+ });
+ };
+
+ const fixedLinksMarkdown = fixEmptyMarkdownLinks(fixedMarkdown);
+
+ const mdPath = path.join(outputDir, 'content.md');
+ await fs.writeFile(mdPath, fixedLinksMarkdown);
+
+ return {
+ markdownPath: mdPath,
+ warnings: warnings,
+ outputDir,
+ };
+ } catch (error) {
+ console.error('Error fetching content:', error);
+ throw error;
+ }
+}
diff --git a/apps/converter/package-lock.json b/apps/converter/package-lock.json
new file mode 100644
index 000000000..fbc3bb1b4
--- /dev/null
+++ b/apps/converter/package-lock.json
@@ -0,0 +1,4118 @@
+{
+ "name": "@custom/converter",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "@custom/converter",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "@extractus/article-extractor": "^8.0.16",
+ "@textlint/markdown-to-ast": "^14.3.0",
+ "dotenv": "^16.4.7",
+ "express": "^4.21.1",
+ "fs-extra": "^11.2.0",
+ "hast-util-to-html": "^9.0.3",
+ "image-type": "^5.2.0",
+ "jsdom": "^25.0.1",
+ "langchain": "^0.3.6",
+ "mammoth": "^1.8.0",
+ "mdast-util-from-markdown": "^2.0.2",
+ "mdast-util-to-hast": "^13.2.0",
+ "node-fetch": "^3.3.2",
+ "openai": "^4.76.1",
+ "pdf-parse": "github:iamh2o/pdf-parse#1.1.3",
+ "pdf2json": "^3.1.4",
+ "sanitize-filename": "^1.6.3",
+ "turndown": "^7.2.0",
+ "unist-util-visit": "^5.0.0"
+ }
+ },
+ "node_modules/@cfworker/json-schema": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.0.3.tgz",
+ "integrity": "sha512-ZykIcDTVv5UNmKWSTLAs3VukO6NDJkkSKxrgUTDPBkAlORVT3H9n5DbRjRl8xIotklscHdbLIa0b9+y3mQq73g==",
+ "peer": true
+ },
+ "node_modules/@extractus/article-extractor": {
+ "version": "8.0.16",
+ "resolved": "https://registry.npmjs.org/@extractus/article-extractor/-/article-extractor-8.0.16.tgz",
+ "integrity": "sha512-amxCKO2uerY0UPxDVSoTDdcTny0otpKsAIGC2q2CUDEhUX6EfxmpURttlKLx9uWFT9DRlNX9LSyMSP/2p7kFLg==",
+ "dependencies": {
+ "@mozilla/readability": "^0.5.0",
+ "bellajs": "^11.2.0",
+ "cross-fetch": "^4.0.0",
+ "linkedom": "^0.18.5",
+ "sanitize-html": "2.13.1"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/@langchain/core": {
+ "version": "0.3.23",
+ "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.23.tgz",
+ "integrity": "sha512-Aut43dEJYH/ibccSErFOLQzymkBG4emlN16P0OHWwx02bDosOR9ilZly4JJiCSYcprn2X2H8nee6P/4VMg1oQA==",
+ "peer": true,
+ "dependencies": {
+ "@cfworker/json-schema": "^4.0.2",
+ "ansi-styles": "^5.0.0",
+ "camelcase": "6",
+ "decamelize": "1.2.0",
+ "js-tiktoken": "^1.0.12",
+ "langsmith": "^0.2.8",
+ "mustache": "^4.2.0",
+ "p-queue": "^6.6.2",
+ "p-retry": "4",
+ "uuid": "^10.0.0",
+ "zod": "^3.22.4",
+ "zod-to-json-schema": "^3.22.3"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@langchain/openai": {
+ "version": "0.3.14",
+ "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.3.14.tgz",
+ "integrity": "sha512-lNWjUo1tbvsss45IF7UQtMu1NJ6oUKvhgPYWXnX9f/d6OmuLu7D99HQ3Y88vLcUo9XjjOy417olYHignMduMjA==",
+ "dependencies": {
+ "js-tiktoken": "^1.0.12",
+ "openai": "^4.71.0",
+ "zod": "^3.22.4",
+ "zod-to-json-schema": "^3.22.3"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@langchain/core": ">=0.2.26 <0.4.0"
+ }
+ },
+ "node_modules/@langchain/textsplitters": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/@langchain/textsplitters/-/textsplitters-0.1.0.tgz",
+ "integrity": "sha512-djI4uw9rlkAb5iMhtLED+xJebDdAG935AdP4eRTB02R7OB/act55Bj9wsskhZsvuyQRpO4O1wQOp85s6T6GWmw==",
+ "dependencies": {
+ "js-tiktoken": "^1.0.12"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@langchain/core": ">=0.2.21 <0.4.0"
+ }
+ },
+ "node_modules/@mixmark-io/domino": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz",
+ "integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="
+ },
+ "node_modules/@mozilla/readability": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@mozilla/readability/-/readability-0.5.0.tgz",
+ "integrity": "sha512-Z+CZ3QaosfFaTqvhQsIktyGrjFjSC0Fa4EMph4mqKnWhmyoGICsV/8QK+8HpXut6zV7zwfWwqDmEjtk1Qf6EgQ==",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@textlint/ast-node-types": {
+ "version": "14.3.0",
+ "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-14.3.0.tgz",
+ "integrity": "sha512-baDgKcA8MeO55I2+LNc9FTAJ/aUKlxN6DgM5B511tT9kDwECXRk+iYi/H+oaP25z5Zq3FqrL6n7mmyfFWDUWkQ=="
+ },
+ "node_modules/@textlint/markdown-to-ast": {
+ "version": "14.3.0",
+ "resolved": "https://registry.npmjs.org/@textlint/markdown-to-ast/-/markdown-to-ast-14.3.0.tgz",
+ "integrity": "sha512-z4UMKFh3r5KtylPt5OO6su7DScU+fMZ7Qv5LTrJNaOqcmOzFho64Y1I26BJv86f8BC+MUYP0kza5MZGaR2LYQA==",
+ "dependencies": {
+ "@textlint/ast-node-types": "^14.3.0",
+ "debug": "^4.3.4",
+ "mdast-util-gfm-autolink-literal": "^0.1.3",
+ "neotraverse": "^0.6.15",
+ "remark-footnotes": "^3.0.0",
+ "remark-frontmatter": "^3.0.0",
+ "remark-gfm": "^1.0.0",
+ "remark-parse": "^9.0.0",
+ "unified": "^9.2.2"
+ }
+ },
+ "node_modules/@tokenizer/token": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
+ "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="
+ },
+ "node_modules/@types/debug": {
+ "version": "4.1.12",
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
+ "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
+ "dependencies": {
+ "@types/ms": "*"
+ }
+ },
+ "node_modules/@types/hast": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/@types/mdast": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
+ "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/@types/ms": {
+ "version": "0.7.34",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
+ "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="
+ },
+ "node_modules/@types/node": {
+ "version": "18.19.67",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.67.tgz",
+ "integrity": "sha512-wI8uHusga+0ZugNp0Ol/3BqQfEcCCNfojtO6Oou9iVNGPTL6QNSdnUdqq85fRgIorLhLMuPIKpsN98QE9Nh+KQ==",
+ "dependencies": {
+ "undici-types": "~5.26.4"
+ }
+ },
+ "node_modules/@types/node-fetch": {
+ "version": "2.6.12",
+ "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz",
+ "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==",
+ "dependencies": {
+ "@types/node": "*",
+ "form-data": "^4.0.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=="
+ },
+ "node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="
+ },
+ "node_modules/@types/uuid": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
+ "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ=="
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
+ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ=="
+ },
+ "node_modules/@xmldom/xmldom": {
+ "version": "0.8.10",
+ "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
+ "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "event-target-shim": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=6.5"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
+ "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/agentkeepalive": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz",
+ "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==",
+ "dependencies": {
+ "humanize-ms": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "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=="
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
+ "node_modules/bail": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz",
+ "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "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"
+ }
+ ]
+ },
+ "node_modules/bellajs": {
+ "version": "11.2.0",
+ "resolved": "https://registry.npmjs.org/bellajs/-/bellajs-11.2.0.tgz",
+ "integrity": "sha512-Wjss+Bc674ZABPr+SCKWTqA4V1pyYFhzDTjNBJy4jdmgOv0oGIGXeKBRJyINwP5tIy+iIZD9SfgZpztduzQ5QA==",
+ "engines": {
+ "node": ">= 18.4"
+ }
+ },
+ "node_modules/bluebird": {
+ "version": "3.4.7",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz",
+ "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA=="
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.3",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
+ "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
+ "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.0",
+ "iconv-lite": "0.4.24",
+ "on-finished": "2.4.1",
+ "qs": "6.13.0",
+ "raw-body": "2.5.2",
+ "type-is": "~1.6.18",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/body-parser/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/body-parser/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "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=="
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
+ "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "set-function-length": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ccount": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
+ "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
+ "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-html4": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
+ "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-legacy": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz",
+ "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-reference-invalid": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz",
+ "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/comma-separated-tokens": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
+ "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/commander": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
+ "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "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==",
+ "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==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
+ "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
+ },
+ "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=="
+ },
+ "node_modules/cross-fetch": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
+ "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==",
+ "dependencies": {
+ "node-fetch": "^2.6.12"
+ }
+ },
+ "node_modules/cross-fetch/node_modules/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/cross-fetch/node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
+ },
+ "node_modules/cross-fetch/node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
+ },
+ "node_modules/cross-fetch/node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
+ "node_modules/css-select": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
+ "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
+ "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.1.0",
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
+ "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
+ "engines": {
+ "node": ">= 6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/fb55"
+ }
+ },
+ "node_modules/cssom": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
+ "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw=="
+ },
+ "node_modules/cssstyle": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.1.0.tgz",
+ "integrity": "sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==",
+ "dependencies": {
+ "rrweb-cssom": "^0.7.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/data-urls": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
+ "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
+ "dependencies": {
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/decimal.js": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
+ "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA=="
+ },
+ "node_modules/decode-named-character-reference": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz",
+ "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==",
+ "dependencies": {
+ "character-entities": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/define-data-property": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "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==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/devlop": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
+ "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
+ "dependencies": {
+ "dequal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/dingbat-to-unicode": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dingbat-to-unicode/-/dingbat-to-unicode-1.0.1.tgz",
+ "integrity": "sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w=="
+ },
+ "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==",
+ "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"
+ }
+ ]
+ },
+ "node_modules/domhandler": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
+ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
+ "dependencies": {
+ "domelementtype": "^2.3.0"
+ },
+ "engines": {
+ "node": ">= 4"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
+ }
+ },
+ "node_modules/domutils": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
+ "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
+ "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": "16.4.7",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
+ "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/duck": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/duck/-/duck-0.1.12.tgz",
+ "integrity": "sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==",
+ "dependencies": {
+ "underscore": "^1.13.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=="
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
+ "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
+ "dependencies": {
+ "get-intrinsic": "^1.2.4"
+ },
+ "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==",
+ "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=="
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "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==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/eventemitter3": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
+ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
+ },
+ "node_modules/express": {
+ "version": "4.21.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
+ "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
+ "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.10",
+ "proxy-addr": "~2.0.7",
+ "qs": "6.13.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"
+ }
+ },
+ "node_modules/express/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/express/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "node_modules/extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+ },
+ "node_modules/fault": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz",
+ "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==",
+ "dependencies": {
+ "format": "^0.2.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "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"
+ }
+ ],
+ "dependencies": {
+ "node-domexception": "^1.0.0",
+ "web-streams-polyfill": "^3.0.3"
+ },
+ "engines": {
+ "node": "^12.20 || >= 14.13"
+ }
+ },
+ "node_modules/file-type": {
+ "version": "18.7.0",
+ "resolved": "https://registry.npmjs.org/file-type/-/file-type-18.7.0.tgz",
+ "integrity": "sha512-ihHtXRzXEziMrQ56VSgU7wkxh55iNchFkosu7Y9/S+tXHdKyrGjVK0ujbqNnsxzea+78MaLhN6PGmfYSAv1ACw==",
+ "dependencies": {
+ "readable-web-to-node-stream": "^3.0.2",
+ "strtok3": "^7.0.0",
+ "token-types": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/file-type?sponsor=1"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
+ "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
+ "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.1",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/finalhandler/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/finalhandler/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "node_modules/form-data": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
+ "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/form-data-encoder": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
+ "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="
+ },
+ "node_modules/format": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
+ "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==",
+ "engines": {
+ "node": ">=0.4.x"
+ }
+ },
+ "node_modules/formdata-node": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
+ "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
+ "dependencies": {
+ "node-domexception": "1.0.0",
+ "web-streams-polyfill": "4.0.0-beta.3"
+ },
+ "engines": {
+ "node": ">= 12.20"
+ }
+ },
+ "node_modules/formdata-node/node_modules/web-streams-polyfill": {
+ "version": "4.0.0-beta.3",
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
+ "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "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==",
+ "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==",
+ "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==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fs-extra": {
+ "version": "11.2.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
+ "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==",
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.14"
+ }
+ },
+ "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==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
+ "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "hasown": "^2.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+ "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+ "dependencies": {
+ "get-intrinsic": "^1.1.3"
+ },
+ "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=="
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+ "dependencies": {
+ "es-define-property": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-proto": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
+ "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+ "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==",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hast-util-to-html": {
+ "version": "9.0.3",
+ "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.3.tgz",
+ "integrity": "sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "ccount": "^2.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "hast-util-whitespace": "^3.0.0",
+ "html-void-elements": "^3.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "stringify-entities": "^4.0.0",
+ "zwitch": "^2.0.4"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-whitespace": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
+ "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/html-encoding-sniffer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
+ "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
+ "dependencies": {
+ "whatwg-encoding": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/html-escaper": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz",
+ "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="
+ },
+ "node_modules/html-void-elements": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz",
+ "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/htmlparser2": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz",
+ "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==",
+ "funding": [
+ "https://github.com/fb55/htmlparser2?sponsor=1",
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3",
+ "domutils": "^3.1.0",
+ "entities": "^4.5.0"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/http-proxy-agent": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/humanize-ms": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
+ "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
+ "dependencies": {
+ "ms": "^2.0.0"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "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"
+ }
+ ]
+ },
+ "node_modules/image-type": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/image-type/-/image-type-5.2.0.tgz",
+ "integrity": "sha512-f0+6qHeGfyEh1HhFGPUWZb+Dqqm6raKeeAR6Opt01wBBIQL32/1wpZkPQm8gcliB/Ws6oiX2ofFYXB57+CV0iQ==",
+ "dependencies": {
+ "file-type": "^18.1.0"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/immediate": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "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==",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-alphabetical": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz",
+ "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-alphanumerical": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz",
+ "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==",
+ "dependencies": {
+ "is-alphabetical": "^1.0.0",
+ "is-decimal": "^1.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-buffer": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz",
+ "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/is-decimal": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz",
+ "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-hexadecimal": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz",
+ "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-plain-obj": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+ "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-plain-object": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
+ "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-potential-custom-element-name": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="
+ },
+ "node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
+ },
+ "node_modules/js-tiktoken": {
+ "version": "1.0.15",
+ "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.15.tgz",
+ "integrity": "sha512-65ruOWWXDEZHHbAo7EjOcNxOGasQKbL4Fq3jEr2xsCqSsoOo6VVSqzWQb6PRIqypFSDcma4jO90YP0w5X8qVXQ==",
+ "dependencies": {
+ "base64-js": "^1.5.1"
+ }
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/js-yaml/node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
+ },
+ "node_modules/jsdom": {
+ "version": "25.0.1",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-25.0.1.tgz",
+ "integrity": "sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==",
+ "dependencies": {
+ "cssstyle": "^4.1.0",
+ "data-urls": "^5.0.0",
+ "decimal.js": "^10.4.3",
+ "form-data": "^4.0.0",
+ "html-encoding-sniffer": "^4.0.0",
+ "http-proxy-agent": "^7.0.2",
+ "https-proxy-agent": "^7.0.5",
+ "is-potential-custom-element-name": "^1.0.1",
+ "nwsapi": "^2.2.12",
+ "parse5": "^7.1.2",
+ "rrweb-cssom": "^0.7.1",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^5.0.0",
+ "w3c-xmlserializer": "^5.0.0",
+ "webidl-conversions": "^7.0.0",
+ "whatwg-encoding": "^3.1.1",
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.0.0",
+ "ws": "^8.18.0",
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "canvas": "^2.11.2"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dependencies": {
+ "universalify": "^2.0.0"
+ },
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/jsonpointer": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz",
+ "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/jszip": {
+ "version": "3.10.1",
+ "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
+ "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
+ "dependencies": {
+ "lie": "~3.3.0",
+ "pako": "~1.0.2",
+ "readable-stream": "~2.3.6",
+ "setimmediate": "^1.0.5"
+ }
+ },
+ "node_modules/langchain": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/langchain/-/langchain-0.3.6.tgz",
+ "integrity": "sha512-erZOIKXzwCOrQHqY9AyjkQmaX62zUap1Sigw1KrwMUOnVoLKkVNRmAyxFlNZDZ9jLs/58MaQcaT9ReJtbj3x6w==",
+ "dependencies": {
+ "@langchain/openai": ">=0.1.0 <0.4.0",
+ "@langchain/textsplitters": ">=0.0.0 <0.2.0",
+ "js-tiktoken": "^1.0.12",
+ "js-yaml": "^4.1.0",
+ "jsonpointer": "^5.0.1",
+ "langsmith": "^0.2.0",
+ "openapi-types": "^12.1.3",
+ "p-retry": "4",
+ "uuid": "^10.0.0",
+ "yaml": "^2.2.1",
+ "zod": "^3.22.4",
+ "zod-to-json-schema": "^3.22.3"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@langchain/anthropic": "*",
+ "@langchain/aws": "*",
+ "@langchain/cohere": "*",
+ "@langchain/core": ">=0.2.21 <0.4.0",
+ "@langchain/google-genai": "*",
+ "@langchain/google-vertexai": "*",
+ "@langchain/groq": "*",
+ "@langchain/mistralai": "*",
+ "@langchain/ollama": "*",
+ "axios": "*",
+ "cheerio": "*",
+ "handlebars": "^4.7.8",
+ "peggy": "^3.0.2",
+ "typeorm": "*"
+ },
+ "peerDependenciesMeta": {
+ "@langchain/anthropic": {
+ "optional": true
+ },
+ "@langchain/aws": {
+ "optional": true
+ },
+ "@langchain/cohere": {
+ "optional": true
+ },
+ "@langchain/google-genai": {
+ "optional": true
+ },
+ "@langchain/google-vertexai": {
+ "optional": true
+ },
+ "@langchain/groq": {
+ "optional": true
+ },
+ "@langchain/mistralai": {
+ "optional": true
+ },
+ "@langchain/ollama": {
+ "optional": true
+ },
+ "axios": {
+ "optional": true
+ },
+ "cheerio": {
+ "optional": true
+ },
+ "handlebars": {
+ "optional": true
+ },
+ "peggy": {
+ "optional": true
+ },
+ "typeorm": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/langsmith": {
+ "version": "0.2.11",
+ "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.2.11.tgz",
+ "integrity": "sha512-rVPUN/jQEHjTuYaoVKGjfb3NsYNLGTQT9LXcgJvka5M0EDcXciC598A+DsAQrl6McdfSJCFJDelgRPqVoF2xNA==",
+ "dependencies": {
+ "@types/uuid": "^10.0.0",
+ "commander": "^10.0.1",
+ "p-queue": "^6.6.2",
+ "p-retry": "4",
+ "semver": "^7.6.3",
+ "uuid": "^10.0.0"
+ },
+ "peerDependencies": {
+ "openai": "*"
+ },
+ "peerDependenciesMeta": {
+ "openai": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/lie": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
+ "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+ "dependencies": {
+ "immediate": "~3.0.5"
+ }
+ },
+ "node_modules/linkedom": {
+ "version": "0.18.5",
+ "resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.18.5.tgz",
+ "integrity": "sha512-JGLaGGtqtu+eOhYrC1wkWYTBcpVWL4AsnwAtMtgO1Q0gI0PuPJKI0zBBE+a/1BrhOE3Uw8JI/ycByAv5cLrAuQ==",
+ "dependencies": {
+ "css-select": "^5.1.0",
+ "cssom": "^0.5.0",
+ "html-escaper": "^3.0.3",
+ "htmlparser2": "^9.1.0",
+ "uhyphen": "^0.2.0"
+ }
+ },
+ "node_modules/longest-streak": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.4.tgz",
+ "integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/lop": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/lop/-/lop-0.4.2.tgz",
+ "integrity": "sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw==",
+ "dependencies": {
+ "duck": "^0.1.12",
+ "option": "~0.2.1",
+ "underscore": "^1.13.1"
+ }
+ },
+ "node_modules/mammoth": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/mammoth/-/mammoth-1.8.0.tgz",
+ "integrity": "sha512-pJNfxSk9IEGVpau+tsZFz22ofjUsl2mnA5eT8PjPs2n0BP+rhVte4Nez6FdgEuxv3IGI3afiV46ImKqTGDVlbA==",
+ "dependencies": {
+ "@xmldom/xmldom": "^0.8.6",
+ "argparse": "~1.0.3",
+ "base64-js": "^1.5.1",
+ "bluebird": "~3.4.0",
+ "dingbat-to-unicode": "^1.0.1",
+ "jszip": "^3.7.1",
+ "lop": "^0.4.1",
+ "path-is-absolute": "^1.0.0",
+ "underscore": "^1.13.1",
+ "xmlbuilder": "^10.0.0"
+ },
+ "bin": {
+ "mammoth": "bin/mammoth"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/markdown-table": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz",
+ "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==",
+ "dependencies": {
+ "repeat-string": "^1.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/mdast-util-find-and-replace": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-1.1.1.tgz",
+ "integrity": "sha512-9cKl33Y21lyckGzpSmEQnIDjEfeeWelN5s1kUW1LwdB0Fkuq2u+4GdqcGEygYxJE8GVqCl0741bYXHgamfWAZA==",
+ "dependencies": {
+ "escape-string-regexp": "^4.0.0",
+ "unist-util-is": "^4.0.0",
+ "unist-util-visit-parents": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-footnote": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/mdast-util-footnote/-/mdast-util-footnote-0.1.7.tgz",
+ "integrity": "sha512-QxNdO8qSxqbO2e3m09KwDKfWiLgqyCurdWTQ198NpbZ2hxntdc+VKS4fDJCmNWbAroUdYnSthu+XbZ8ovh8C3w==",
+ "dependencies": {
+ "mdast-util-to-markdown": "^0.6.0",
+ "micromark": "~2.11.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-footnote/node_modules/micromark": {
+ "version": "2.11.4",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz",
+ "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "debug": "^4.0.0",
+ "parse-entities": "^2.0.0"
+ }
+ },
+ "node_modules/mdast-util-from-markdown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
+ "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-to-string": "^4.0.0",
+ "micromark": "^4.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-decode-string": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0",
+ "unist-util-stringify-position": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-frontmatter": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-0.2.0.tgz",
+ "integrity": "sha512-FHKL4w4S5fdt1KjJCwB0178WJ0evnyyQr5kXTM3wrOVpytD0hrkvd+AOOjU9Td8onOejCkmZ+HQRT3CZ3coHHQ==",
+ "dependencies": {
+ "micromark-extension-frontmatter": "^0.2.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-0.1.2.tgz",
+ "integrity": "sha512-NNkhDx/qYcuOWB7xHUGWZYVXvjPFFd6afg6/e2g+SV4r9q5XUcCbV4Wfa3DLYIiD+xAEZc6K4MGaE/m0KDcPwQ==",
+ "dependencies": {
+ "mdast-util-gfm-autolink-literal": "^0.1.0",
+ "mdast-util-gfm-strikethrough": "^0.2.0",
+ "mdast-util-gfm-table": "^0.1.0",
+ "mdast-util-gfm-task-list-item": "^0.1.0",
+ "mdast-util-to-markdown": "^0.6.1"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-autolink-literal": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-0.1.3.tgz",
+ "integrity": "sha512-GjmLjWrXg1wqMIO9+ZsRik/s7PLwTaeCHVB7vRxUwLntZc8mzmTsLVr6HW1yLokcnhfURsn5zmSVdi3/xWWu1A==",
+ "dependencies": {
+ "ccount": "^1.0.0",
+ "mdast-util-find-and-replace": "^1.1.0",
+ "micromark": "^2.11.3"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-autolink-literal/node_modules/ccount": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.1.0.tgz",
+ "integrity": "sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark": {
+ "version": "2.11.4",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz",
+ "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "debug": "^4.0.0",
+ "parse-entities": "^2.0.0"
+ }
+ },
+ "node_modules/mdast-util-gfm-strikethrough": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-0.2.3.tgz",
+ "integrity": "sha512-5OQLXpt6qdbttcDG/UxYY7Yjj3e8P7X16LzvpX8pIQPYJ/C2Z1qFGMmcw+1PZMUM3Z8wt8NRfYTvCni93mgsgA==",
+ "dependencies": {
+ "mdast-util-to-markdown": "^0.6.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-table": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-0.1.6.tgz",
+ "integrity": "sha512-j4yDxQ66AJSBwGkbpFEp9uG/LS1tZV3P33fN1gkyRB2LoRL+RR3f76m0HPHaby6F4Z5xr9Fv1URmATlRRUIpRQ==",
+ "dependencies": {
+ "markdown-table": "^2.0.0",
+ "mdast-util-to-markdown": "~0.6.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-task-list-item": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-0.1.6.tgz",
+ "integrity": "sha512-/d51FFIfPsSmCIRNp7E6pozM9z1GYPIkSy1urQ8s/o4TC22BZ7DqfHFWiqBD23bc7J3vV1Fc9O4QIHBlfuit8A==",
+ "dependencies": {
+ "mdast-util-to-markdown": "~0.6.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-hast": {
+ "version": "13.2.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz",
+ "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "@ungap/structured-clone": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "trim-lines": "^3.0.0",
+ "unist-util-position": "^5.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-markdown": {
+ "version": "0.6.5",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-0.6.5.tgz",
+ "integrity": "sha512-XeV9sDE7ZlOQvs45C9UKMtfTcctcaj/pGwH8YLbMHoMOXNNCn2LsqVQOqrF1+/NU8lKDAqozme9SCXWyo9oAcQ==",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "longest-streak": "^2.0.0",
+ "mdast-util-to-string": "^2.0.0",
+ "parse-entities": "^2.0.0",
+ "repeat-string": "^1.0.0",
+ "zwitch": "^1.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-markdown/node_modules/@types/unist": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
+ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="
+ },
+ "node_modules/mdast-util-to-markdown/node_modules/mdast-util-to-string": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz",
+ "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-markdown/node_modules/zwitch": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz",
+ "integrity": "sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/mdast-util-to-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
+ "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
+ "dependencies": {
+ "@types/mdast": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "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==",
+ "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==",
+ "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==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/micromark": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.1.tgz",
+ "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "@types/debug": "^4.0.0",
+ "debug": "^4.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-core-commonmark": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-combine-extensions": "^2.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-subtokenize": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-core-commonmark": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz",
+ "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-factory-destination": "^2.0.0",
+ "micromark-factory-label": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-factory-title": "^2.0.0",
+ "micromark-factory-whitespace": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-html-tag-name": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-subtokenize": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-extension-footnote": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/micromark-extension-footnote/-/micromark-extension-footnote-0.3.2.tgz",
+ "integrity": "sha512-gr/BeIxbIWQoUm02cIfK7mdMZ/fbroRpLsck4kvFtjbzP4yi+OPVbnukTc/zy0i7spC2xYE/dbX1Sur8BEDJsQ==",
+ "dependencies": {
+ "micromark": "~2.11.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-footnote/node_modules/micromark": {
+ "version": "2.11.4",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz",
+ "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "debug": "^4.0.0",
+ "parse-entities": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-extension-frontmatter": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-0.2.2.tgz",
+ "integrity": "sha512-q6nPLFCMTLtfsctAuS0Xh4vaolxSFUWUWR6PZSrXXiRy+SANGllpcqdXFv2z07l0Xz/6Hl40hK0ffNCJPH2n1A==",
+ "dependencies": {
+ "fault": "^1.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-0.3.3.tgz",
+ "integrity": "sha512-oVN4zv5/tAIA+l3GbMi7lWeYpJ14oQyJ3uEim20ktYFAcfX1x3LNlFGGlmrZHt7u9YlKExmyJdDGaTt6cMSR/A==",
+ "dependencies": {
+ "micromark": "~2.11.0",
+ "micromark-extension-gfm-autolink-literal": "~0.5.0",
+ "micromark-extension-gfm-strikethrough": "~0.6.5",
+ "micromark-extension-gfm-table": "~0.4.0",
+ "micromark-extension-gfm-tagfilter": "~0.3.0",
+ "micromark-extension-gfm-task-list-item": "~0.3.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-autolink-literal": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-0.5.7.tgz",
+ "integrity": "sha512-ePiDGH0/lhcngCe8FtH4ARFoxKTUelMp4L7Gg2pujYD5CSMb9PbblnyL+AAMud/SNMyusbS2XDSiPIRcQoNFAw==",
+ "dependencies": {
+ "micromark": "~2.11.3"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark": {
+ "version": "2.11.4",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz",
+ "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "debug": "^4.0.0",
+ "parse-entities": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-extension-gfm-strikethrough": {
+ "version": "0.6.5",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-0.6.5.tgz",
+ "integrity": "sha512-PpOKlgokpQRwUesRwWEp+fHjGGkZEejj83k9gU5iXCbDG+XBA92BqnRKYJdfqfkrRcZRgGuPuXb7DaK/DmxOhw==",
+ "dependencies": {
+ "micromark": "~2.11.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-strikethrough/node_modules/micromark": {
+ "version": "2.11.4",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz",
+ "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "debug": "^4.0.0",
+ "parse-entities": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-extension-gfm-table": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-0.4.3.tgz",
+ "integrity": "sha512-hVGvESPq0fk6ALWtomcwmgLvH8ZSVpcPjzi0AjPclB9FsVRgMtGZkUcpE0zgjOCFAznKepF4z3hX8z6e3HODdA==",
+ "dependencies": {
+ "micromark": "~2.11.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-table/node_modules/micromark": {
+ "version": "2.11.4",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz",
+ "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "debug": "^4.0.0",
+ "parse-entities": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-extension-gfm-tagfilter": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-0.3.0.tgz",
+ "integrity": "sha512-9GU0xBatryXifL//FJH+tAZ6i240xQuFrSL7mYi8f4oZSbc+NvXjkrHemeYP0+L4ZUT+Ptz3b95zhUZnMtoi/Q==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-task-list-item": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-0.3.3.tgz",
+ "integrity": "sha512-0zvM5iSLKrc/NQl84pZSjGo66aTGd57C1idmlWmE87lkMcXrTxg1uXa/nXomxJytoje9trP0NDLvw4bZ/Z/XCQ==",
+ "dependencies": {
+ "micromark": "~2.11.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark": {
+ "version": "2.11.4",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz",
+ "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "debug": "^4.0.0",
+ "parse-entities": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-extension-gfm/node_modules/micromark": {
+ "version": "2.11.4",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz",
+ "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "debug": "^4.0.0",
+ "parse-entities": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-destination": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
+ "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-label": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz",
+ "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-space": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz",
+ "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-title": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz",
+ "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-whitespace": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz",
+ "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-character": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
+ "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-chunked": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz",
+ "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-classify-character": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz",
+ "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-combine-extensions": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz",
+ "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-decode-numeric-character-reference": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz",
+ "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-decode-string": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz",
+ "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-encode": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
+ "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ]
+ },
+ "node_modules/micromark-util-html-tag-name": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz",
+ "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ]
+ },
+ "node_modules/micromark-util-normalize-identifier": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz",
+ "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-resolve-all": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz",
+ "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-sanitize-uri": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
+ "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-subtokenize": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.2.tgz",
+ "integrity": "sha512-xKxhkB62vwHUuuxHe9Xqty3UaAsizV2YKq5OV344u3hFBbf8zIYrhYOWhAQb94MtMPkjTOzzjJ/hid9/dR5vFA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-symbol": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
+ "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ]
+ },
+ "node_modules/micromark-util-types": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz",
+ "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ]
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "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==",
+ "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==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "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=="
+ },
+ "node_modules/mustache": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
+ "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==",
+ "peer": true,
+ "bin": {
+ "mustache": "bin/mustache"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
+ "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/neotraverse": {
+ "version": "0.6.18",
+ "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz",
+ "integrity": "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==",
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "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==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "github",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "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=="
+ },
+ "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==",
+ "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/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==",
+ "dependencies": {
+ "boolbase": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/nth-check?sponsor=1"
+ }
+ },
+ "node_modules/nwsapi": {
+ "version": "2.2.16",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.16.tgz",
+ "integrity": "sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ=="
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.3",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz",
+ "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==",
+ "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==",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/openai": {
+ "version": "4.76.1",
+ "resolved": "https://registry.npmjs.org/openai/-/openai-4.76.1.tgz",
+ "integrity": "sha512-ci63/WFEMd6QjjEVeH0pV7hnFS6CCqhgJydSti4Aak/8uo2SpgzKjteUDaY+OkwziVj11mi6j+0mRUIiGKUzWw==",
+ "dependencies": {
+ "@types/node": "^18.11.18",
+ "@types/node-fetch": "^2.6.4",
+ "abort-controller": "^3.0.0",
+ "agentkeepalive": "^4.2.1",
+ "form-data-encoder": "1.7.2",
+ "formdata-node": "^4.3.2",
+ "node-fetch": "^2.6.7"
+ },
+ "bin": {
+ "openai": "bin/cli"
+ },
+ "peerDependencies": {
+ "zod": "^3.23.8"
+ },
+ "peerDependenciesMeta": {
+ "zod": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/openai/node_modules/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/openai/node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
+ },
+ "node_modules/openai/node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
+ },
+ "node_modules/openai/node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
+ "node_modules/openapi-types": {
+ "version": "12.1.3",
+ "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz",
+ "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="
+ },
+ "node_modules/option": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/option/-/option-0.2.4.tgz",
+ "integrity": "sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A=="
+ },
+ "node_modules/p-finally": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+ "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/p-queue": {
+ "version": "6.6.2",
+ "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz",
+ "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==",
+ "dependencies": {
+ "eventemitter3": "^4.0.4",
+ "p-timeout": "^3.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-retry": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
+ "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==",
+ "dependencies": {
+ "@types/retry": "0.12.0",
+ "retry": "^0.13.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-timeout": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz",
+ "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==",
+ "dependencies": {
+ "p-finally": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
+ },
+ "node_modules/parse-entities": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz",
+ "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==",
+ "dependencies": {
+ "character-entities": "^1.0.0",
+ "character-entities-legacy": "^1.0.0",
+ "character-reference-invalid": "^1.0.0",
+ "is-alphanumerical": "^1.0.0",
+ "is-decimal": "^1.0.0",
+ "is-hexadecimal": "^1.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/parse-entities/node_modules/character-entities": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz",
+ "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/parse-srcset": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz",
+ "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q=="
+ },
+ "node_modules/parse5": {
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
+ "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
+ "dependencies": {
+ "entities": "^4.5.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?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==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.10",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
+ "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
+ },
+ "node_modules/pdf-parse": {
+ "version": "1.1.3",
+ "resolved": "git+ssh://git@github.com/iamh2o/pdf-parse.git#d7a41d5aaed1503bee2d7ea50bf89588d3b2d2cf",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^3.1.0",
+ "node-ensure": "^0.0.0"
+ },
+ "engines": {
+ "node": ">=6.8.1"
+ }
+ },
+ "node_modules/pdf-parse/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/pdf2json": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/pdf2json/-/pdf2json-3.1.4.tgz",
+ "integrity": "sha512-rS+VapXpXZr+5lUpHmRh3ugXdFXp24p1RyG24yP1DMpqP4t0mrYNGpLtpSbWD42PnQ59GIXofxF+yWb7M+3THg==",
+ "bundleDependencies": [
+ "@xmldom/xmldom"
+ ],
+ "dependencies": {
+ "@xmldom/xmldom": "^0.8.10"
+ },
+ "bin": {
+ "pdf2json": "bin/pdf2json.js"
+ },
+ "engines": {
+ "node": ">=18.12.1",
+ "npm": ">=8.19.2"
+ }
+ },
+ "node_modules/pdf2json/node_modules/@xmldom/xmldom": {
+ "version": "0.8.10",
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/peek-readable": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.3.1.tgz",
+ "integrity": "sha512-GVlENSDW6KHaXcd9zkZltB7tCLosKB/4Hg0fqBJkAoBgYG2Tn1xtMgXtSUuMU9AK/gCm/tTdT8mgAeF4YNeeqw==",
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Borewit"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
+ },
+ "node_modules/postcss": {
+ "version": "8.4.49",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
+ "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "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=="
+ },
+ "node_modules/property-information": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz",
+ "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.13.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
+ "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
+ "dependencies": {
+ "side-channel": "^1.0.6"
+ },
+ "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==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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==",
+ "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/readable-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=="
+ },
+ "node_modules/readable-web-to-node-stream": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz",
+ "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==",
+ "dependencies": {
+ "readable-stream": "^3.6.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Borewit"
+ }
+ },
+ "node_modules/readable-web-to-node-stream/node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/remark-footnotes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/remark-footnotes/-/remark-footnotes-3.0.0.tgz",
+ "integrity": "sha512-ZssAvH9FjGYlJ/PBVKdSmfyPc3Cz4rTWgZLI4iE/SX8Nt5l3o3oEjv3wwG5VD7xOjktzdwp5coac+kJV9l4jgg==",
+ "dependencies": {
+ "mdast-util-footnote": "^0.1.0",
+ "micromark-extension-footnote": "^0.3.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-frontmatter": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-3.0.0.tgz",
+ "integrity": "sha512-mSuDd3svCHs+2PyO29h7iijIZx4plX0fheacJcAoYAASfgzgVIcXGYSq9GFyYocFLftQs8IOmmkgtOovs6d4oA==",
+ "dependencies": {
+ "mdast-util-frontmatter": "^0.2.0",
+ "micromark-extension-frontmatter": "^0.2.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-gfm": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-1.0.0.tgz",
+ "integrity": "sha512-KfexHJCiqvrdBZVbQ6RopMZGwaXz6wFJEfByIuEwGf0arvITHjiKKZ1dpXujjH9KZdm1//XJQwgfnJ3lmXaDPA==",
+ "dependencies": {
+ "mdast-util-gfm": "^0.1.0",
+ "micromark-extension-gfm": "^0.3.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-parse": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-9.0.0.tgz",
+ "integrity": "sha512-geKatMwSzEXKHuzBNU1z676sGcDcFoChMK38TgdHJNAYfFtsfHDQG7MoJAjs6sgYMqyLduCYWDIWZIxiPeafEw==",
+ "dependencies": {
+ "mdast-util-from-markdown": "^0.8.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-parse/node_modules/@types/mdast": {
+ "version": "3.0.15",
+ "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz",
+ "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==",
+ "dependencies": {
+ "@types/unist": "^2"
+ }
+ },
+ "node_modules/remark-parse/node_modules/@types/unist": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
+ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="
+ },
+ "node_modules/remark-parse/node_modules/mdast-util-from-markdown": {
+ "version": "0.8.5",
+ "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz",
+ "integrity": "sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==",
+ "dependencies": {
+ "@types/mdast": "^3.0.0",
+ "mdast-util-to-string": "^2.0.0",
+ "micromark": "~2.11.0",
+ "parse-entities": "^2.0.0",
+ "unist-util-stringify-position": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-parse/node_modules/mdast-util-to-string": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-2.0.0.tgz",
+ "integrity": "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-parse/node_modules/micromark": {
+ "version": "2.11.4",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-2.11.4.tgz",
+ "integrity": "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "dependencies": {
+ "debug": "^4.0.0",
+ "parse-entities": "^2.0.0"
+ }
+ },
+ "node_modules/remark-parse/node_modules/unist-util-stringify-position": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz",
+ "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==",
+ "dependencies": {
+ "@types/unist": "^2.0.2"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/repeat-string": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
+ "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/retry": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
+ "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/rrweb-cssom": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz",
+ "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg=="
+ },
+ "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"
+ }
+ ]
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "node_modules/sanitize-filename": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz",
+ "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==",
+ "dependencies": {
+ "truncate-utf8-bytes": "^1.0.0"
+ }
+ },
+ "node_modules/sanitize-html": {
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.13.1.tgz",
+ "integrity": "sha512-ZXtKq89oue4RP7abL9wp/9URJcqQNABB5GGJ2acW1sdO8JTVl92f4ygD7Yc9Ze09VAZhnt2zegeU0tbNsdcLYg==",
+ "dependencies": {
+ "deepmerge": "^4.2.2",
+ "escape-string-regexp": "^4.0.0",
+ "htmlparser2": "^8.0.0",
+ "is-plain-object": "^5.0.0",
+ "parse-srcset": "^1.0.2",
+ "postcss": "^8.3.11"
+ }
+ },
+ "node_modules/sanitize-html/node_modules/htmlparser2": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
+ "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
+ "funding": [
+ "https://github.com/fb55/htmlparser2?sponsor=1",
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.3",
+ "domutils": "^3.0.1",
+ "entities": "^4.4.0"
+ }
+ },
+ "node_modules/saxes": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+ "dependencies": {
+ "xmlchars": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=v12.22.7"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/send": {
+ "version": "0.19.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
+ "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/send/node_modules/debug/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "node_modules/send/node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "1.16.2",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
+ "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.19.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/set-function-length": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+ "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/setimmediate": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+ "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+ },
+ "node_modules/side-channel": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
+ "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.4",
+ "object-inspect": "^1.13.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/space-separated-tokens": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
+ "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
+ },
+ "node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/string_decoder/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=="
+ },
+ "node_modules/stringify-entities": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
+ "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
+ "dependencies": {
+ "character-entities-html4": "^2.0.0",
+ "character-entities-legacy": "^3.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/stringify-entities/node_modules/character-entities-legacy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
+ "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/strtok3": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-7.1.1.tgz",
+ "integrity": "sha512-mKX8HA/cdBqMKUr0MMZAFssCkIGoZeSCMXgnt79yKxNFguMLVFgRe6wB+fsL0NmoHDbeyZXczy7vEPSoo3rkzg==",
+ "dependencies": {
+ "@tokenizer/token": "^0.3.0",
+ "peek-readable": "^5.1.3"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Borewit"
+ }
+ },
+ "node_modules/symbol-tree": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="
+ },
+ "node_modules/tldts": {
+ "version": "6.1.66",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.66.tgz",
+ "integrity": "sha512-l3ciXsYFel/jSRfESbyKYud1nOw7WfhrBEF9I3UiarYk/qEaOOwu3qXNECHw4fHGHGTEOuhf/VdKgoDX5M/dhQ==",
+ "dependencies": {
+ "tldts-core": "^6.1.66"
+ },
+ "bin": {
+ "tldts": "bin/cli.js"
+ }
+ },
+ "node_modules/tldts-core": {
+ "version": "6.1.66",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.66.tgz",
+ "integrity": "sha512-s07jJruSwndD2X8bVjwioPfqpIc1pDTzszPe9pL1Skbh4bjytL85KNQ3tolqLbCvpQHawIsGfFi9dgerWjqW4g=="
+ },
+ "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==",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/token-types": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/token-types/-/token-types-5.0.1.tgz",
+ "integrity": "sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==",
+ "dependencies": {
+ "@tokenizer/token": "^0.3.0",
+ "ieee754": "^1.2.1"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Borewit"
+ }
+ },
+ "node_modules/tough-cookie": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz",
+ "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==",
+ "dependencies": {
+ "tldts": "^6.1.32"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz",
+ "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/trim-lines": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
+ "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/trough": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz",
+ "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/truncate-utf8-bytes": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
+ "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==",
+ "dependencies": {
+ "utf8-byte-length": "^1.0.1"
+ }
+ },
+ "node_modules/turndown": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.0.tgz",
+ "integrity": "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A==",
+ "dependencies": {
+ "@mixmark-io/domino": "^2.2.0"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/uhyphen": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/uhyphen/-/uhyphen-0.2.0.tgz",
+ "integrity": "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA=="
+ },
+ "node_modules/underscore": {
+ "version": "1.13.7",
+ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz",
+ "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g=="
+ },
+ "node_modules/undici-types": {
+ "version": "5.26.5",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
+ },
+ "node_modules/unified": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/unified/-/unified-9.2.2.tgz",
+ "integrity": "sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==",
+ "dependencies": {
+ "bail": "^1.0.0",
+ "extend": "^3.0.0",
+ "is-buffer": "^2.0.0",
+ "is-plain-obj": "^2.0.0",
+ "trough": "^1.0.0",
+ "vfile": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unified/node_modules/@types/unist": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
+ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="
+ },
+ "node_modules/unified/node_modules/unist-util-stringify-position": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz",
+ "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==",
+ "dependencies": {
+ "@types/unist": "^2.0.2"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unified/node_modules/vfile": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.2.1.tgz",
+ "integrity": "sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "is-buffer": "^2.0.0",
+ "unist-util-stringify-position": "^2.0.0",
+ "vfile-message": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unified/node_modules/vfile-message": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz",
+ "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "unist-util-stringify-position": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-is": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz",
+ "integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-position": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
+ "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-stringify-position": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
+ "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz",
+ "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0",
+ "unist-util-visit-parents": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit-parents": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz",
+ "integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "unist-util-is": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit-parents/node_modules/@types/unist": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
+ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="
+ },
+ "node_modules/unist-util-visit/node_modules/unist-util-is": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz",
+ "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit/node_modules/unist-util-visit-parents": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz",
+ "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/universalify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
+ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/utf8-byte-length": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
+ "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA=="
+ },
+ "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=="
+ },
+ "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==",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/uuid": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
+ "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
+ "funding": [
+ "https://github.com/sponsors/broofa",
+ "https://github.com/sponsors/ctavan"
+ ],
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/vfile": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
+ "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vfile-message": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz",
+ "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-stringify-position": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/w3c-xmlserializer": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
+ "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
+ "dependencies": {
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "iconv-lite": "0.6.3"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-encoding/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==",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "14.1.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.0.tgz",
+ "integrity": "sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==",
+ "dependencies": {
+ "tr46": "^5.0.0",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
+ "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
+ "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/xml-name-validator": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
+ "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/xmlbuilder": {
+ "version": "10.1.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz",
+ "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/xmlchars": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="
+ },
+ "node_modules/yaml": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz",
+ "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/zod": {
+ "version": "3.24.1",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz",
+ "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-to-json-schema": {
+ "version": "3.23.5",
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.23.5.tgz",
+ "integrity": "sha512-5wlSS0bXfF/BrL4jPAbz9da5hDlDptdEppYfe+x4eIJ7jioqKG9uUxOwPzqof09u/XeVdrgFu29lZi+8XNDJtA==",
+ "peerDependencies": {
+ "zod": "^3.23.3"
+ }
+ },
+ "node_modules/zwitch": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
+ "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ }
+ }
+}
diff --git a/apps/converter/package.json b/apps/converter/package.json
new file mode 100644
index 000000000..4f3f2dddb
--- /dev/null
+++ b/apps/converter/package.json
@@ -0,0 +1,38 @@
+{
+ "name": "@custom/converter",
+ "version": "1.0.0",
+ "description": "A custom converter from multiple formats to markdown.",
+ "main": "index.js",
+ "type": "module",
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "scripts": {
+ "start": "node index.js",
+ "dev": "node --watch index.js"
+ },
+ "dependencies": {
+ "@extractus/article-extractor": "^8.0.16",
+ "@opendocsg/pdf2md": "^0.2.1",
+ "@textlint/markdown-to-ast": "^14.3.0",
+ "axios": "^1.7.9",
+ "dotenv": "^16.4.7",
+ "express": "^4.21.1",
+ "fs-extra": "^11.2.0",
+ "hast-util-to-html": "^9.0.3",
+ "image-type": "^5.2.0",
+ "install": "^0.13.0",
+ "jsdom": "^25.0.1",
+ "mammoth": "^1.8.0",
+ "markdownlint": "^0.37.1",
+ "mdast-util-from-markdown": "^2.0.2",
+ "mdast-util-to-hast": "^13.2.0",
+ "node-fetch": "^3.3.2",
+ "openai": "^4.77.0",
+ "pdf-parse": "github:iamh2o/pdf-parse#1.1.3",
+ "pdf2pic": "^3.1.3",
+ "sanitize-filename": "^1.6.3",
+ "turndown": "^7.2.0",
+ "unist-util-visit": "^5.0.0"
+ }
+}
diff --git a/apps/converter/pdfToMarkdown.js b/apps/converter/pdfToMarkdown.js
new file mode 100644
index 000000000..8508601d3
--- /dev/null
+++ b/apps/converter/pdfToMarkdown.js
@@ -0,0 +1,53 @@
+import pdf2md from '@opendocsg/pdf2md';
+import fs from 'fs-extra';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+import { generateFolderName, validateAndFixMarkdown } from './utils/utils.js';
+
+// @todo Fix this to work locally and live
+const isLagoon = !!process.env.LAGOON;
+const __dirname = isLagoon
+ ? '/app/web/sites/default/files/converted'
+ : '/tmp/converted';
+
+export async function pdfToMarkdown(pdfPath) {
+ try {
+ // Validate input file exists and is a PDF
+ if (!fs.existsSync(pdfPath) || !pdfPath.toLowerCase().endsWith('.pdf')) {
+ throw new Error('Invalid PDF file path');
+ }
+
+ // Generate output folder name
+ const folderName = generateFolderName(pdfPath);
+ const outputDir = path.join(__dirname, folderName);
+ const imagesDir = path.join(outputDir, 'images');
+
+ // Create output directories
+ if (!fs.existsSync(outputDir)) {
+ fs.mkdirSync(outputDir);
+ }
+ if (!fs.existsSync(imagesDir)) {
+ fs.mkdirSync(imagesDir);
+ }
+
+ const pdfBuffer = fs.readFileSync(pdfPath);
+ const fullMarkdown = await pdf2md(pdfBuffer);
+
+ // const fullMarkdown = await convertToMarkdown(markdown);
+ const { markdown: fixedMarkdown, warnings } =
+ validateAndFixMarkdown(fullMarkdown);
+
+ // Save markdown file
+ const mdPath = path.join(outputDir, 'content.md');
+ await fs.writeFile(mdPath, fixedMarkdown);
+
+ return {
+ markdownPath: mdPath,
+ warnings: warnings, // You could add warnings for failed image downloads etc.
+ outputDir,
+ };
+ } catch (error) {
+ throw new Error(`PDF conversion failed: ${error.message}`);
+ }
+}
diff --git a/apps/converter/utils/utils.js b/apps/converter/utils/utils.js
new file mode 100644
index 000000000..e9d2dac82
--- /dev/null
+++ b/apps/converter/utils/utils.js
@@ -0,0 +1,87 @@
+import crypto from 'crypto';
+import fs from 'fs-extra';
+import TurndownService from 'turndown';
+
+export function validateAndFixMarkdown(markdown) {
+ const warnings = [];
+
+ // Regex to match the entire image syntax
+ const imageRegex = /!\[.*?\]\(.*?\)/g;
+
+ markdown = markdown.replace(imageRegex, (match) => {
+ // Parse the components of the Markdown image syntax
+ const altMatch = match.match(/!\[(.*?)\]/); // Match alt text
+ const urlMatch = match.match(/\((.*?)(?=\s|$)/); // Match URL
+ const titleMatch = match.match(/"([^"]*?)"\)$/); // Match title (if it exists)
+
+ let altText = altMatch ? altMatch[1] : '';
+ let url = urlMatch ? urlMatch[1] : '';
+ let title = titleMatch ? titleMatch[1] : null;
+
+ // Fix double quotes in alt text
+ if (altText.includes('"')) {
+ warnings.push(`Double quotes in alt text fixed: "${altText}"`);
+ altText = altText.replace(/"/g, "'");
+ }
+
+ // Fix double quotes in title
+ if (title && title.includes('"')) {
+ warnings.push(`Double quotes in title fixed: "${title}"`);
+ title = title.replace(/"/g, "'");
+ }
+
+ // Rebuild the image syntax
+ return title ? `![${altText}](${url} "${title}")` : `![${altText}](${url})`;
+ });
+
+ // Trim leading and trailing whitespace
+ const trimmedMarkdown = markdown.trim();
+ if (markdown !== trimmedMarkdown) {
+ warnings.push('Leading or trailing whitespace detected and removed.');
+ markdown = trimmedMarkdown;
+ }
+
+ return { markdown, warnings };
+}
+
+export function generateFolderName(path) {
+ const hash = crypto.createHash('md5').update(path).digest('hex');
+ return hash.substring(0, 12);
+}
+
+export function convertToMarkdown(input) {
+ const turndownService = new TurndownService({
+ headingStyle: 'atx',
+ codeBlockStyle: 'fenced',
+ hr: '---',
+ bulletListMarker: '-',
+ strongDelimiter: '**',
+ });
+
+ turndownService.addRule('tables', {
+ filter: 'table',
+ replacement: function (content, node) {
+ const rows = node.querySelectorAll('tr');
+ const headers = Array.from(rows[0]?.querySelectorAll('th,td') || [])
+ .map((cell) => cell.textContent.trim())
+ .join(' | ');
+
+ const separator = headers
+ .split('|')
+ .map(() => '---')
+ .join(' | ');
+
+ const body = Array.from(rows)
+ .slice(1)
+ .map((row) =>
+ Array.from(row.querySelectorAll('td'))
+ .map((cell) => cell.textContent.trim())
+ .join(' | '),
+ )
+ .join('\n');
+
+ return `\n${headers}\n${separator}\n${body}\n\n`;
+ },
+ });
+ return turndownService.turndown(input);
+}
diff --git a/apps/converter/wordToMarkdown.js b/apps/converter/wordToMarkdown.js
new file mode 100644
index 000000000..26d3ab4ea
--- /dev/null
+++ b/apps/converter/wordToMarkdown.js
@@ -0,0 +1,65 @@
+import crypto from 'crypto';
+import fs from 'fs-extra';
+import imageType from 'image-type';
+import mammoth from 'mammoth';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+import { convertToMarkdown, generateFolderName } from './utils/utils.js';
+
+// @todo Fix this to work locally and live
+const isLagoon = !!process.env.LAGOON;
+const __dirname = isLagoon
+ ? '/app/web/sites/default/files/converted'
+ : '/tmp/converted'; // Local only
+
+async function getImageExtension(buffer) {
+ const type = await imageType(buffer);
+ return type ? `.${type.ext}` : '.png';
+}
+
+export async function wordToMarkdown(filePath) {
+ if (!fs.existsSync(filePath)) {
+ throw new Error('File does not exist: ' + filePath);
+ }
+
+ const folderName = generateFolderName(filePath);
+ const outputDir = path.join(__dirname, folderName);
+ const imagesDir = path.join(outputDir, 'images');
+
+ await fs.ensureDir(outputDir);
+ await fs.ensureDir(imagesDir);
+
+ const options = {
+ convertImage: mammoth.images.imgElement(async (image) => {
+ const imageBuffer = await image.read();
+ const extension = await getImageExtension(imageBuffer);
+ const filename = `image-${crypto.randomBytes(4).toString('hex')}${extension}`;
+ const imagePath = path.join(imagesDir, filename);
+
+ await fs.writeFile(imagePath, imageBuffer);
+
+ return {
+ src: path.join('images', filename),
+ };
+ }),
+ };
+
+ const result = await mammoth.convertToHtml({ path: filePath }, options);
+
+ let markdown = convertToMarkdown(result.value);
+
+ markdown = markdown
+ .replace(/\n\s*\n\s*\n/g, '\n\n')
+ .replace(/!\[\]\(/g, '![image](')
+ .trim();
+
+ const mdPath = path.join(outputDir, 'content.md');
+ await fs.writeFile(mdPath, markdown);
+
+ return {
+ markdownPath: mdPath,
+ warnings: result.messages,
+ outputDir,
+ };
+}
diff --git a/docker-compose.yml b/docker-compose.yml
index 663f65444..f28d7b166 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -121,6 +121,22 @@ services:
lagoon.type: node
lagoon.name: preview
+ convertmd:
+ build:
+ context: .
+ target: convertmd
+ dockerfile: .lagoon/Dockerfile
+ environment:
+ <<: *default-environment
+ LAGOON_LOCALDEV_URL: convertmd-${COMPOSE_PROJECT_NAME:-slbtemplate}.docker.amazee.io
+ networks:
+ - amazeeio-network
+ - default
+ labels:
+ lagoon.type: node-persistent
+ lagoon.persistent.name: nginx # mount the persistent storage of nginx into this container
+ lagoon.persistent: /app/web/sites/default/files/
+
networks:
amazeeio-network:
external: true
diff --git a/packages/drupal/silverback_ai/README.md b/packages/drupal/silverback_ai/README.md
new file mode 100644
index 000000000..be933eb97
--- /dev/null
+++ b/packages/drupal/silverback_ai/README.md
@@ -0,0 +1,30 @@
+## INTRODUCTION
+
+The Silverback AI module is a base module
+
+## REQUIREMENTS
+
+- Media
+- Webform (using some webform elements on reporting)
+
+## INSTALLATION
+
+Install as you would normally install a contributed Drupal module.
+See: for further information.
+
+## CONFIGURATION
+
+- Open AI credentials can be set on: `/admin/config/system/silverback-ai-settings`.
+It is recommended though to add the Open AI Api key as environment variable (`OPEN_AI_API_KEY`).
+
+## USAGE TRACKING
+
+The Silverback AI module tracks OpenAI API token usage for monitoring and cost management purposes:
+
+- All Silverback AI submodules automatically report their token usage through the `TokenUsage` service
+- Usage statistics can be viewed at `/admin/reports/silverback-ai-usage`
+- The report shows:
+ - Total tokens used per module
+ - Cost estimates based on current OpenAI pricing
+ - Usage breakdown by time period
+ - Details of individual API calls
diff --git a/packages/drupal/silverback_ai/config/install/silverback_ai.settings.yml b/packages/drupal/silverback_ai/config/install/silverback_ai.settings.yml
new file mode 100644
index 000000000..ed081fab3
--- /dev/null
+++ b/packages/drupal/silverback_ai/config/install/silverback_ai.settings.yml
@@ -0,0 +1,2 @@
+open_ai_base_uri: 'https://api.openai.com/v1/'
+open_ai_api_key: ''
diff --git a/packages/drupal/silverback_ai/modules/silverback_ai_import/README.md b/packages/drupal/silverback_ai/modules/silverback_ai_import/README.md
new file mode 100644
index 000000000..7075d9389
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_ai_import/README.md
@@ -0,0 +1,101 @@
+### Silverback AI Import Module
+
+---
+
+## Introduction
+
+The **Silverback AI Import** module enables advanced content importation using AI-driven parsing and conversion. It provides tools to process content from sources such as Microsoft Word documents and HTML pages, transforming them into Gutenberg-compatible blocks for Drupal sites.
+
+This module is ideal for websites requiring seamless integration of structured content into their CMS, leveraging AI for efficiency and accuracy.
+
+---
+
+## Features
+
+- **Content Import Support**: Import content from:
+
+ - Microsoft Word (`.docx`) files.
+
+ - HTML URLs.
+
+- **AI-driven Parsing**: Extract and process content using the OpenAI model.
+
+- **Custom Gutenberg Blocks**: Automatically convert content into pre-defined block types such as headers, lists, paragraphs, tables, and images.
+
+- **Batch Processing**: Import large content sets efficiently using batch operations.
+
+---
+
+## Requirements
+
+The module depends on:
+
+- **Drupal Core**: Versions 10 or 11.
+
+- **Silverback AI** module.
+
+- **Media** module for handling images.
+
+Ensure these dependencies are installed before enabling the module.
+
+---
+
+## Configuration
+
+1\. Navigate to the **AI Import Settings** page:
+
+ `Admin > Configuration > System > Silverback Import AI Settings`
+
+2\. Set the following:
+
+ - **OpenAI Model**: Select or configure the OpenAI model.
+
+ - **Converter Service URL**: Provide the URL for the external service used to parse and process files.
+
+3\. Access the import functionality when creating or editing content.
+
+---
+
+## Usage
+
+1\. **Importing Content**:
+
+ - Add a content node and locate the "Import Content" section.
+
+ - Choose a source:
+
+ - **Microsoft Word File**: Upload a `.docx` file.
+
+ - **HTML Page**: Enter a valid URL.
+
+ - Process the import to convert content into structured blocks.
+
+2\. **Batch Import**:
+
+ - Large sets of data can be processed using the batch handler available in the module. [TBD]
+
+---
+
+## Custom Plugins
+
+The module supports extensible AI plugins for various content types:
+
+- **Default**: Generic HTML content.
+
+- **Header**: Markdown headers.
+
+- **Image**: Image embedding and metadata.
+
+- **List**: Ordered and unordered lists.
+
+- **Paragraph**: Text paragraphs.
+
+- **Table**: Tabular data.
+
+Developers can add custom plugins by extending the `AiImportPluginManagerInterface`.
+
+---
+
+## Maintainers
+
+This module is maintained by the **Silverback** team.
diff --git a/packages/drupal/silverback_ai/modules/silverback_ai_import/config/schema/silverback_ai_import.schema.yml b/packages/drupal/silverback_ai/modules/silverback_ai_import/config/schema/silverback_ai_import.schema.yml
new file mode 100644
index 000000000..b904f1b8a
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_ai_import/config/schema/silverback_ai_import.schema.yml
@@ -0,0 +1,8 @@
+# Schema for the configuration files of the silverback_ai_import module.
+silverback_ai_import.settings:
+ type: config_object
+ label: 'silverback_ai_import settings'
+ mapping:
+ example:
+ type: string
+ label: 'Example'
diff --git a/packages/drupal/silverback_ai/modules/silverback_ai_import/silverback_ai_import.info.yml b/packages/drupal/silverback_ai/modules/silverback_ai_import/silverback_ai_import.info.yml
new file mode 100644
index 000000000..9c154fcf0
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_ai_import/silverback_ai_import.info.yml
@@ -0,0 +1,8 @@
+name: 'Silverback Import AI'
+type: module
+description: 'Silverback AI content import and more'
+package: Silverback
+core_version_requirement: ^10 || ^11
+dependencies:
+ - silverback_ai:silverback_ai
+ - media:media
diff --git a/packages/drupal/silverback_ai/modules/silverback_ai_import/silverback_ai_import.install b/packages/drupal/silverback_ai/modules/silverback_ai_import/silverback_ai_import.install
new file mode 100644
index 000000000..c0cef5037
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_ai_import/silverback_ai_import.install
@@ -0,0 +1,86 @@
+schema();
+ if ($db_schema->tableExists('silverback_ai_import')) {
+ $db_schema->dropTable('silverback_ai_import');
+ }
+
+ $schema['silverback_ai_import'] = [
+ 'description' => 'Import log for the Silverback AI import module.',
+ 'fields' => [
+ 'id' => [
+ 'type' => 'serial',
+ 'not null' => TRUE,
+ 'description' => 'Primary Key.',
+ ],
+ 'uid' => [
+ 'description' => 'Foreign key to {users}.uid; uniquely identifies a Drupal user executed the ai fetch action.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ],
+ 'timestamp' => [
+ 'description' => 'Date/time if the import, as Unix timestamp.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ],
+ 'target_entity_type_id' => [
+ 'type' => 'varchar_ascii',
+ 'length' => EntityTypeInterface::ID_MAX_LENGTH,
+ 'not null' => FALSE,
+ 'default' => '',
+ 'description' => 'The ID of the associated entity type.',
+ ],
+ 'target_entity_id' => [
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => FALSE,
+ 'description' => 'The ID of the associated entity.',
+ ],
+ 'target_entity_revision_id' => [
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => FALSE,
+ 'description' => 'The revision ID of the associated entity.',
+ ],
+ 'source' => [
+ 'type' => 'text',
+ 'not null' => TRUE,
+ 'size' => 'normal',
+ 'description' => 'The source of the import.',
+ ],
+ 'output_folder' => [
+ 'type' => 'text',
+ 'not null' => TRUE,
+ 'size' => 'small',
+ 'description' => 'The name of the folder exported.',
+ ],
+ 'data' => [
+ 'type' => 'blob',
+ 'not null' => TRUE,
+ 'size' => 'big',
+ 'description' => 'The import response *usually a text series of gutenberg formatted blocks.',
+ ],
+ 'primary key' => ['id'],
+ 'indexes' => [
+ 'uid' => ['uid'],
+ 'timestamp' => ['timestamp'],
+ ],
+ ],
+ ];
+
+ return $schema;
+}
diff --git a/packages/drupal/silverback_ai/modules/silverback_ai_import/silverback_ai_import.links.action.yml b/packages/drupal/silverback_ai/modules/silverback_ai_import/silverback_ai_import.links.action.yml
new file mode 100644
index 000000000..3d9bf7ed2
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_ai_import/silverback_ai_import.links.action.yml
@@ -0,0 +1,5 @@
+silverback_ai_ipmort.add_page:
+ route_name: silverback_ai_import.page_drop
+ title: 'Page Drop'
+ appears_on:
+ - system.admin_content
diff --git a/packages/drupal/silverback_ai/modules/silverback_ai_import/silverback_ai_import.links.menu.yml b/packages/drupal/silverback_ai/modules/silverback_ai_import/silverback_ai_import.links.menu.yml
new file mode 100644
index 000000000..440c29f20
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_ai_import/silverback_ai_import.links.menu.yml
@@ -0,0 +1,6 @@
+silverback_ai_import.import_ai_settings:
+ title: Import AI settings
+ description: Settings for content import (AI)
+ parent: silverback_ai.admin_config_ai
+ route_name: silverback_ai_import.import_ai_settings
+ weight: 11
diff --git a/packages/drupal/silverback_ai/modules/silverback_ai_import/silverback_ai_import.module b/packages/drupal/silverback_ai/modules/silverback_ai_import/silverback_ai_import.module
new file mode 100644
index 000000000..1483185a9
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_ai_import/silverback_ai_import.module
@@ -0,0 +1,186 @@
+ 'details',
+ '#title' => t('Import content'),
+ '#open' => TRUE,
+ '#weight' => 99,
+ ];
+ $form['import']['import_type'] = [
+ '#type' => 'radios',
+ '#title' => t('Import content from:'),
+ '#options' => [
+ 'none' => t('Do not import any content'),
+ 'docx' => t('Microsoft Word file'),
+ 'pdf' => t("PDF file"),
+ 'url' => t("Remote HTML page"),
+ ],
+ '#default_value' => 'none',
+ ];
+
+ $form['import']['container_docx'] = [
+ '#type' => 'container',
+ '#states' => [
+ 'visible' => [
+ 'input[name="import_type"]' => ['value' => 'docx'],
+ ],
+ ],
+ ];
+
+ $form['import']['container_docx']['file'] = [
+ '#title' => t('Drag and drop a Microsoft Word file'),
+ '#type' => 'dropzonejs',
+ '#required' => TRUE,
+ '#dropzone_description' => 'Drag and drop a file here',
+ '#max_filesize' => '20M',
+ '#max_files' => 1,
+ '#extensions' => 'doc docx',
+ '#upload_location' => 'public://converted/',
+ '#states' => [
+ 'required' => [
+ 'input[name="import_type"]' => ['value' => 'docx'],
+ ],
+ ],
+ ];
+
+ $form['import']['container_url'] = [
+ '#type' => 'container',
+ '#states' => [
+ 'visible' => [
+ 'input[name="import_type"]' => ['value' => 'url'],
+ ],
+ ],
+ ];
+
+ $form['import']['container_url']['url_value'] = [
+ '#type' => 'url',
+ '#title' => t('URL'),
+ '#maxlength' => 2048,
+ '#size' => 128,
+ '#states' => [
+ 'required' => [
+ 'input[name="import_type"]' => ['value' => 'url'],
+ ],
+ ],
+ ];
+
+ $form['import']['container_pdf'] = [
+ '#type' => 'container',
+ '#states' => [
+ 'visible' => [
+ 'input[name="import_type"]' => ['value' => 'pdf'],
+ ],
+ ],
+ ];
+
+ $form['import']['container_pdf']['pdf_file'] = [
+ '#title' => t('Drag and drop a PDF file'),
+ '#type' => 'dropzonejs',
+ '#required' => TRUE,
+ '#dropzone_description' => 'Drag and drop a file here',
+ '#max_filesize' => '24M',
+ '#max_files' => 1,
+ '#extensions' => 'pdf',
+ '#upload_location' => 'public://converted/',
+ '#states' => [
+ 'required' => [
+ 'input[name="import_type"]' => ['value' => 'pdf'],
+ ],
+ ],
+ ];
+
+ $form['actions']['submit']['#submit'][] = '_silverback_ai_import_form_submit';
+ $form['#validate'][] = '_silverback_ai_import_form_submit_validate';
+ // Better to have this unpublished originally, and then
+ // we will display a message to the user (esp. if there is AI content)
+ $form['moderation_state']['#access'] = FALSE;
+ $form['actions']['submit']['#value'] = t('Create');
+}
+
+/**
+ * @param array $form
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ *
+ * @return void
+ */
+function _silverback_ai_import_form_submit_validate(array $form, FormStateInterface $form_state) {
+ // @todo
+ $type = $form_state->getValue('import_type');
+ if ($type == ContentImportAiService::PDF) {
+ $file = $form_state->getValue('pdf_file');
+ if (empty($file['uploaded_files'])) {
+ $form_state->setErrorByName('pdf_file', t('PDF file is required.'));
+ }
+ }
+
+ if ($type == ContentImportAiService::DOCX) {
+ $file = $form_state->getValue('file');
+ if (empty($file['uploaded_files'])) {
+ $form_state->setErrorByName('file', t('DOCX file is required.'));
+ }
+ }
+}
+
+/**
+ * @param array $form
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ *
+ * @return void
+ */
+function _silverback_ai_import_form_submit(array $form, FormStateInterface $form_state) {
+
+ /** @var \Drupal\Core\Entity\EntityInterface $entity */
+ $entity = $form_state->getFormObject()->getEntity();
+ $url_value = $form_state->getValue('url_value');
+
+ $service = \Drupal::service('silverback_ai_import.content');
+ $content = \Drupal::service('silverback_ai_import.batch.import');
+ $type = $form_state->getValue('import_type');
+ $contentImportLogger = \Drupal::service('silverback_ai_imoprt.logger');
+
+ switch ($type) {
+ case ContentImportAiService::PDF:
+ $file = $form_state->getValue('pdf_file');
+ break;
+ case ContentImportAiService::URL:
+ $file = $url_value;
+ break;
+ case ContentImportAiService::NONE:
+ break;
+ default:
+ $file = $form_state->getValue('file');
+ }
+
+ if (in_array($type, [ContentImportAiService::DOCX, ContentImportAiService::PDF])) {
+ $file = $service->createFileEntityFromDropzoneData($file);
+ }
+
+ if (!empty($file)) {
+ $ast = $service->getAst($file, $type);
+ $flatten = $service->flattenAst($ast->content);
+ $content->create($flatten, $entity);
+ $contentImportLogger->createEntry($ast, $entity, $file->getFileUri());
+ }
+}
diff --git a/packages/drupal/silverback_ai/modules/silverback_ai_import/silverback_ai_import.routing.yml b/packages/drupal/silverback_ai/modules/silverback_ai_import/silverback_ai_import.routing.yml
new file mode 100644
index 000000000..6e46cb547
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_ai_import/silverback_ai_import.routing.yml
@@ -0,0 +1,15 @@
+silverback_ai_import.import_ai_settings:
+ path: '/admin/config/system/silverback/import-ai-settings'
+ defaults:
+ _title: 'Import AI settings'
+ _form: 'Drupal\silverback_ai_import\Form\ImportAiSettingsForm'
+ requirements:
+ _permission: 'administer site configuration'
+
+silverback_ai_import.page_drop:
+ path: '/admin/config/content/silverback-page-drop'
+ defaults:
+ _title: 'Silverback Page Drop'
+ _form: 'Drupal\silverback_ai_import\Form\PageDropForm'
+ requirements:
+ _permission: 'access content'
diff --git a/packages/drupal/silverback_ai/modules/silverback_ai_import/silverback_ai_import.services.yml b/packages/drupal/silverback_ai/modules/silverback_ai_import/silverback_ai_import.services.yml
new file mode 100644
index 000000000..1a82f6e76
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_ai_import/silverback_ai_import.services.yml
@@ -0,0 +1,17 @@
+services:
+ silverback_ai_import.content:
+ class: Drupal\silverback_ai_import\ContentImportAiService
+ arguments: ['@current_route_match', '@current_user', '@entity_type.manager', '@logger.factory', '@config.factory', '@silverback_ai.openai_http_client', '@plugin.manager.ai.import', '@plugin.manager.ai.post.import', '@silverback_ai.service', '@silverback_ai.token.usage']
+ silverback_ai_import.batch.import:
+ class: 'Drupal\silverback_ai_import\ContentImportBatch'
+ arguments:
+ - '@logger.factory'
+ plugin.manager.ai.import:
+ class: Drupal\silverback_ai_import\AiImportPluginManager
+ arguments: ['@container.namespaces', '@cache.default', '@module_handler']
+ plugin.manager.ai.post.import:
+ class: Drupal\silverback_ai_import\AiPostImportPluginManager
+ arguments: ['@container.namespaces', '@cache.default', '@module_handler']
+ silverback_ai_imoprt.logger:
+ class: Drupal\silverback_ai_import\ContentImportLoggerService
+ arguments: ['@database', '@current_user', '@logger.factory', '@config.factory', '@entity_type.manager']
diff --git a/packages/drupal/silverback_ai/modules/silverback_ai_import/src/AiImportPluginManager.php b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/AiImportPluginManager.php
new file mode 100644
index 000000000..983532c3f
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/AiImportPluginManager.php
@@ -0,0 +1,34 @@
+alterInfo('ai_import_info');
+ $this->setCacheBackend($cache_backend, 'ai_import_info');
+ }
+
+}
diff --git a/packages/drupal/silverback_ai/modules/silverback_ai_import/src/AiImportPluginManagerInterface.php b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/AiImportPluginManagerInterface.php
new file mode 100644
index 000000000..1776daa06
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/AiImportPluginManagerInterface.php
@@ -0,0 +1,62 @@
+alterInfo('ai_post_import_info');
+ $this->setCacheBackend($cache_backend, 'ai_post_import_info');
+ }
+
+}
diff --git a/packages/drupal/silverback_ai/modules/silverback_ai_import/src/AiPostImportPluginManagerInterface.php b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/AiPostImportPluginManagerInterface.php
new file mode 100644
index 000000000..7104c6e29
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/AiPostImportPluginManagerInterface.php
@@ -0,0 +1,18 @@
+getPlugin($chunk);
+ return $plugin->convert($chunk);
+ }
+
+ /**
+ * Get Abstract Syntax Tree (AST) from different source types
+ *
+ * @param FileInterface|string $source The source to parse (can be a FileInterface object or string path/URL)
+ * @param int $type The type of source (self::DOCX by default, self::URL, or self::PDF)
+ * @return mixed The Abstract Syntax Tree representation of the source
+ *
+ * @throws \Exception If the source is invalid or cannot be parsed
+ */
+ public function getAst(FileInterface|string $source, $type = self::DOCX) {
+ $handlers = [
+ self::DOCX => 'getAstFromFilePath',
+ self::URL => 'getAstFromUrl',
+ self::PDF => 'getAstFromPdfFile',
+ ];
+ $handler = $handlers[$type];
+ return $this->$handler($source);
+ }
+
+ /**
+ * Retrieves the AST (Abstract Syntax Tree) from a given file path using an HTTP service.
+ *
+ * @param \Drupal\file\FileInterface $file
+ * The file for which to generate the AST.
+ *
+ * @return mixed
+ * The decoded JSON response containing the AST from the external service, or NULL if the request fails.
+ *
+ * @throws \GuzzleHttp\Exception\RequestException|\GuzzleHttp\Exception\GuzzleException
+ * Thrown when the HTTP request fails, though it is caught and logged within this method.
+ *
+ * @todo Implement configuration handling for service endpoints or client headers.
+ */
+ private function getAstFromFilePath(FileInterface $file) {
+ $uri = $file->getFileUri();
+ $stream_wrapper_manager = \Drupal::service('stream_wrapper_manager')->getViaUri($uri);
+ $file_path = $stream_wrapper_manager->realpath();
+ $parse_service_url = $this->configFactory->get('silverback_ai_import.settings')->get('converter_service_url');
+ // @todo Add DI.
+ $client = \Drupal::httpClient();
+ try {
+ // @todo For now this is working only for docx files.
+ $response = $client->request('GET', "{$parse_service_url}/convert?path={$file_path}", [
+ 'headers' => [
+ 'Accept' => 'application/json',
+ ],
+ ]);
+ $body = $response->getBody()->getContents();
+ $response = json_decode($body);
+ } catch (RequestException $e) {
+ // Handle any errors.
+ $this->loggerFactory->get('silverback_ai_import')->error($e->getMessage());
+ }
+ return $response;
+ }
+
+ /**
+ * Retrieves an Abstract Syntax Tree (AST) from a URL using an external conversion service.
+ *
+ * Currently only supports DOCX files. Sends the URL to a configured HTML conversion
+ * service and returns the AST representation of the document.
+ *
+ * @param string $url
+ * The URL of the document to convert (currently only DOCX files)
+ *
+ * @return object|null
+ * Returns the decoded JSON response containing the AST if successful,
+ * or NULL if the request fails
+ *
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * When the HTTP request fails
+ * When JSON decoding fails
+ *
+ * @todo Extend support for other file types besides DOCX
+ *
+ * @see \GuzzleHttp\ClientInterface::request()
+ */
+ private function getAstFromUrl(string $url) {
+ $parse_service_url = $this->configFactory->get('silverback_ai_import.settings')->get('converter_service_url');
+ $client = \Drupal::httpClient();
+ try {
+ // @todo For now this is working only for docx files.
+ $response = $client->request('GET', "{$parse_service_url}/html-convert?path={$url}", [
+ 'headers' => [
+ 'Accept' => 'application/json',
+ ],
+ ]);
+ $body = $response->getBody()->getContents();
+ return json_decode($body);
+ } catch (RequestException $e) {
+ // Handle any errors.
+ $this->loggerFactory->get('silverback_ai_import')->error($e->getMessage());
+ }
+
+ return NULL;
+ }
+
+ /**
+ * Converts a PDF file to an Abstract Syntax Tree (AST) using an external service.
+ *
+ * Takes a Drupal file entity containing a PDF, resolves its real path, and sends it
+ * to a configured conversion service. The service returns a JSON response containing
+ * the PDF's AST representation.
+ *
+ * @param \Drupal\file\FileInterface $file
+ * The PDF file entity to convert.
+ *
+ * @return object|null
+ * Returns the decoded JSON response containing the AST if successful,
+ * or NULL if the request fails
+ *
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ * When the HTTP request fails
+ * @throws \RuntimeException
+ * When stream wrapper manager fails to resolve the file path
+ *
+ * @see \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface::getViaUri()
+ * @see \GuzzleHttp\ClientInterface::request()
+ */
+ private function getAstFromPdfFile(FileInterface $file) {
+ $uri = $file->getFileUri();
+ $stream_wrapper_manager = \Drupal::service('stream_wrapper_manager')->getViaUri($uri);
+ $file_path = $stream_wrapper_manager->realpath();
+ $parse_service_url = $this->configFactory->get('silverback_ai_import.settings')->get('converter_service_url');
+
+ $client = \Drupal::httpClient();
+ try {
+ $response = $client->request('GET', "{$parse_service_url}/pdf-convert?path={$file_path}", [
+ 'headers' => [
+ 'Accept' => 'application/json',
+ ],
+ ]);
+ $body = $response->getBody()->getContents();
+ $response = json_decode($body);
+ } catch (RequestException $e) {
+ // Handle any errors.
+ $this->loggerFactory->get('silverback_ai_import')->error($e->getMessage());
+ }
+ return $response;
+ }
+
+ /**
+ * Helper method.
+ *
+ * @param string $ast
+ * @param string $schema
+ *
+ * @return array
+ * @throws \Exception
+ */
+ public function extractData(string $ast, string $schema) {
+ $model = $this->configFactory->get('silverback_ai_import.settings')->get('ai_model') ?: self::DEFAULT_AI_MODEL;
+
+ $prompt = <<ai->request($prompt, $model, ['module' => 'silverback_import_ai']);
+ }
+
+ /**
+ * Send the AI request.
+ *
+ * @param string $ast
+ * @param string $type
+ * @param string $template
+ * @param string $schema
+ *
+ * @return array
+ * @throws \Exception
+ */
+ public function sendOpenAiRequest(string $ast, string $type, string $template, string $schema) {
+
+ $model = $this->configFactory->get('silverback_ai_import.settings')->get('ai_model') ?: self::DEFAULT_AI_MODEL;
+
+ $prompt = <<ai->request($prompt, $model, ['module' => 'silverback_import_ai']);
+ }
+
+ /**
+ * Extract data from markdown using AI.
+ *
+ * @param string $markdown
+ *
+ * @return array
+ * @throws \Exception
+ */
+ public function extractBaseDataFromMarkdown(string $markdown) {
+
+ $model = $this->configFactory->get('silverback_ai_import.settings')->get('ai_model') ?: self::DEFAULT_AI_MODEL;
+
+ $prompt = <<ai->request($prompt, $model, ['module' => 'silverback_import_ai']);
+ }
+
+ /**
+ * Retrieves a plugin instance that matches the specified chunk.
+ *
+ * This method creates an instance of the default AI plugin and then
+ * iterates through all available plugin definitions to find a plugin
+ * that matches the provided chunk. The first matching plugin instance
+ * will be selected and returned.
+ *
+ * @param array $chunk
+ * The input data that will be used to match against plugin definitions.
+ *
+ * @return object
+ * The plugin instance that matches the provided chunk or the default
+ * plugin if no matches are found.
+ * @throws \Drupal\Component\Plugin\Exception\PluginException
+ * @todo Order the plugin definitions by weight before attempting to find a match.
+ *
+ */
+ public function getPlugin(array $chunk) {
+ $default_plugin = $this->pluginManager->createInstance('ai_default');
+ $definitions = $this->pluginManager->getDefinitions();
+ // @todo Order by weight.
+ foreach ($definitions as $definition) {
+ $plugin = $this->pluginManager->createInstance($definition['id'], ['chunk' => $chunk]);
+ if ($plugin->matches($chunk)) {
+ $default_plugin = $plugin;
+ break;
+ }
+ }
+ return $default_plugin;
+ }
+
+ /**
+ * Flattens a hierarchical AST (Abstract Syntax Tree) into a linear array of nodes.
+ *
+ * This function converts a nested AST structure into a flat array where each node
+ * is assigned a unique ID and maintains a reference to its parent. It processes
+ * specific node types differently and handles recursive traversal of child nodes.
+ *
+ * @param $ast
+ * The AST structure to flatten.
+ * @param int|null $parent
+ * The ID of the parent node (used in recursion)
+ *
+ * @return array An array of flattened nodes, where each node contains:
+ * - type: The capitalized node type
+ * - id: A unique identifier
+ * - parent: Reference to the parent node's ID
+ * - Additional properties specific to each node type
+ */
+ public function flattenAst($ast, int $parent = NULL) {
+
+ $ast = json_decode(json_encode($ast), TRUE);
+ static $flatNodes = [];
+ static $id;
+
+ if ($ast === NULL) {
+ return $flatNodes;
+ }
+
+ foreach ($ast as $chunk) {
+ if (
+ isset($chunk['type'])
+ && in_array($chunk['type'], [
+ 'Strong',
+ 'Text',
+ 'ListItem',
+ 'Emphasis',
+ ])
+ ) {
+ continue;
+ }
+
+ if (
+ isset($chunk['type'])
+ && $chunk['type'] == 'Link'
+ && isset($chunk['children'])
+ && count($chunk['children']) == 1
+ && $chunk['children'][0]['type'] !== 'Image'
+ ) {
+ continue;
+ }
+
+ $children = $chunk['children'] ?? [];
+ // Chunk preprocessing.
+ $chunk['type'] = ucfirst($chunk['type']);
+ $chunk['id'] = ++$id;
+ $chunk['parent'] = $parent;
+
+ $flatNodes[] = $chunk;
+ // Recursively process children.
+ foreach ($children as $child) {
+ $this->flattenAst([$child], $id);
+ }
+ }
+
+ return $flatNodes;
+ }
+
+ /**
+ * Recursively iterates through a nested array to process Image type items.
+ *
+ * Traverses through the array and its nested children, processing any items
+ * of type 'Image' by adding a 'gutenberg' property. The function modifies
+ * the array in place using reference parameters.
+ *
+ * @param array &$data
+ * The array to process, passed by reference
+ * Expected structure: [
+ * 'type' => string,
+ * 'children' => array (optional)
+ * ].
+ * @param int $depth
+ * Current depth in the recursive traversal (default: 0)
+Ω *
+ * @throws \JsonException
+ * @see processChunk() Method used to process Image type items
+ */
+ public function iterateArray(array &$data, int $depth = 0): void {
+ foreach ($data as &$item) {
+ // Process item here.
+ if (isset($item['type'])) {
+ if ($item['type'] == 'Image') {
+ $item['gutenberg'] = $this->processChunk($item);
+ }
+ }
+ if (isset($item['children']) && is_array($item['children'])) {
+ $this->iterateArray($item['children'], $depth + 1);
+ }
+ }
+ }
+
+ /**
+ * Extracts various metadata from a given URL by fetching and parsing its HTML content.
+ *
+ * This function attempts to retrieve the HTML content of a URL and extract key information
+ * including title, path, meta tags, and language settings. It includes error handling for
+ * various failure scenarios.
+ *
+ * @param string $url
+ * The URL to extract data from.
+ *
+ * @return array An associative array containing:
+ * - title: string|null The page title if found
+ * - path: string The URL path component, defaults to "/" if not found
+ * - metatags: array Meta tag name-content pairs
+ * - language: string|null The page language if specified
+ * - error: string|null Error message if any error occurred, null otherwise
+ *
+ * @throws \Exception Caught internally and returned as error in result array
+ */
+ public function extractPageDataFromUrl($url) {
+ $data = [
+ 'title' => NULL,
+ 'path' => NULL,
+ 'metatags' => [],
+ 'language' => NULL,
+ 'error' => NULL,
+ ];
+
+ // Validate URL.
+ if (!filter_var($url, FILTER_VALIDATE_URL)) {
+ $data['error'] = "Invalid URL";
+ return $data;
+ }
+
+ try {
+ // Use file_get_contents with a user agent to avoid being blocked by some servers.
+ $options = [
+ 'http' => [
+ 'method' => 'GET',
+ // Example user agent.
+ 'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
+ // Timeout in seconds.
+ 'timeout' => 10,
+ ],
+ ];
+ $context = stream_context_create($options);
+ $html = @file_get_contents($url, FALSE, $context);
+
+ if ($html === FALSE) {
+ $error = error_get_last();
+ $data['error'] = "Failed to fetch URL: " . ($error ? $error['message'] : "Unknown error");
+ return $data;
+ }
+
+ if (preg_match('/(.*?)<\/title>/i', $html, $matches)) {
+ $data['title'] = trim(html_entity_decode($matches[1]));
+ }
+
+ $data['path'] = parse_url($url, PHP_URL_PATH);
+ if ($data['path'] === NULL) {
+ // Handle cases where there's no path.
+ $data['path'] = "/";
+ }
+
+ // Extract Meta Tags.
+ preg_match_all('/getMessage();
+ }
+
+ return $data;
+ }
+
+ /**
+ * Creates a Node entity from a DOCX Abstract Syntax Tree (AST).
+ *
+ * Processes the content.md file from the AST's output directory,
+ * extracts data through markdown processing, and creates a new node
+ * entity if valid data is present in the expected JSON structure.
+ *
+ * @param object $ast
+ * The AST object containing outputDirectory property
+ * with path to the processed DOCX content.
+ *
+ * @return \Drupal\node\Entity\Node|null
+ * Returns a Node entity if creation is successful and data is valid,
+ * or NULL if required data structure is not found
+ *
+ * @throws \Drupal\Core\Entity\EntityStorageException
+ * When there's an error saving the node entity
+ * @throws \RuntimeException
+ * When the content.md file cannot be read
+ *
+ * @see \Drupal\node\Entity\Node::create()
+ */
+ public function createEntityFromDocxAst($ast) {
+ $markdown = file_get_contents($ast->outputDirectory . '/content.md');
+ $data = $this->extractBaseDataFromMarkdown($markdown);
+ // @todo Surround with try-catch
+ if (isset($data['choices'][0]['message']['content'])) {
+ $data = json_decode($data['choices'][0]['message']['content'], TRUE);
+ $entity = Node::create([
+ 'type' => 'page',
+ 'title' => $data['title'],
+ 'langcode' => strtolower($data['language']),
+ ]);
+ $entity->save();
+ return $entity;
+ }
+ return NULL;
+ }
+
+ /**
+ * Creates a Node entity of type 'page' from a given URL.
+ *
+ * Extracts page data from the provided URL and creates a new node entity
+ * if the required data (title and language) is available.
+ *
+ * @param string $url
+ * The URL to extract page data from.
+ *
+ * @return \Drupal\node\Entity\Node|null
+ * Returns a Node entity if creation is successful,
+ * or NULL if required data is missing
+ *
+ * @throws \Drupal\Core\Entity\EntityStorageException
+ * @throws \Exception
+ * When there's an error saving the node entity
+ *
+ * @see \Drupal\node\Entity\Node::create()
+ */
+ public function createEntityFromUrl($url) {
+ $data = $this->extractPageDataFromUrl($url);
+ // @todo Handle exceptions
+ if (!empty($data['title']) && !empty($data['language'])) {
+ $entity = Node::create([
+ 'type' => 'page',
+ 'title' => $data['title'],
+ 'langcode' => strtolower($data['language']),
+ ]);
+ $entity->save();
+ return $entity;
+ }
+ return NULL;
+ }
+
+ /**
+ * Creates a File entity from Dropzone uploaded file data.
+ *
+ * This function handles the process of copying an uploaded file to a designated
+ * public directory and creating a corresponding File entity in Drupal.
+ *
+ * @param array $file_data
+ * The file data from Dropzone upload
+ * Expected structure: ['uploaded_files'][0]['path'].
+ *
+ * @return \Drupal\file\Entity\File The created and saved file entity
+ *
+ * @throws \Drupal\Core\File\Exception\FileException When file operations fail
+ * @throws \Drupal\Core\Entity\EntityStorageException When file entity creation fails
+ *
+ * @see \Drupal\Core\File\FileSystemInterface::prepareDirectory()
+ * @see \Drupal\Core\File\FileSystemInterface::copy()
+ * @see \Drupal\file\Entity\File::create()
+ */
+ public function createFileEntityFromDropzoneData($file_data) {
+ // @todo Handle exceptions
+ $filepath = $file_data['uploaded_files'][0]['path'];
+ $directory = 'public://converted';
+ $file_system = \Drupal::service('file_system');
+ $file_system->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
+ $file_system->copy($filepath, $directory . '/' . basename($filepath), FileSystemInterface::EXISTS_REPLACE);
+
+ $file = File::create([
+ 'filename' => basename($filepath),
+ 'uri' => "{$directory}/" . basename($filepath),
+ 'status' => FileInterface::STATUS_PERMANENT,
+ 'uid' => $this->currentUser->id() ?? self::ADMINISTRATOR_ID,
+ ]);
+ $file->setPermanent();
+ $file->save();
+ return $file;
+ }
+
+ /**
+ * Get all post import plugins.
+ *
+ * @return array
+ */
+ public function getPostImportPlugins() {
+ $definitions = $this->pluginManagerPost->getDefinitions();
+ $plugins = [];
+ foreach ($definitions as $definition) {
+ $plugins[] = $definition['id'];
+ }
+ return $plugins;
+ }
+
+ /**
+ * Post import process for plugin.
+ *
+ * @param $plugin_id
+ * @param $chunks
+ *
+ * @return mixed
+ * @throws \Drupal\Component\Plugin\Exception\PluginException
+ */
+ public function postProcessChunks($plugin_id, $chunks,) {
+ $plugin = $this->pluginManagerPost->createInstance($plugin_id);
+ return $plugin->convert($chunks);
+ }
+}
diff --git a/packages/drupal/silverback_ai/modules/silverback_ai_import/src/ContentImportBatch.php b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/ContentImportBatch.php
new file mode 100644
index 000000000..28c62fed4
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/ContentImportBatch.php
@@ -0,0 +1,197 @@
+loggerChannel = $loggerFactory->get('silverback_image_ai');
+ }
+
+ /**
+ * Creates a batch operation to process media image updates.
+ *
+ * This method initializes a batch process for updating media images, setting
+ * up the batch operations and conditions for Drush integration if run via CLI.
+ *
+ * @return void
+ */
+ public function create(array $chunks, $entity): void {
+
+ $batchBuilder = (new BatchBuilder())
+ ->setTitle($this->t('Running import...'))
+ ->setFinishCallback([self::class, 'finish'])
+ ->setInitMessage('The initialization message (optional)')
+ ->setProgressMessage('Completed @current of @total.');
+
+ $total = count($chunks);
+ $count = 0;
+ // Create multiple batch operations based on the $batchSize.
+ foreach ($chunks as $chunk) {
+ $item = [
+ 'chunk' => $chunk,
+ 'nid' => $entity->id(),
+ ];
+ $batch = [
+ 'item' => $item,
+ 'count' => $count++,
+ 'total' => $total,
+ ];
+ $batchBuilder->addOperation([ContentImportBatch::class, 'process'], [$batch]);
+ }
+
+ // @todo Add DI
+ $service = \Drupal::service('silverback_ai_import.content');
+ $post_import_plugins = $service->getPostImportPlugins();
+ foreach ($post_import_plugins as $plugin) {
+ $batch = [
+ 'plugin_id' => $plugin,
+ 'entity' => $entity,
+ 'count' => $count++,
+ 'total' => $total,
+ ];
+ $batchBuilder->addOperation([ContentImportBatch::class, 'postProcess'], [$batch]);
+ }
+
+ // @todo Here, discover all post import plugins and add an operation at the end of this array.
+ batch_set($batchBuilder->toArray());
+ }
+
+ /**
+ * Batch operation callback.
+ *
+ * @param array $batch
+ * Information about batch (items, size, total, ...).
+ * @param array $context
+ * Batch context.
+ */
+ public static function process(array $batch, array &$context) {
+ $processed = !empty($context['results']) ? count($context['results']) : $batch['count'];
+ $service = \Drupal::service('silverback_ai_import.content');
+ $content = $service->processChunk($batch['item']['chunk']);
+ \Drupal::logger('silverback_ai_import')->debug($content);
+ $context['results']['content'][] = $content;
+
+ $context['results']['nid'] = $batch['item']['nid'];
+
+ $context['message'] = t('Processing chunk @processed/@total', [
+ '@processed' => $processed,
+ '@total' => $batch['total'],
+ ]);
+ }
+
+ /**
+ * Batch operation callback.
+ *
+ * @param array $batch
+ * Information about batch (items, size, total, ...).
+ * @param array $context
+ * Batch context.
+ */
+ public static function postProcess(array $batch, array &$context) {
+ $service = \Drupal::service('silverback_ai_import.content');
+ $processed_chunks = $service->postProcessChunks($batch['plugin_id'], $context['results']['content']);
+ $context['results']['content'] = $processed_chunks;
+ $processed = !empty($context['results']) ? count($context['results']) : $batch['count'];
+ $context['message'] = t('Processing chunk @processed/@total', [
+ '@processed' => $processed,
+ '@total' => $batch['total'],
+ ]);
+ }
+
+ /**
+ * Finish batch.
+ *
+ * This function is a static function to avoid serializing the ConfigSync
+ * object unnecessarily.
+ *
+ * @param bool $success
+ * Indicate that the batch API tasks were all completed successfully.
+ * @param array $results
+ * An array of all the results that were updated in update_do_one().
+ * @param array $operations
+ * A list of the operations that had not been completed by the batch API.
+ */
+ public static function finish(bool $success, array $results, array $operations) {
+ $nid = $results['nid'];
+ if (!empty($nid)) {
+ $node = Node::load($nid);
+
+ // @todo Possible we need more process here.
+ $results['content'] = array_map(function ($item) {
+ return str_replace('', '', $item);
+ }, $results['content']);
+
+ // @todo Improve that to respect also templates
+ $implode = implode(PHP_EOL, $results['content']);
+ $content = <<
+ $implode
+
+
+ EOD;
+
+ // @todo Add post import process here
+ try {
+
+ // @todo
+ $config = \Drupal::service('config.factory')->get('gutenberg.settings');
+ $node_type = $node->type->getString();
+ $gutenberg_template = $config->get($node_type . '_template') ? json_decode($config->get($node_type . '_template')) : NULL;
+ $init = '';
+ // @todo We can do better than this
+ foreach ($gutenberg_template as $item) {
+ $init .= "\n";
+ }
+
+ $cleaned = str_replace('', $content, $init);
+ $node->body->value = $cleaned;
+ $node->save();
+ } catch (\Exception $e) {
+ // @todo
+ }
+ }
+
+ $messenger = \Drupal::messenger();
+ if ($success) {
+ $messenger->addStatus(t('Items processed successfully.'));
+ } else {
+ // An error occurred.
+ // $operations contains the operations that remained unprocessed.
+ $error_operation = reset($operations);
+ $message = t(
+ 'An error occurred while processing %error_operation with arguments: @arguments',
+ ['%error_operation' => $error_operation[0], '@arguments' => print_r($error_operation[1], TRUE)]
+ );
+ $messenger->addError($message);
+ }
+ }
+}
diff --git a/packages/drupal/silverback_ai/modules/silverback_ai_import/src/ContentImportLoggerService.php b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/ContentImportLoggerService.php
new file mode 100644
index 000000000..8cde5cca9
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/ContentImportLoggerService.php
@@ -0,0 +1,183 @@
+currentUser) {
+ $uid = $this->currentUser->id();
+ }
+
+ // @todo Validate input array
+ try {
+ $this->connection
+ ->insert('silverback_ai_import')
+ ->fields([
+ 'uid' => $uid,
+ 'timestamp' => (new DrupalDateTime())->getTimestamp(),
+ 'target_entity_type_id' => $entity->getEntityTypeId(),
+ 'target_entity_id' => $entity->id(),
+ 'target_entity_revision_id' => NULL,
+ 'source' => $source ?? '',
+ 'output_folder' => $ast->outputDirectory,
+ 'data' => serialize($ast),
+ ])
+ ->execute();
+ } catch (\Exception $e) {
+ $this->loggerFactory->get('silverback_ai')->error($e->getMessage());
+ }
+ }
+
+ /**
+ * Retrieves a list of entries from the 'silverback_ai_import' table.
+ *
+ * This function queries the database to select fields related to AI usage
+ * and orders them by ID in descending order. It paginates the result
+ * according to a predefined limit. Each row fetched from the database
+ * is processed using the `buildRow` method before being added to the result set.
+ *
+ * @return array
+ * An array of processed database records.
+ */
+ public function getEntries() {
+ $query = $this->connection->select('silverback_ai_import', 's')
+ ->fields('s', [
+ 'id',
+ 'uid',
+ 'timestamp',
+ 'target_entity_id',
+ 'target_entity_type_id',
+ 'target_entity_revision_id',
+ 'source',
+ 'output_folder',
+ 'data',
+ ])
+ ->orderBy('id', 'DESC');
+ $pager = $query->extend('Drupal\Core\Database\Query\PagerSelectExtender')->limit(self::PAGER_LIMIT);
+ $rsc = $pager->execute();
+ $rows = [];
+
+ foreach ($rsc->fetchAll() as $row) {
+ $rows[] = $this->buildRow($row);
+ }
+
+ return $rows;
+ }
+
+ /**
+ * Builds a renderable array representing a row of data.
+ *
+ * This method constructs an array of information based on the data from
+ * the provided row, including entity details, user information, and additional
+ * metadata such as timestamps and provider information.
+ *
+ * @param object $row
+ * The data row object containing properties such as 'target_entity_id',
+ * 'target_entity_type_id', 'uid', 'timestamp', 'total_count', 'provider',
+ * 'model', and 'module'.
+ *
+ * @return array
+ * Array with the following elements:
+ * - 'timestamp': The formatted timestamp of when the entry was created.
+ * - 'username': The display name of the user associated with the entry.
+ * - 'entity_id': The capitalized entity bundle string or empty string if
+ * the entity is not found.
+ * - 'info': A renderable link to detailed usage information displayed in
+ * a modal dialog.
+ *
+ * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+ * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+ */
+ public function buildRow($row) {
+ $entity_info = '';
+ if ($row->target_entity_id && $row->target_entity_type_id) {
+ // @todo Aldo check revision
+ $entity = $this->entityTypeManager->getStorage($row->target_entity_type_id)->load($row->target_entity_id);
+ $entity_info = $entity ? $entity->bundle() : '';
+ // @todo Add url to entity. Problem is the e.g. File entities
+ // they return exception calling this method.
+ }
+
+ $user = User::load($row->uid);
+ $username = '';
+ if ($user) {
+ $username = $user->getDisplayName();
+ }
+
+ $icon_info = '';
+
+ $link = Link::createFromRoute(
+ Markup::create($icon_info),
+ 'silverback_ai.ai_usage.details',
+ ['record' => $row->id],
+ [
+ 'attributes' => [
+ 'class' => ['use-ajax'],
+ 'data-dialog-type' => 'modal',
+ 'data-dialog-options' => Json::encode([
+ 'width' => 800,
+ ]),
+ ],
+ 'attached' => [
+ 'library' => ['core/drupal.dialog.ajax'],
+ ],
+ ]
+ );
+
+ return [
+ 'timestamp' => DrupalDateTime::createFromTimestamp($row->timestamp)->format('d.m.Y H:i'),
+ 'username' => $username,
+ 'entity_id' => ucfirst($entity_info),
+ 'info' => $link,
+ ];
+ }
+}
diff --git a/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Form/ImportAiSettingsForm.php b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Form/ImportAiSettingsForm.php
new file mode 100644
index 000000000..72280ffc9
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Form/ImportAiSettingsForm.php
@@ -0,0 +1,86 @@
+ 'details',
+ '#title' => $this->t('Open AI model'),
+ '#open' => TRUE,
+ ];
+
+ // @todo Make this dynamically
+ $form['credentials']['ai_model'] = [
+ '#type' => 'select',
+ '#title' => $this->t('Model'),
+ '#options' => [
+ 'gpt-4o-mini' => 'gpt-4o-mini',
+ 'gpt-4o-mini-2024-07-18' => 'gpt-4o-mini-2024-07-18',
+ ],
+ '#empty_option' => $this->t('- Select model -'),
+ '#description' => $this->t('Leave empty to use the default gpt-4o-mini model.') . '
' .
+ $this->t('Learn more about the models.', [
+ '@href' => 'https://openai.com/index/gpt-4o-mini-advancing-cost-efficient-intelligence',
+ ]),
+ ];
+
+ $form['general'] = [
+ '#type' => 'details',
+ '#title' => $this->t('General settings'),
+ '#open' => TRUE,
+ ];
+
+ $form['general']['converter_service_url'] = [
+ '#type' => 'textfield',
+ '#title' => $this->t('Converter URL service'),
+ '#default_value' => $this->config('silverback_ai_import.settings')->get('converter_service_url'),
+ ];
+ return parent::buildForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateForm(array &$form, FormStateInterface $form_state): void {
+ parent::validateForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state): void {
+ $this->config('silverback_ai_import.settings')
+ ->set('converter_service_url', $form_state->getValue('converter_service_url'))
+ ->set('ai_model', $form_state->getValue('ai_model'))
+ ->save();
+ parent::submitForm($form, $form_state);
+ }
+}
diff --git a/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Form/PageDropForm.php b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Form/PageDropForm.php
new file mode 100644
index 000000000..c55d1771f
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Form/PageDropForm.php
@@ -0,0 +1,248 @@
+ 'item',
+ '#markup' => Markup::create('Create content by importing either a file or an existing URL.'),
+ ];
+
+ $form['import'] = [
+ '#type' => 'details',
+ '#title' => $this->t('Create content'),
+ '#open' => TRUE,
+ '#weight' => 99,
+ ];
+ $form['import']['import_type'] = [
+ '#type' => 'radios',
+ '#title' => $this->t('Create content from:'),
+ '#description' => $this->t('* Experimental, use with caution'),
+ '#options' => [
+ 'docx' => $this->t('Microsoft Word file'),
+ 'pdf' => $this->t("PDF file (*)"),
+ 'url' => $this->t("Remote web page (*)"),
+ ],
+ '#default_value' => 'none',
+ '#required' => TRUE,
+ ];
+
+ $form['import']['container_docx'] = [
+ '#type' => 'container',
+ '#states' => [
+ 'visible' => [
+ 'input[name="import_type"]' => ['value' => 'docx'],
+ ],
+ ],
+ ];
+
+ $form['import']['container_docx']['file'] = [
+ '#title' => $this->t('Drag and drop a Microsoft Word file'),
+ '#type' => 'dropzonejs',
+ '#required' => TRUE,
+ '#dropzone_description' => 'Drag and drop a file here',
+ '#max_filesize' => '1M',
+ '#max_files' => 1,
+ '#extensions' => 'doc docx',
+ '#upload_location' => 'public://converted/',
+ '#states' => [
+ 'required' => [
+ 'input[name="import_type"]' => ['value' => 'docx'],
+ ],
+ ],
+ ];
+
+ $form['import']['container_url'] = [
+ '#type' => 'container',
+ '#states' => [
+ 'visible' => [
+ 'input[name="import_type"]' => ['value' => 'url'],
+ ],
+ ],
+ ];
+
+ $form['import']['container_url']['url_value'] = [
+ '#type' => 'url',
+ '#title' => $this->t('URL'),
+ '#maxlength' => 2048,
+ '#size' => 128,
+ '#states' => [
+ 'required' => [
+ 'input[name="import_type"]' => ['value' => 'url'],
+ ],
+ ],
+ ];
+
+ $form['import']['container_pdf'] = [
+ '#type' => 'container',
+ '#states' => [
+ 'visible' => [
+ 'input[name="import_type"]' => ['value' => 'pdf'],
+ ],
+ ],
+ ];
+
+ $form['import']['container_pdf']['pdf_file'] = [
+ '#title' => $this->t('Drag and drop a PDF file'),
+ '#type' => 'dropzonejs',
+ '#required' => TRUE,
+ '#dropzone_description' => 'Drag and drop a file here',
+ '#max_filesize' => '24M',
+ '#max_files' => 1,
+ '#extensions' => 'pdf',
+ '#upload_location' => 'public://converted/',
+ '#states' => [
+ 'required' => [
+ 'input[name="import_type"]' => ['value' => 'pdf'],
+ ],
+ ],
+ ];
+
+ $form['import']['container_output'] = [
+ '#type' => 'container',
+ ];
+ $form['import']['container_output']['output'] = [
+ '#type' => 'item',
+ '#prefix' => '',
+ '#suffix' => '
',
+ ];
+
+ $form['actions'] = [
+ '#type' => 'actions',
+ '#states' => [
+ 'visible' => [
+ 'input[name="import_type"]' => ['value' => 'docx'],
+ ],
+ ],
+ 'submit' => [
+ '#type' => 'submit',
+ '#value' => $this->t('Import document'),
+ ],
+ ];
+
+ $form['actions_url'] = [
+ '#type' => 'actions',
+ '#states' => [
+ 'visible' => [
+ 'input[name="import_type"]' => ['value' => 'url'],
+ ],
+ ],
+ 'submit' => [
+ '#type' => 'submit',
+ '#value' => $this->t('Import web page'),
+ ],
+ ];
+
+ $form['actions_pdf'] = [
+ '#type' => 'actions',
+ '#states' => [
+ 'visible' => [
+ 'input[name="import_type"]' => ['value' => 'pdf'],
+ ],
+ ],
+ 'submit' => [
+ '#type' => 'submit',
+ '#value' => $this->t('Import PDF'),
+ ],
+ ];
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateForm(array &$form, FormStateInterface $form_state): void {
+ $file = $form_state->getValue('file');
+ $pdf_file = $form_state->getValue('pdf_file');
+
+ $type = $form_state->getValue('import_type');
+ if ($type == 'docx' && empty($file['uploaded_files'])) {
+ $form_state->setErrorByName('file', $this->t('Please upload a file to import.'));
+ }
+ if ($type == 'pdf' && empty($pdf_file['uploaded_files'])) {
+ $form_state->setErrorByName('file', $this->t('Please upload a PDF to import.'));
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state): void {
+
+ /** @var \Drupal\Core\Entity\EntityInterface $entity */
+
+ $url_value = $form_state->getValue('url_value');
+
+ $service = \Drupal::service('silverback_ai_import.content');
+ $content = \Drupal::service('silverback_ai_import.batch.import');
+ $type = $form_state->getValue('import_type');
+ $contentImportLogger = \Drupal::service('silverback_ai_imoprt.logger');
+
+ switch ($type) {
+ case ContentImportAiService::PDF:
+ $file = $form_state->getValue('pdf_file');
+ break;
+ case ContentImportAiService::URL:
+ $file = $url_value;
+ break;
+ case ContentImportAiService::NONE:
+ break;
+ default:
+ $file = $form_state->getValue('file');
+ }
+
+ if (in_array($type, [ContentImportAiService::DOCX, ContentImportAiService::PDF])) {
+ $file = $service->createFileEntityFromDropzoneData($file);
+ }
+
+ if (!empty($file)) {
+ $ast = $service->getAst($file, $type);
+ $entity = NULL;
+
+ switch ($type) {
+ case ContentImportAiService::PDF:
+ $entity = $service->createEntityFromDocxAst($ast);
+ break;
+ case ContentImportAiService::URL:
+ $entity = $service->createEntityFromUrl($file);
+ break;
+ default:
+ $entity = $service->createEntityFromDocxAst($ast);
+ }
+
+ $flatten = $service->flattenAst($ast->content);
+ $content->create($flatten, $entity);
+ $contentImportLogger->createEntry($ast, $entity, $file->getFileUri());
+ $form_state->setRedirectUrl($entity->toUrl('edit-form'));
+ }
+ }
+}
diff --git a/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Plugin/AiImport/DefaultImportPlugin.php b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Plugin/AiImport/DefaultImportPlugin.php
new file mode 100644
index 000000000..dc96ba9fe
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Plugin/AiImport/DefaultImportPlugin.php
@@ -0,0 +1,120 @@
+configuration = $configuration;
+ $this->pluginId = $plugin_id;
+ $this->pluginDefinition = $plugin_definition;
+ $this->schema = $this->getSchema();
+ $this->template = $this->getTemplate();
+ }
+
+ /**
+ * Get a description if the plugin.
+ */
+ public function description() {
+ return $this->t('Default converter, just throws the html into Gutenberg blocks.');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getSchema() {
+ return [
+ 'htmlValue' => 'string, html markup',
+ ];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getTemplate() {
+ return <<
+ htmlValue
+
+ EOD;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function matches(array $chunk) {
+ // This should not much by default.
+ return FALSE;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function convert(array $chunk) {
+ // We are using some custom method here.
+ $data['htmlValue'] = $chunk['htmlValue'];
+ return $this->generateBlock($data);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function generateBlock(array $data): string {
+ // Validate required keys.
+ $required_keys = array_keys($this->getSchema());
+ $template = $this->getTemplate();
+ foreach ($required_keys as $key) {
+ if (!isset($data[$key])) {
+ throw new \InvalidArgumentException("Missing required key: {$key}");
+ }
+ }
+
+ // Create replacement pairs.
+ foreach ($required_keys as $key) {
+ $replacements[$key] = $data[$key];
+ }
+
+ // Perform replacements.
+ foreach ($replacements as $key => $value) {
+ $template = str_replace($key, $value, $template);
+ }
+
+ return $template;
+ }
+}
diff --git a/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Plugin/AiImport/HeaderImportPlugin.php b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Plugin/AiImport/HeaderImportPlugin.php
new file mode 100644
index 000000000..2e8910fc6
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Plugin/AiImport/HeaderImportPlugin.php
@@ -0,0 +1,184 @@
+configuration = $configuration;
+ $this->pluginId = $plugin_id;
+ $this->pluginDefinition = $plugin_definition;
+ $this->schema = $this->getSchema();
+ $this->template = $this->getTemplate();
+ }
+
+ /**
+ * Get a description if the plugin.
+ */
+ public function description() {
+ return $this->t('Convert markdown headers to Gutenberg blocks.');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getSchema() {
+ // Actually we are only interest here in keys.
+ // Values can be descriptions to help the data extraction if using AI.
+ return [
+ 'attributesJson' => 'json attributes string',
+ 'headingText' => 'string',
+ 'headingLevel' => 'number, 2 or 3 or 4.',
+ 'headerHtmlTag' => 'h2 or h3 or h4. Any other header should be converted to h2.',
+ ];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getTemplate() {
+ return <<
+ headingText
+
+ EOD;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function matches(array $chunk) {
+ return $chunk['type'] == 'Header';
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function convert(array $chunk) {
+ // We are using some custom method here.
+ // @todo Add a validation method.
+ $data = $this->parseMarkdownHeader($chunk['raw']);
+ return $this->generateBlock($data);
+ }
+
+ /**
+ * Parses a markdown header string and returns details about it.
+ *
+ * This method trims whitespace from the header string, matches a specific
+ * markdown heading pattern, determines the heading level based on the number
+ * of '#' characters, and generates the corresponding HTML tag for the header.
+ *
+ * The resulting heading level is constrained between 2 and 4 based on custom rules.
+ *
+ * @param string $header
+ * The markdown header string to parse.
+ *
+ * @return array
+ * An associative array.
+ *
+ * @throws \InvalidArgumentException if the header is not in a valid markdown format.
+ */
+ private function parseMarkdownHeader(string $header): array {
+ // Trim whitespace.
+ $header = trim($header);
+
+ // Match the heading pattern.
+ if (!preg_match('/^(#{1,6})\s+(.+)$/', $header, $matches)) {
+ throw new \InvalidArgumentException('Invalid markdown header format');
+ }
+
+ // Get the number of # symbols to determine heading level.
+ $level = strlen($matches[1]);
+
+ // Restrictions from the custom/header SLB block.
+ if ($level == 1) {
+ $level = 2;
+ }
+
+ if ($level > 4) {
+ $level = 4;
+ }
+
+ // Get the actual heading text.
+ $text = trim($matches[2]);
+ // $text = str_replace('"', '', $text);
+ // $text = str_replace('\\', '', $text);
+ // Create the corresponding HTML tag.
+ $headerHtmlTag = 'h' . $level;
+
+ $attributesJson = [
+ 'level' => intval($level),
+ 'text' => $text,
+ ];
+
+ return [
+ 'attributesJson' => json_encode($attributesJson),
+ 'headingText' => $text,
+ 'headingLevel' => $level,
+ 'headerHtmlTag' => $headerHtmlTag,
+ ];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function generateBlock(array $data): string {
+ // Validate required keys.
+ $required_keys = array_keys($this->getSchema());
+ $template = $this->getTemplate();
+ foreach ($required_keys as $key) {
+ if (!isset($data[$key])) {
+ throw new \InvalidArgumentException("Missing required key: {$key}");
+ }
+ }
+
+ // Create replacement pairs.
+ foreach ($required_keys as $key) {
+ $replacements[$key] = $data[$key];
+ }
+
+ // Perform replacements.
+ foreach ($replacements as $key => $value) {
+ $template = str_replace($key, $value, $template);
+ }
+
+ return $template;
+ }
+
+}
diff --git a/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Plugin/AiImport/ImageImportPlugin.php b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Plugin/AiImport/ImageImportPlugin.php
new file mode 100644
index 000000000..945c82aac
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Plugin/AiImport/ImageImportPlugin.php
@@ -0,0 +1,204 @@
+configuration = $configuration;
+ $this->pluginId = $plugin_id;
+ $this->pluginDefinition = $plugin_definition;
+ $this->schema = $this->getSchema();
+ $this->template = $this->getTemplate();
+ }
+
+ /**
+ * Get a description if the plugin.
+ */
+ public function description() {
+ return $this->t('Convert markdown images to Gutenberg blocks.');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getSchema() {
+ return [
+ 'mediaId' => 'number',
+ ];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getTemplate() {
+ return <<
+
+
+
+
+ EOD;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function matches(array $chunk) {
+ return $chunk['type'] == 'Image';
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function convert(array $chunk) {
+ // We are using some custom method here.
+ // @todo Add a validation method.
+ $src = $chunk['src'];
+ $media = $this->createMediaImageFromPath($src);
+ $data = ['mediaId' => ''];
+ if ($media) {
+ $data = ['mediaId' => $media->id()];
+ }
+ return $this->generateBlock($data);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function generateBlock(array $data): string {
+ // Validate required keys.
+ $required_keys = array_keys($this->getSchema());
+ $template = $this->getTemplate();
+ foreach ($required_keys as $key) {
+ if (!isset($data[$key])) {
+ throw new \InvalidArgumentException("Missing required key: {$key}");
+ }
+ }
+
+ // Create replacement pairs.
+ foreach ($required_keys as $key) {
+ $replacements[$key] = $data[$key];
+ }
+
+ // Perform replacements.
+ foreach ($replacements as $key => $value) {
+ $template = str_replace($key, $value, $template);
+ }
+
+ return $template;
+ }
+
+ /**
+ * Creates a file entity and a media image entity from a given image path.
+ *
+ * @param string $image_path
+ * The server path to the image file.
+ * @param string $media_bundle
+ * The media bundle type (optional, defaults to 'image').
+ * @param int $user_id
+ * The user ID to associate with the created entities (optional, defaults to current user).
+ *
+ * @return \Drupal\media\MediaInterface|null
+ * The created media entity, or NULL if creation fails.
+ *
+ * @throws \Drupal\Core\Entity\EntityStorageException
+ */
+ public function createMediaImageFromPath($image_path, $media_bundle = 'image', $user_id = NULL) {
+ // Ensure the file exists.
+ if (!file_exists($image_path)) {
+ \Drupal::logger('image_import')->error('File does not exist: @path', ['@path' => $image_path]);
+ return NULL;
+ }
+
+ // Get the current user if no user ID is provided.
+ if ($user_id === NULL) {
+ $user_id = \Drupal::currentUser()->id();
+ }
+
+ // Prepare the file.
+ $file_uri = 'public://' . basename($image_path);
+
+ try {
+ // Create file entity.
+ $file = \Drupal::service('file.repository')->writeData(
+ file_get_contents($image_path),
+ $file_uri,
+ FileSystemInterface::EXISTS_REPLACE
+ );
+
+ // Set file status to permanent.
+ if ($file) {
+ $file->setPermanent();
+ $file->save();
+ }
+ else {
+ \Drupal::logger('image_import')->error('Failed to create file entity for: @path', ['@path' => $image_path]);
+ return NULL;
+ }
+
+ // Create media entity.
+ $media_storage = \Drupal::entityTypeManager()->getStorage('media');
+ /** @var \Drupal\media\Entity\media $media */
+ $media = $media_storage->create([
+ 'bundle' => $media_bundle,
+ 'name' => $file->getFilename(),
+ 'uid' => $user_id,
+ 'status' => self::PUBLISHED,
+ 'field_media_image' => [
+ 'target_id' => $file->id(),
+ // @todo Improve alt text generation.
+ 'alt' => $file->getFilename(),
+ 'title' => $file->getFilename(),
+ ],
+ ]);
+
+ $media->save();
+ return $media;
+ }
+ catch (\Exception $e) {
+ \Drupal::logger('image_import')->error('Error creating media entity: @error', ['@error' => $e->getMessage()]);
+ return NULL;
+ }
+ }
+
+}
diff --git a/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Plugin/AiImport/ListImportPlugin.php b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Plugin/AiImport/ListImportPlugin.php
new file mode 100644
index 000000000..44ef7227f
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Plugin/AiImport/ListImportPlugin.php
@@ -0,0 +1,136 @@
+configuration = $configuration;
+ $this->pluginId = $plugin_id;
+ $this->pluginDefinition = $plugin_definition;
+ $this->schema = $this->getSchema();
+ $this->template = $this->getTemplate();
+ }
+
+ /**
+ * Get a description if the plugin.
+ */
+ public function description() {
+ return $this->t('List converter, markdown into Gutenberg block.');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getSchema() {
+ return [
+ 'listItems' => 'string, html markup',
+ ];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getTemplate() {
+ $config = $this->configuration;
+ $chunk = $config['chunk'];
+
+ if (isset($chunk['ordered'])
+ && $chunk['ordered'] == TRUE) {
+ return <<
+ listItems
+
+ EOD;
+ }
+
+ return <<
+ listItems
+
+ EOD;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function matches(array $chunk) {
+ return $chunk['type'] == 'List';
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function convert(array $chunk) {
+
+ $html = $chunk['htmlValue'];
+ $html = str_replace(["\r", "\n"], '', $html);
+
+ $data['listItems'] = $html;
+
+ return $this->generateBlock($data);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function generateBlock(array $data): string {
+ // Validate required keys.
+ $required_keys = array_keys($this->getSchema());
+ $template = $this->getTemplate();
+ foreach ($required_keys as $key) {
+ if (!isset($data[$key])) {
+ throw new \InvalidArgumentException("Missing required key: {$key}");
+ }
+ }
+
+ // Create replacement pairs.
+ foreach ($required_keys as $key) {
+ $replacements[$key] = $data[$key];
+ }
+
+ // Perform replacements.
+ foreach ($replacements as $key => $value) {
+ $template = str_replace($key, $value, $template);
+ }
+
+ return $template;
+ }
+
+}
diff --git a/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Plugin/AiImport/ParagraphImportPlugin.php b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Plugin/AiImport/ParagraphImportPlugin.php
new file mode 100644
index 000000000..61ad647c5
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Plugin/AiImport/ParagraphImportPlugin.php
@@ -0,0 +1,120 @@
+configuration = $configuration;
+ $this->pluginId = $plugin_id;
+ $this->pluginDefinition = $plugin_definition;
+ $this->schema = $this->getSchema();
+ $this->template = $this->getTemplate();
+ }
+
+ /**
+ * Get a description if the plugin.
+ */
+ public function description() {
+ return $this->t('Paragraph converter for Gutenberg blocks.');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getSchema() {
+ return [
+ 'paragraphText' => 'string, html markup',
+ ];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getTemplate() {
+ return <<
+ paragraphText
+
+ EOD;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function matches(array $chunk) {
+ return $chunk['type'] == 'Paragraph';
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function convert(array $chunk) {
+ $val = $chunk['htmlValue'];
+ $data['paragraphText'] = preg_replace('/]+>/i', '', $val);
+ return $this->generateBlock($data);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function generateBlock(array $data): string {
+ // Validate required keys.
+ $required_keys = array_keys($this->getSchema());
+ $template = $this->getTemplate();
+ foreach ($required_keys as $key) {
+ if (!isset($data[$key])) {
+ throw new \InvalidArgumentException("Missing required key: {$key}");
+ }
+ }
+
+ // Create replacement pairs.
+ foreach ($required_keys as $key) {
+ $replacements[$key] = $data[$key];
+ }
+
+ // Perform replacements.
+ foreach ($replacements as $key => $value) {
+ $template = str_replace($key, $value, $template);
+ }
+
+ return $template;
+ }
+
+}
diff --git a/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Plugin/AiImport/TableImportPlugin.php b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Plugin/AiImport/TableImportPlugin.php
new file mode 100644
index 000000000..5dcc9ffa7
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Plugin/AiImport/TableImportPlugin.php
@@ -0,0 +1,119 @@
+configuration = $configuration;
+ $this->pluginId = $plugin_id;
+ $this->pluginDefinition = $plugin_definition;
+ $this->schema = $this->getSchema();
+ $this->template = $this->getTemplate();
+ }
+
+ /**
+ * Get a description if the plugin.
+ */
+ public function description() {
+ return $this->t('Table converter, markdown into Gutenberg block.');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getSchema() {
+ return [
+ 'htmlTable' => 'string, html markup',
+ ];
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function getTemplate() {
+ return <<
+
+
+ EOD;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function matches(array $chunk) {
+ return $chunk['type'] == 'Table';
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function convert(array $chunk) {
+ $data['htmlTable'] = $chunk['htmlValue'];
+ return $this->generateBlock($data);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function generateBlock(array $data): string {
+ // Validate required keys.
+ $required_keys = array_keys($this->getSchema());
+ $template = $this->getTemplate();
+ foreach ($required_keys as $key) {
+ if (!isset($data[$key])) {
+ throw new \InvalidArgumentException("Missing required key: {$key}");
+ }
+ }
+
+ // Create replacement pairs.
+ foreach ($required_keys as $key) {
+ $replacements[$key] = $data[$key];
+ }
+
+ // Perform replacements.
+ foreach ($replacements as $key => $value) {
+ $template = str_replace($key, $value, $template);
+ }
+
+ return $template;
+ }
+
+}
diff --git a/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Plugin/AiPostImport/EmptyChunksRemovePostImportPlugin.php b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Plugin/AiPostImport/EmptyChunksRemovePostImportPlugin.php
new file mode 100644
index 000000000..a951d1935
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Plugin/AiPostImport/EmptyChunksRemovePostImportPlugin.php
@@ -0,0 +1,55 @@
+configuration = $configuration;
+ $this->pluginId = $plugin_id;
+ $this->pluginDefinition = $plugin_definition;
+ }
+
+ /**
+ * Get a description if the plugin.
+ */
+ public function description() {
+ return $this->t('Filters empty chunks.');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function convert(array $chunks) {
+ $chunks = array_filter($chunks, function ($item) {
+ if (str_starts_with(trim($item), '')) {
+ $cleaned = $cleaned = preg_replace('/[\r\n]+/', '', strip_tags($item));
+ return strlen($cleaned) > 0;
+ }
+ return TRUE;
+ });
+ return $chunks;
+ }
+}
diff --git a/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Plugin/AiPostImport/ImageCaptionPostImport.php b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Plugin/AiPostImport/ImageCaptionPostImport.php
new file mode 100644
index 000000000..2f4527e45
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_ai_import/src/Plugin/AiPostImport/ImageCaptionPostImport.php
@@ -0,0 +1,94 @@
+configuration = $configuration;
+ $this->pluginId = $plugin_id;
+ $this->pluginDefinition = $plugin_definition;
+ }
+
+ /**
+ * Get a description if the plugin.
+ */
+ public function description() {
+ return $this->t('Handles media image captions');
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function convert(array $chunks) {
+ // @todo Find a better way to explode/implode chunks
+ // @todo Add DI
+ // $ai = \Drupal::service('silverback_ai_import.content');
+ // $input = implode('\n', $chunks);
+ // $out = $ai->request($this->getPrompt($input));
+ return $chunks;
+ }
+
+ private function getPrompt(string $input) {
+ // @todo
+ $prompt = <<` tags)
+
+ 2. Caption Integration:
+ - Move identified captions inside wp:custom/image-with-text blocks
+ - Replace only existing empty paragraph tags (``)
+ - Maintain original caption formatting
+
+ 3. Structure Preservation:
+ - Keep all other content unchanged
+ - Maintain valid Gutenberg block syntax
+ - Preserve all block attributes and properties
+
+ ## Output Format
+ Return complete Gutenberg block structure with integrated captions and nothing else.
+
+ ## Validation
+ - Ensure output maintains valid Gutenberg syntax
+ - Verify all captions are properly nested
+ - Confirm no content loss during transformation
+ EOD;
+
+ return $prompt;
+ }
+}
diff --git a/packages/drupal/silverback_ai/modules/silverback_ai_test/tests/src/Unit/ExampleTest.php b/packages/drupal/silverback_ai/modules/silverback_ai_test/tests/src/Unit/ExampleTest.php
new file mode 100644
index 000000000..5a01a5081
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_ai_test/tests/src/Unit/ExampleTest.php
@@ -0,0 +1,31 @@
+ for further information.
+
+## CONFIGURATION
+
+- Base settings form: `/admin/config/system/silverback/image-ai-settings`.
+
+## SERVICES
+
+### ImageAiUtilities Service
+
+The `ImageAiUtilities` service provides core functionality for AI-powered image processing. It handles:
+
+- Generation of ALT text for images using OpenAI's vision models
+- Processing of image files and media entities
+- Integration with OpenAI's API for image analysis
+- Token usage tracking and logging
diff --git a/packages/drupal/silverback_ai/modules/silverback_image_ai/config/install/silverback_image_ai.settings.yml b/packages/drupal/silverback_ai/modules/silverback_image_ai/config/install/silverback_image_ai.settings.yml
new file mode 100644
index 000000000..011175f09
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_image_ai/config/install/silverback_image_ai.settings.yml
@@ -0,0 +1,7 @@
+open_ai_base_uri: 'https://api.openai.com/v1/'
+open_ai_key: ''
+ai_model: ''
+words_length: 30
+alt_ai_context: 'Silverback is a PHP and Javascript framework to generate decoupled web sites.'
+debug_mode: 0
+alt_disclaimer: 'The alternative text is generated by artificial intelligence. Verify for accuracy before publishing.'
diff --git a/packages/drupal/silverback_ai/modules/silverback_image_ai/config/optional/system.action.media_alt_ai_update_action.yml b/packages/drupal/silverback_ai/modules/silverback_image_ai/config/optional/system.action.media_alt_ai_update_action.yml
new file mode 100644
index 000000000..0fcad06e4
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_image_ai/config/optional/system.action.media_alt_ai_update_action.yml
@@ -0,0 +1,10 @@
+langcode: en
+status: true
+dependencies:
+ module:
+ - media
+id: media_alt_ai_update_action
+label: 'Alt text update (imaged only)'
+type: media
+plugin: entity:alt_ai_update_action:media
+configuration: { }
diff --git a/packages/drupal/silverback_ai/modules/silverback_image_ai/config/schema/silverback_image_ai.schema.yml b/packages/drupal/silverback_ai/modules/silverback_image_ai/config/schema/silverback_image_ai.schema.yml
new file mode 100644
index 000000000..ddd1e7092
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_image_ai/config/schema/silverback_image_ai.schema.yml
@@ -0,0 +1,19 @@
+silverback_image_ai.settings:
+ type: config_object
+ label: 'Silverback Image AI settings'
+ mapping:
+ ai_model:
+ label: 'Model'
+ type: string
+ words_length:
+ label: 'Number of ALT text words to generate'
+ type: integer
+ alt_ai_context:
+ label: 'Context'
+ type: text
+ debug_mode:
+ label: 'Debug mode'
+ type: boolean
+ alt_disclaimer:
+ label: 'Disclaimer text'
+ type: text
diff --git a/packages/drupal/silverback_ai/modules/silverback_image_ai/drush.services.yml b/packages/drupal/silverback_ai/modules/silverback_image_ai/drush.services.yml
new file mode 100644
index 000000000..bbfff79d8
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_image_ai/drush.services.yml
@@ -0,0 +1,6 @@
+services:
+ silverback_image_ai.commands:
+ class: \Drupal\silverback_image_ai\Drush\Commands\SilverbackImageAiCommands
+ arguments: ['@entity_type.manager', '@silverback_image_ai.batch.updater', '@silverback_image_ai.utilities']
+ tags:
+ - { name: drush.command }
diff --git a/packages/drupal/silverback_ai/modules/silverback_image_ai/silverback_image_ai.config_translation.yml b/packages/drupal/silverback_ai/modules/silverback_image_ai/silverback_image_ai.config_translation.yml
new file mode 100644
index 000000000..66521d111
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_image_ai/silverback_image_ai.config_translation.yml
@@ -0,0 +1,5 @@
+silverback_image_ai.config:
+ title: 'Silverback Image AI settings'
+ base_route_name: silverback_image_ai.settings
+ names:
+ - silverback_image_ai.settings
diff --git a/packages/drupal/silverback_ai/modules/silverback_image_ai/silverback_image_ai.info.yml b/packages/drupal/silverback_ai/modules/silverback_image_ai/silverback_image_ai.info.yml
new file mode 100644
index 000000000..447786720
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_image_ai/silverback_image_ai.info.yml
@@ -0,0 +1,7 @@
+name: 'Silverback Alt AI'
+type: module
+description: 'Silverback AI utilities for images'
+package: Silverback
+core_version_requirement: ^10 || ^11
+dependencies:
+ - silverback_ai:silverback_ai
diff --git a/packages/drupal/silverback_ai/modules/silverback_image_ai/silverback_image_ai.install b/packages/drupal/silverback_ai/modules/silverback_image_ai/silverback_image_ai.install
new file mode 100644
index 000000000..5f303f2f0
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_image_ai/silverback_image_ai.install
@@ -0,0 +1,6 @@
+get('entity_type.manager'),
+ $container->get('silverback_image_ai.batch.updater'),
+ $container->get('silverback_image_ai.utilities'),
+ );
+ }
+
+ /**
+ * Command description here.
+ *
+ * @param false[] $options
+ *
+ * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+ * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+ * @throws \Drush\Exceptions\UserAbortException
+ */
+ #[CLI\Command(name: 'silverback-image-ai:alt:generate', aliases: ['slb:alt:g'])]
+ #[CLI\Option(name: 'update-all', description: 'Update all image alt texts. ATTENTION: This will overwrite existing alt texts.')]
+ #[CLI\Usage(name: 'silverback-image-ai:alt:generate', description: 'Generate alt text for media images.')]
+ public function commandName(array $options = [
+ 'update-all' => FALSE,
+ ]) {
+ $media_entities = [];
+ if ($options['update-all']) {
+ $this->io()->warning(dt('ATTENTION: This action will overwrite all existing media image alt texts.'));
+ if ($this->io()->confirm(dt('Are you sure you want to update all existing alt texts?'), FALSE)) {
+ $media_entities = $this->service->getMediaEntitiesToUpdateAll();
+ $this->batch->create($media_entities);
+ }
+ else {
+ throw new UserAbortException();
+ }
+ }
+ else {
+ try {
+ $media_entities = $this->service->getMediaEntitiesToUpdateWithAlt();
+ $this->batch->create($media_entities);
+ }
+ catch (InvalidPluginDefinitionException | PluginNotFoundException $e) {
+ // @todo
+ }
+ }
+
+ $this->logger()->success(dt('@count media images updated.', [
+ '@count' => count($media_entities),
+ ]));
+ }
+
+}
diff --git a/packages/drupal/silverback_ai/modules/silverback_image_ai/src/Form/ImageAiBatchUpdateForm.php b/packages/drupal/silverback_ai/modules/silverback_image_ai/src/Form/ImageAiBatchUpdateForm.php
new file mode 100644
index 000000000..a9d9575b1
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_image_ai/src/Form/ImageAiBatchUpdateForm.php
@@ -0,0 +1,162 @@
+messenger = $messenger;
+ $this->batch = $batch;
+ $this->service = $service;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('messenger'),
+ $container->get('silverback_image_ai.batch.updater'),
+ $container->get('silverback_image_ai.utilities'),
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(array $form, FormStateInterface $form_state): array {
+
+ $form['description'] = [
+ '#markup' => 'This form will run batch processing.
',
+ ];
+
+ $form['batch_container'] = [
+ '#type' => 'details',
+ '#title' => $this->t('Media imaged ALT text batch update'),
+ '#open' => TRUE,
+ ];
+
+ // $form['batch_missing_only']['actions']['#type'] = 'actions';
+ $missing_alt_count = $this->service->getMediaEntitiesToUpdateWithAlt();
+ $media_images_count = $this->service->getMediaEntitiesToUpdateAll();
+
+ $form['batch_container']['info'] = [
+ '#type' => 'markup',
+ '#markup' => $this->t('There are @count/@total media images with missing alt text.', [
+ '@count' => count($missing_alt_count),
+ '@total' => count($media_images_count),
+ ]),
+ ];
+
+ $form['batch_container']['selection'] = [
+ '#type' => 'radios',
+ '#title' => $this->t('Select what to update'),
+ '#default_value' => 1,
+ '#options' => [
+ 1 => $this->t('Update only media images with missing ALT text'),
+ 2 => $this->t('Update all media images'),
+ ],
+ ];
+
+ $form['batch_container']['confirm'] = [
+ '#title' => $this->t('⚠️ I understand that this action will overwrite all existing ALT texts and I want to proceed.'),
+ '#type' => 'checkbox',
+ '#states' => [
+ 'visible' => [
+ [
+ ':input[name="selection"]' => ['value' => 2],
+ ],
+ ],
+ ],
+ ];
+
+ $form['batch_container']['actions']['submit_all'] = [
+ '#type' => 'submit',
+ '#value' => $this->t('Run update process'),
+ '#button_type' => 'primary',
+ '#states' => [
+ 'disabled' => [
+ [
+ ':input[name="confirm"]' => ['checked' => FALSE],
+ ],
+ ],
+ ],
+ ];
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateForm(array &$form, FormStateInterface $form_state): void {
+ // ..
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state): void {
+ // @todo Create a method for this
+ try {
+ $media_entities = $this->service->getMediaEntitiesToUpdateAll();
+ $this->batch->create($media_entities);
+ }
+ catch (InvalidPluginDefinitionException | PluginNotFoundException $e) {
+ // @todo
+ }
+ }
+
+}
diff --git a/packages/drupal/silverback_ai/modules/silverback_image_ai/src/Form/ImageAiSettingsForm.php b/packages/drupal/silverback_ai/modules/silverback_image_ai/src/Form/ImageAiSettingsForm.php
new file mode 100644
index 000000000..ee51793c0
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_image_ai/src/Form/ImageAiSettingsForm.php
@@ -0,0 +1,129 @@
+configFactory->get('silverback_ai.settings')->get('open_ai_api_key');
+
+ // ..
+ if (!$open_ai_api_key) {
+ $url = Url::fromRoute('silverback_ai.ai_settings');
+ \Drupal::messenger()->addWarning($this->t('Open AI API key is missing. Click here to add your key.', [
+ '@link' => $url->toString(),
+ ]));
+ }
+
+ $form['credentials'] = [
+ '#type' => 'details',
+ '#title' => $this->t('Open AI model'),
+ '#open' => TRUE,
+ ];
+
+ // @todo Make this dynamically
+ $form['credentials']['ai_model'] = [
+ '#type' => 'select',
+ '#title' => $this->t('Model'),
+ '#options' => [
+ 'gpt-4o-mini' => 'gpt-4o-mini',
+ 'gpt-4o-mini-2024-07-18' => 'gpt-4o-mini-2024-07-18',
+ ],
+ '#empty_option' => $this->t('- Select model -'),
+ '#description' => $this->t('Leave empty to use the default gpt-4o-mini model.') . '
' .
+ $this->t('Learn more about the models.', [
+ '@href' => 'https://openai.com/index/gpt-4o-mini-advancing-cost-efficient-intelligence',
+ ]),
+ ];
+
+ $form['general'] = [
+ '#type' => 'details',
+ '#title' => $this->t('General settings'),
+ '#open' => TRUE,
+ ];
+
+ $form['general']['debug_mode'] = [
+ '#title' => $this->t('Debug mode'),
+ '#type' => 'checkbox',
+ '#default_value' => $this->configFactory->get('silverback_image_ai.settings')->get('debug_mode') ?? FALSE,
+ ];
+
+ $form['general']['words_length'] = [
+ '#type' => 'number',
+ '#title' => $this->t('Number of ALT text words to generate'),
+ '#description' => $this->t('Define the number of ALT text words to be generated. Should be between 40 and 60 words.'),
+ '#min' => 10,
+ '#max' => 40,
+ '#default_value' => $this->config('silverback_image_ai.settings')->get('words_length') ?? 30,
+ '#field_suffix' => $this->t(' words'),
+ ];
+
+ $form['general']['alt_disclaimer'] = [
+ '#type' => 'textfield',
+ '#title' => $this->t('Disclaimer text'),
+ '#default_value' => $this->config('silverback_image_ai.settings')->get('alt_disclaimer')
+ ?? $this->t('The ALT contents are generated by artificial intelligence. Verify for accuracy before publishing.'),
+ '#description' => $this->t("Define a disclaimer text for the editors. Keep it short."),
+ ];
+
+ $form['general']['alt_ai_context'] = [
+ '#type' => 'textarea',
+ '#title' => $this->t('Context'),
+ '#rows' => 3,
+ '#access' => TRUE,
+ '#default_value' => $this->config('silverback_image_ai.settings')->get('alt_ai_context'),
+ '#description' => $this->t('Optionally, you can use a context to generate your ALT text. Keep it short and precise.'),
+ ];
+
+ return parent::buildForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateForm(array &$form, FormStateInterface $form_state): void {
+ parent::validateForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state): void {
+ $this->config('silverback_image_ai.settings')
+ ->set('ai_model', $form_state->getValue('ai_model'))
+ ->set('debug_mode', $form_state->getValue('debug_mode'))
+ ->set('words_length', intval($form_state->getValue('words_length')))
+ ->set('alt_disclaimer', trim($form_state->getValue('alt_disclaimer')))
+ ->set('alt_ai_context', trim($form_state->getValue('alt_ai_context')))
+ ->save();
+ parent::submitForm($form, $form_state);
+ }
+
+}
diff --git a/packages/drupal/silverback_ai/modules/silverback_image_ai/src/ImageAiUtilities.php b/packages/drupal/silverback_ai/modules/silverback_image_ai/src/ImageAiUtilities.php
new file mode 100644
index 000000000..529d25688
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_image_ai/src/ImageAiUtilities.php
@@ -0,0 +1,398 @@
+getBase64EncodeData($image);
+
+ if (getenv('SILVERBACK_IMAGE_AI_DRY_RUN')) {
+ $response_body = $this->getFakeResponseBody($base_64_data, $langcode);
+ } else {
+ $response_body = $this->sendOpenAiRequest($base_64_data, $langcode);
+ }
+
+ $this->logUsage($response_body, $image);
+
+ if ($this->configFactory->get('silverback_image_ai.settings')->get('debug_mode')) {
+ \Drupal::logger('debug')->debug('' . print_r($response_body, TRUE) . "
");
+ }
+
+ if (isset($response_body['choices'][0]['message']['content'])) {
+ return trim($response_body['choices'][0]['message']['content']);
+ }
+
+ return NULL;
+ }
+
+ /**
+ * Converts an image file to a base64-encoded string.
+ *
+ * This method takes an image file represented by a FileInterface object,
+ * processes it through a specified image style to ensure the desired derivative
+ * is created, and then returns the image data encoded in base64 format,
+ * suitable for embedding in HTML.
+ *
+ * @param \Drupal\file\FileInterface $image
+ * The image file object for which the base64 data needs to be generated.
+ *
+ * @return string
+ * A string containing the base64-encoded image data prefixed with the
+ * appropriate data URI scheme and mime type.
+ *
+ * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+ * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+ *
+ * @todo
+ * Extract the image processing logic to a separate method for improved
+ * code maintainability and readability.
+ */
+ public function getBase64EncodeData(FileInterface $image) {
+ // @todo Extract this to method
+ $image_uri = $image->getFileUri();
+ $image_type = $image->getMimeType();
+ $fileSystem = \Drupal::service('file_system');
+
+ /** @var \Drupal\image\ImageStyleInterface $image_style */
+ $image_style = \Drupal::entityTypeManager()->getStorage('image_style')->load('large');
+
+ // Create image derivatives if they not already exists.
+ if ($image_style) {
+ $derivative_uri = $image_style->buildUri($image_uri);
+ if (!file_exists($derivative_uri)) {
+ $image_style->createDerivative($image_uri, $derivative_uri);
+ }
+ $absolute_path = $fileSystem->realpath($derivative_uri);
+ } else {
+ $absolute_path = $fileSystem->realpath($image_uri);
+ }
+
+ $image_file = file_get_contents($absolute_path);
+ $base_64_image = base64_encode($image_file);
+ return "data:$image_type;base64,$base_64_image";
+ }
+
+ /**
+ * Sends a request to the OpenAI API to generate ALT text for an image.
+ *
+ * This method takes base64-encoded image data and a language code as parameters.
+ * It constructs a payload for the OpenAI API using the specified model and message format,
+ * including an instruction to generate a concise ALT text for the image in the specified language.
+ *
+ * @param string $base_64_data
+ * The base64-encoded data of the image for which to generate ALT text.
+ * @param string $langcode
+ * The language code for the language in which the ALT text should be generated.
+ *
+ * @return array
+ * The decoded JSON response from the OpenAI API containing the generated ALT text.
+ *
+ * @throws \Exception|\GuzzleHttp\Exception\GuzzleException
+ * Thrown if the HTTP request to the OpenAI API fails.
+ */
+ public function sendOpenAiRequest(string $base_64_data, string $langcode) {
+ $language_name = $langcode ? \Drupal::languageManager()->getLanguageName($langcode) : 'English';
+ // @todo Get some of these from settings
+ $model = $this->configFactory->get('silverback_image_ai.settings')->get('ai_model') ?: self::DEFAULT_AI_MODEL;
+ $words = $this->configFactory->get('silverback_image_ai.settings')->get('words_length') ?: self::DEFAULT_WORD_LENGTH;
+
+ $context = $this->configFactory->get('silverback_image_ai.settings')->get('alt_ai_context');
+
+ if (!empty($context)) {
+ $prompt = "Given the following context:\r\n'{$context}' \r\n";
+ $prompt .= "generate a concise and descriptive ALT text for this image. The ALT text should be a single sentence, no more than {$words} words long. The Alt text should be in the {$language_name} language.";
+ } else {
+ $prompt = "Generate a concise and descriptive ALT text for this image. The ALT text should be a single sentence, no more than {$words} words long. The Alt text should be in the {$language_name} language.";
+ }
+
+ $payload = [
+ 'model' => $model,
+ 'messages' => [
+ [
+ 'role' => 'user',
+ 'content' => [
+ [
+ 'type' => 'text',
+ 'text' => $prompt,
+ ],
+ [
+ 'type' => 'image_url',
+ 'image_url' => [
+ "url" => $base_64_data,
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'max_tokens' => 100,
+ ];
+
+ try {
+ $response = $this->openAiHttpClient->post('chat/completions', [
+ 'json' => $payload,
+ ]);
+ } catch (\Exception $e) {
+ throw new \Exception('HTTP request failed: ' . $e->getMessage());
+ }
+
+ $responseBodyContents = $response->getBody()->getContents();
+ return json_decode($responseBodyContents, TRUE, 512, JSON_THROW_ON_ERROR);
+ }
+
+ /**
+ * Number of media entities with the 'image' bundle that are missing alt text.
+ *
+ * @return int
+ * The number of media entities missing alt text.
+ *
+ * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+ * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+ *
+ * @todo Create a db table to store data, query can be slow for large number of entities.
+ */
+ public function getMissingAltEntitiesCount() {
+ $count = 0;
+ // @todo Add DI
+ $media_entities = \Drupal::entityTypeManager()->getStorage('media')->loadByProperties([
+ 'bundle' => 'image',
+ ]);
+ foreach ($media_entities as $media) {
+ /** @var \Drupal\media\Entity\Media $media */
+ foreach ($media->getTranslationLanguages() as $langcode => $translation) {
+ $entity = $media->getTranslation($langcode);
+ if (!$entity->field_media_image->alt) {
+ $count++;
+ }
+ }
+ }
+ return $count;
+ }
+
+ /**
+ * Sets the alt text for the media image field.
+ *
+ * This method updates the alt text of the given media entity's image field.
+ * It saves the changes to the entity unless the 'SILVERBACK_IMAGE_AI_DRY_RUN' environment
+ * variable is set. The method is intended for use with Drupal media entities.
+ *
+ * @param \Drupal\media\Entity\Media $media
+ * The media entity whose image alt text is being set.
+ * @param string $alt_text
+ * The alt text to set for the media image.
+ *
+ * @throws \Drupal\Core\Entity\EntityStorageException
+ */
+ public function setMediaImageAltText(MediaInterface $media, string $alt_text) {
+ /** @var \Drupal\media\Entity\Media $media */
+ $media->field_media_image->alt = $alt_text;
+ if (!getenv('SILVERBACK_IMAGE_AI_DRY_RUN')) {
+ $media->save();
+ }
+ }
+
+ /**
+ * Emulates a fake response. Used for development.
+ */
+ public function getFakeResponseBody(string $base_64_data, string $langcode) {
+ return [
+ "id" => "chatcmpl-AJe6memR1kLukQdK957wAFydW54rK",
+ "object" => "chat.completion",
+ "created" => 1729245772,
+ "model" => "gpt-4o-mini",
+ "choices" => [
+ 0 => [
+ "index" => 0,
+ "message" => [
+ "role" => "assistant",
+ "content" => "A group of three people collaborating around a table with laptops and data displays.",
+ "refusal" => NULL,
+ ],
+ "logprobs" => NULL,
+ "finish_reason" => "stop",
+ ],
+ ],
+ "usage" => [
+ "prompt_tokens" => 25536,
+ "completion_tokens" => 15,
+ "total_tokens" => 25551,
+ "prompt_tokens_details" => [
+ "cached_tokens" => 0,
+ ],
+ "completion_tokens_details" => [
+ "reasoning_tokens" => 0,
+ ],
+ ],
+ "system_fingerprint" => "fp_8552ec53e1",
+ ];
+ }
+
+ /**
+ * Retrieves the total count of media items of type 'image'.
+ *
+ * This function executes a database query to count the distinct media items
+ * where the bundle is 'image' and the media ID (mid) is not null.
+ *
+ * @return int
+ * The total count of image media items.
+ */
+ public function getMediaImagesTotalCount() {
+ $query = \Drupal::database()->select('media', 'm')
+ ->fields('m', ['mid'])
+ ->condition('bundle', 'image')
+ ->isNotNull('mid')
+ ->distinct();
+ return (int) $query->countQuery()->execute()->fetchField();
+ }
+
+ /**
+ * Gets a list of media entities.
+ *
+ * This function loads media entities of the 'image' bundle and iterates over
+ * their translations. It builds and returns an array of entities with language
+ * codes.
+ *
+ * @return array
+ * An array of arrays, each containing:
+ * - entity: The media entity translation.
+ * - langcode: The language code of the translation.
+ *
+ * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+ * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+ */
+ public function getMediaEntitiesToUpdateAll() {
+ $entities = [];
+ $media_entities = $this->entityTypeManager->getStorage('media')->loadByProperties([
+ 'bundle' => 'image',
+ ]);
+ foreach ($media_entities as $media) {
+ /** @var \Drupal\media\Entity\Media $media */
+ foreach ($media->getTranslationLanguages() as $langcode => $translation) {
+ $entity = $media->getTranslation($langcode);
+ $entities[] = [
+ 'entity' => $entity,
+ 'langcode' => $langcode,
+ ];
+ }
+ }
+ return $entities;
+ }
+
+ /**
+ * Gets a list of media entities to update without alt value.
+ *
+ * This function loads media entities of the 'image' bundle and iterates over
+ * their translations. It builds and returns an array of entities with language
+ * codes.
+ *
+ * @return array
+ * An array of arrays, each containing:
+ * - entity: The media entity translation.
+ * - langcode: The language code of the translation.
+ *
+ * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+ * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+ */
+ public function getMediaEntitiesToUpdateWithAlt() {
+ $entities = [];
+ $media_entities = $this->entityTypeManager->getStorage('media')->loadByProperties([
+ 'bundle' => 'image',
+ ]);
+ foreach ($media_entities as $media) {
+ /** @var \Drupal\media\Entity\Media $media */
+ foreach ($media->getTranslationLanguages() as $langcode => $translation) {
+ $entity = $media->getTranslation($langcode);
+ if (!$entity->field_media_image->alt) {
+ $entities[] = [
+ 'entity' => $entity,
+ 'langcode' => $langcode,
+ ];
+ }
+ }
+ }
+ return $entities;
+ }
+
+ /**
+ * Logs the usage of the Silverback Image AI module.
+ *
+ * This method updates the response body with module and entity details and
+ * creates a new usage entry using the Silverback AI Token Usage service.
+ *
+ * @param array $response_body
+ * An associative array that will be enhanced with module and entity information.
+ * @param \Drupal\Core\Entity\EntityInterface|null $entity
+ * The entity for which to log usage details. If provided, its id, type,
+ * and revision id will be added to the response body if the entity is revisionable.
+ *
+ * @throws \Exception
+ */
+ public function logUsage(array $response_body, EntityInterface $entity = NULL) {
+ $response_body['module'] = 'Silverback Image AI';
+
+ if ($entity) {
+ $response_body['entity_id'] = (string) $entity->id();
+ $response_body['entity_type_id'] = (string) $entity->getEntityTypeId();
+ if ($entity->getEntityType()->isRevisionable()) {
+ $response_body['entity_revision_id'] = (string) $entity->getRevisionId();
+ }
+ }
+
+ $this->silverbackAiTokenUsage->createUsageEntry($response_body);
+ }
+}
diff --git a/packages/drupal/silverback_ai/modules/silverback_image_ai/src/ImageAiUtilitiesInterface.php b/packages/drupal/silverback_ai/modules/silverback_image_ai/src/ImageAiUtilitiesInterface.php
new file mode 100644
index 000000000..d6c46863a
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_image_ai/src/ImageAiUtilitiesInterface.php
@@ -0,0 +1,159 @@
+loggerChannel = $loggerFactory->get('silverback_image_ai');
+ }
+
+ /**
+ * Creates a batch operation to process media image updates.
+ *
+ * This method initializes a batch process for updating media images, setting
+ * up the batch operations and conditions for Drush integration if run via CLI.
+ *
+ * @param array $items
+ * An array of items to be processed in the batch. Each item represents
+ * a single media entity requiring updates.
+ *
+ * @return void
+ */
+ public function create(array $items): void {
+
+ $batchBuilder = (new BatchBuilder())
+ ->setTitle($this->t('Running media image updates...'))
+ ->setFinishCallback([self::class, 'finish'])
+ ->setInitMessage('The initialization message (optional)')
+ ->setProgressMessage('Completed @current of @total. See other placeholders.');
+
+ $total = count($items);
+ $count = 0;
+ // Create multiple batch operations based on the $batchSize.
+ foreach ($items as $item) {
+ $batch = [
+ 'item' => $item,
+ 'count' => $count++,
+ 'total' => $total,
+ ];
+ $batchBuilder->addOperation([MediaUpdaterBatch::class, 'process'], [$batch]);
+ }
+
+ batch_set($batchBuilder->toArray());
+ if (function_exists('drush_backend_batch_process') && PHP_SAPI === 'cli') {
+ drush_backend_batch_process();
+ }
+ }
+
+ /**
+ * Batch operation callback.
+ *
+ * @param array $batch
+ * Information about batch (items, size, total, ...).
+ * @param array $context
+ * Batch context.
+ */
+ public static function process(array $batch, array &$context) {
+ // Process elements stored in each batch (operation).
+ $processed = !empty($context['results']) ? count($context['results']) : $batch['count'];
+ $entity = $batch['item']['entity'];
+
+ $service = \Drupal::service('silverback_image_ai.utilities');
+ $alt_text = '-';
+ $file = $entity->field_media_image->entity;
+ if ($file) {
+ $alt_text = $service->generateImageAlt($file, $batch['item']['langcode']);
+ $service->setMediaImageAltText($entity, $alt_text);
+ }
+
+ $context['message'] = t('Processing media item @processed/@total with id: @id (@langcode) ', [
+ '@processed' => $processed,
+ '@total' => $batch['total'],
+ '@id' => $entity->id(),
+ '@langcode' => $batch['item']['langcode'],
+ ]);
+
+ sleep(1);
+ }
+
+ /**
+ * Finish batch.
+ *
+ * This function is a static function to avoid serializing the ConfigSync
+ * object unnecessarily.
+ *
+ * @param bool $success
+ * Indicate that the batch API tasks were all completed successfully.
+ * @param array $results
+ * An array of all the results that were updated in update_do_one().
+ * @param array $operations
+ * A list of the operations that had not been completed by the batch API.
+ */
+ public static function finish(bool $success, array $results, array $operations) {
+ $messenger = \Drupal::messenger();
+ if ($success) {
+ $messenger->addStatus(t('Items processed successfully.'));
+ }
+ else {
+ // An error occurred.
+ // $operations contains the operations that remained unprocessed.
+ $error_operation = reset($operations);
+ $message = t('An error occurred while processing %error_operation with arguments: @arguments',
+ ['%error_operation' => $error_operation[0], '@arguments' => print_r($error_operation[1], TRUE)]);
+ $messenger->addError($message);
+ }
+ }
+
+}
diff --git a/packages/drupal/silverback_ai/modules/silverback_image_ai/src/MediaUpdaterBatchInterface.php b/packages/drupal/silverback_ai/modules/silverback_image_ai/src/MediaUpdaterBatchInterface.php
new file mode 100644
index 000000000..b33cf4fe6
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_image_ai/src/MediaUpdaterBatchInterface.php
@@ -0,0 +1,53 @@
+bundle() == self::BUNDLE_IMAGE) {
+ $langcode = $entity->langcode->value ?? self::DEFAULT_LANGCODE;
+ $service = \Drupal::service('silverback_image_ai.utilities');
+ $file = $entity->field_media_image->entity;
+ if ($file) {
+ $alt_text = $service->generateImageAlt($file, $langcode);
+ if (!empty($alt_text)) {
+ $service->setMediaImageAltText($entity, $alt_text);
+ }
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
+ $result = AccessResult::allowedIfHasPermission($account, 'create media');
+ return $return_as_object ? $result : $result->isAllowed();
+ }
+
+}
diff --git a/packages/drupal/silverback_ai/modules/silverback_image_ai/src/Plugin/Field/FieldWidget/FocalPointImageWidgetAi.php b/packages/drupal/silverback_ai/modules/silverback_image_ai/src/Plugin/Field/FieldWidget/FocalPointImageWidgetAi.php
new file mode 100644
index 000000000..e21350d3a
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_image_ai/src/Plugin/Field/FieldWidget/FocalPointImageWidgetAi.php
@@ -0,0 +1,360 @@
+ 'throbber',
+ 'preview_image_style' => 'thumbnail',
+ 'preview_link' => TRUE,
+ 'offsets' => '50,50',
+ ] + parent::defaultSettings();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function settingsForm(array $form, FormStateInterface $form_state) {
+ $form = parent::settingsForm($form, $form_state);
+
+ // We need a preview image for this widget.
+ $form['preview_image_style']['#required'] = TRUE;
+ unset($form['preview_image_style']['#empty_option']);
+ // @todo Implement https://www.drupal.org/node/2872960
+ // The preview image should not be generated using a focal point effect
+ // and should maintain the aspect ratio of the original image.
+ // phpcs:disable
+ $form['preview_image_style']['#description'] = t(
+ // phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString
+ $form['preview_image_style']['#description']->getUntranslatedString() . "
Do not choose an image style that alters the aspect ratio of the original image nor an image style that uses a focal point effect.",
+ $form['preview_image_style']['#description']->getArguments(),
+ $form['preview_image_style']['#description']->getOptions()
+ );
+ // phpcs:enable
+
+ $form['preview_link'] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t('Display preview link'),
+ '#default_value' => $this->getSetting('preview_link'),
+ '#weight' => 30,
+ ];
+
+ $form['offsets'] = [
+ '#type' => 'textfield',
+ '#title' => $this->t('Default focal point value'),
+ '#default_value' => $this->getSetting('offsets'),
+ '#description' => $this->t('Specify the default focal point of this widget in the form "leftoffset,topoffset" where offsets are in percentages. Ex: 25,75.'),
+ '#size' => 7,
+ '#maxlength' => 7,
+ '#element_validate' => [[$this, 'validateFocalPointWidget']],
+ '#required' => TRUE,
+ '#weight' => 35,
+ ];
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function settingsSummary() {
+ $summary = parent::settingsSummary();
+
+ $status = $this->getSetting('preview_link') ? $this->t('Yes') : $this->t('No');
+ $summary[] = $this->t('Preview link: @status', ['@status' => $status]);
+
+ $offsets = $this->getSetting('offsets');
+ $summary[] = $this->t('Default focal point: @offsets', ['@offsets' => $offsets]);
+
+ return $summary;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
+ $element = parent::formElement($items, $delta, $element, $form, $form_state);
+ $element['#focal_point'] = [
+ 'preview_link' => $this->getSetting('preview_link'),
+ 'offsets' => $this->getSetting('offsets'),
+ ];
+
+ return $element;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * Processes an image_focal_point field Widget.
+ *
+ * Expands the image_focal_point Widget to include the focal_point field.
+ * This method is assigned as a #process callback in formElement() method.
+ *
+ * @todo Implement https://www.drupal.org/node/2657592
+ * Convert focal point selector tool into a standalone form element.
+ * @todo Implement https://www.drupal.org/node/2848511
+ * Focal Point offsets not accessible by keyboard.
+ */
+ public static function process($element, FormStateInterface $form_state, $form) {
+ $element = parent::process($element, $form_state, $form);
+
+ $item = $element['#value'];
+ $item['fids'] = $element['fids']['#value'];
+ $element_selectors = [
+ 'focal_point' => 'focal-point-' . implode('-', $element['#parents']),
+ ];
+
+ $default_focal_point_value = $item['focal_point'] ?? $element['#focal_point']['offsets'];
+
+ // Override the default Image Widget template when using the Media Library
+ // module so we can use the image field's preview rather than the preview
+ // provided by Media Library.
+ if ($form['#form_id'] == 'media_library_upload_form' || $form['#form_id'] == 'media_library_add_form') {
+ $element['#theme'] = 'focal_point_media_library_image_widget';
+ unset($form['media'][0]['preview']);
+ }
+
+ // Add the focal point indicator to preview.
+ if (isset($element['preview'])) {
+ $preview = [
+ 'indicator' => self::createFocalPointIndicator($element['#delta'], $element_selectors),
+ 'thumbnail' => $element['preview'],
+ ];
+
+ // Even for image fields with a cardinality higher than 1 the correct fid
+ // can always be found in $item['fids'][0].
+ $fid = $item['fids'][0] ?? '';
+ if ($element['#focal_point']['preview_link'] && !empty($fid)) {
+ $preview['preview_link'] = self::createPreviewLink($fid, $element['#field_name'], $element_selectors, $default_focal_point_value);
+ }
+
+ // Use the existing preview weight value so that the focal point indicator
+ // and thumbnail appear in the correct order.
+ $preview['#weight'] = $element['preview']['#weight'] ?? 0;
+ unset($preview['thumbnail']['#weight']);
+
+ $element['preview'] = $preview;
+ }
+
+ // Add the focal point field.
+ $element['focal_point'] = self::createFocalPointField($element['#field_name'], $element_selectors, $default_focal_point_value);
+
+ return $element;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * Form API callback. Retrieves the value for the file_generic field element.
+ *
+ * This method is assigned as a #value_callback in formElement() method.
+ */
+ public static function value($element, $input, FormStateInterface $form_state) {
+ $return = parent::value($element, $input, $form_state);
+
+ // When an element is loaded, focal_point needs to be set. During a form
+ // submission the value will already be there.
+ if (isset($return['target_id']) && !isset($return['focal_point'])) {
+ /** @var \Drupal\file\FileInterface $file */
+ $file = \Drupal::service('entity_type.manager')
+ ->getStorage('file')
+ ->load($return['target_id']);
+ if ($file) {
+ $crop_type = \Drupal::config('focal_point.settings')->get('crop_type');
+ $crop = Crop::findCrop($file->getFileUri(), $crop_type);
+ if ($crop) {
+ $anchor = \Drupal::service('focal_point.manager')
+ ->absoluteToRelative($crop->x->value, $crop->y->value, $return['width'], $return['height']);
+ $return['focal_point'] = "{$anchor['x']},{$anchor['y']}";
+ }
+ }
+ else {
+ \Drupal::logger('focal_point')->notice("Attempted to get a focal point value for an invalid or temporary file.");
+ $return['focal_point'] = $element['#focal_point']['offsets'];
+ }
+ }
+ return $return;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * Validation Callback; Focal Point process field.
+ */
+ public static function validateFocalPoint($element, FormStateInterface $form_state) {
+ if (empty($element['#value']) || (FALSE === \Drupal::service('focal_point.manager')->validateFocalPoint($element['#value']))) {
+ $replacements = ['@title' => strtolower($element['#title'])];
+ $form_state->setError($element, new TranslatableMarkup('The @title field should be in the form "leftoffset,topoffset" where offsets are in percentages. Ex: 25,75.', $replacements));
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * Validation Callback; Focal Point widget setting.
+ */
+ public function validateFocalPointWidget(array &$element, FormStateInterface $form_state) {
+ static::validateFocalPoint($element, $form_state);
+ }
+
+ /**
+ * Create and return a token to use for accessing the preview page.
+ *
+ * @return string
+ * A valid token.
+ *
+ * @codeCoverageIgnore
+ */
+ public static function getPreviewToken() {
+ return \Drupal::csrfToken()->get(self::PREVIEW_TOKEN_NAME);
+ }
+
+ /**
+ * Validate a preview token.
+ *
+ * @param string $token
+ * A drupal generated token.
+ *
+ * @return bool
+ * True if the token is valid.
+ *
+ * @codeCoverageIgnore
+ */
+ public static function validatePreviewToken($token) {
+ return \Drupal::csrfToken()->validate($token, self::PREVIEW_TOKEN_NAME);
+ }
+
+ /**
+ * Create the focal point form element.
+ *
+ * @param string $field_name
+ * The name of the field element for the image field.
+ * @param array $element_selectors
+ * The element selectors to ultimately be used by javascript.
+ * @param string $default_focal_point_value
+ * The default focal point value in the form x,y.
+ *
+ * @return array
+ * The preview link form element.
+ */
+ private static function createFocalPointField($field_name, array $element_selectors, $default_focal_point_value) {
+ $field = [
+ '#type' => 'textfield',
+ '#title' => new TranslatableMarkup('Focal point'),
+ '#description' => new TranslatableMarkup('Specify the focus of this image in the form "leftoffset,topoffset" where offsets are in percents. Ex: 25,75'),
+ '#default_value' => $default_focal_point_value,
+ '#element_validate' => [[static::class, 'validateFocalPoint']],
+ '#attributes' => [
+ 'class' => ['focal-point', $element_selectors['focal_point']],
+ 'data-selector' => $element_selectors['focal_point'],
+ 'data-field-name' => $field_name,
+ ],
+ '#wrapper_attributes' => [
+ 'class' => ['focal-point-wrapper'],
+ ],
+ '#attached' => [
+ 'library' => ['focal_point/drupal.focal_point'],
+ ],
+ ];
+
+ return $field;
+ }
+
+ /**
+ * Create the focal point form element.
+ *
+ * @param int $delta
+ * The delta of the image field widget.
+ * @param array $element_selectors
+ * The element selectors to ultimately be used by javascript.
+ *
+ * @return array
+ * The focal point field form element.
+ */
+ private static function createFocalPointIndicator($delta, array $element_selectors) {
+ $indicator = [
+ '#type' => 'html_tag',
+ '#tag' => 'div',
+ '#attributes' => [
+ 'class' => ['focal-point-indicator'],
+ 'data-selector' => $element_selectors['focal_point'],
+ 'data-delta' => $delta,
+ ],
+ ];
+
+ return $indicator;
+ }
+
+ /**
+ * Create the preview link form element.
+ *
+ * @param int $fid
+ * The fid of the image file.
+ * @param string $field_name
+ * The name of the field element for the image field.
+ * @param array $element_selectors
+ * The element selectors to ultimately be used by javascript.
+ * @param string $default_focal_point_value
+ * The default focal point value in the form x,y.
+ *
+ * @return array
+ * The preview link form element.
+ */
+ private static function createPreviewLink($fid, $field_name, array $element_selectors, $default_focal_point_value) {
+ // Replace comma (,) with an x to make javascript handling easier.
+ $preview_focal_point_value = str_replace(',', 'x', $default_focal_point_value);
+
+ // Create a token to be used during an access check on the preview page.
+ $token = self::getPreviewToken();
+
+ $preview_link = [
+ '#type' => 'link',
+ '#title' => new TranslatableMarkup('Preview'),
+ '#url' => new Url('focal_point.preview',
+ [
+ 'fid' => $fid,
+ 'focal_point_value' => $preview_focal_point_value,
+ ],
+ [
+ 'query' => ['focal_point_token' => $token],
+ ]),
+ '#attached' => [
+ 'library' => ['core/drupal.dialog.ajax'],
+ ],
+ '#attributes' => [
+ 'class' => ['focal-point-preview-link', 'use-ajax'],
+ 'data-selector' => $element_selectors['focal_point'],
+ 'data-field-name' => $field_name,
+ 'data-dialog-type' => 'modal',
+ 'target' => '_blank',
+ ],
+ ];
+
+ return $preview_link;
+ }
+
+}
diff --git a/packages/drupal/silverback_ai/modules/silverback_image_ai/src/Plugin/Field/FieldWidget/ImageWidgetAi.php b/packages/drupal/silverback_ai/modules/silverback_image_ai/src/Plugin/Field/FieldWidget/ImageWidgetAi.php
new file mode 100644
index 000000000..27c39e47a
--- /dev/null
+++ b/packages/drupal/silverback_ai/modules/silverback_image_ai/src/Plugin/Field/FieldWidget/ImageWidgetAi.php
@@ -0,0 +1,454 @@
+imageFactory = $image_factory ?: \Drupal::service('image.factory');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function defaultSettings() {
+ return [
+ 'progress_indicator' => 'throbber',
+ 'preview_image_style' => 'thumbnail',
+ ] + parent::defaultSettings();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function settingsForm(array $form, FormStateInterface $form_state) {
+ $element = parent::settingsForm($form, $form_state);
+
+ $element['preview_image_style'] = [
+ '#title' => $this->t('Preview image style'),
+ '#type' => 'select',
+ '#options' => image_style_options(FALSE),
+ '#empty_option' => '<' . $this->t('no preview') . '>',
+ '#default_value' => $this->getSetting('preview_image_style'),
+ '#description' => $this->t('The preview image will be shown while editing the content.'),
+ '#weight' => 15,
+ ];
+
+ return $element;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function settingsSummary() {
+ $summary = parent::settingsSummary();
+
+ $image_styles = image_style_options(FALSE);
+ // Unset possible 'No defined styles' option.
+ unset($image_styles['']);
+ // Styles could be lost because of enabled/disabled modules that defines
+ // their styles in code.
+ $image_style_setting = $this->getSetting('preview_image_style');
+ if (isset($image_styles[$image_style_setting])) {
+ $preview_image_style = $this->t('Preview image style: @style', ['@style' => $image_styles[$image_style_setting]]);
+ }
+ else {
+ $preview_image_style = $this->t('No preview');
+ }
+
+ array_unshift($summary, $preview_image_style);
+
+ return $summary;
+ }
+
+ /**
+ * Overrides \Drupal\file\Plugin\Field\FieldWidget\FileWidget::formMultipleElements().
+ *
+ * Special handling for draggable multiple widgets and 'add more' button.
+ */
+ protected function formMultipleElements(FieldItemListInterface $items, array &$form, FormStateInterface $form_state) {
+ $elements = parent::formMultipleElements($items, $form, $form_state);
+
+ $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
+ $file_upload_help = [
+ '#theme' => 'file_upload_help',
+ '#description' => '',
+ '#upload_validators' => $elements[0]['#upload_validators'],
+ '#cardinality' => $cardinality,
+ ];
+ if ($cardinality == 1) {
+ // If there's only one field, return it as delta 0.
+ if (empty($elements[0]['#default_value']['fids'])) {
+ $file_upload_help['#description'] = $this->getFilteredDescription();
+ $elements[0]['#description'] = \Drupal::service('renderer')->renderPlain($file_upload_help);
+ }
+ }
+ else {
+ $elements['#file_upload_description'] = $file_upload_help;
+ }
+
+ return $elements;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
+ $element = parent::formElement($items, $delta, $element, $form, $form_state);
+
+ $field_settings = $this->getFieldSettings();
+
+ // Add image validation.
+ $element['#upload_validators']['FileIsImage'] = [];
+
+ // Add upload dimensions validation.
+ if ($field_settings['max_resolution'] || $field_settings['min_resolution']) {
+ $element['#upload_validators']['FileImageDimensions'] = [
+ 'maxDimensions' => $field_settings['max_resolution'],
+ 'minDimensions' => $field_settings['min_resolution'],
+ ];
+ }
+
+ $extensions = $field_settings['file_extensions'];
+ $supported_extensions = $this->imageFactory->getSupportedExtensions();
+
+ // If using custom extension validation, ensure that the extensions are
+ // supported by the current image toolkit. Otherwise, validate against all
+ // toolkit supported extensions.
+ $extensions = !empty($extensions) ? array_intersect(explode(' ', $extensions), $supported_extensions) : $supported_extensions;
+ $element['#upload_validators']['FileExtension']['extensions'] = implode(' ', $extensions);
+
+ // Add mobile device image capture acceptance.
+ $element['#accept'] = 'image/*';
+
+ // Add properties needed by process() method.
+ $element['#preview_image_style'] = $this->getSetting('preview_image_style');
+ $element['#title_field'] = $field_settings['title_field'];
+ $element['#title_field_required'] = $field_settings['title_field_required'];
+ $element['#alt_field'] = $field_settings['alt_field'];
+ $element['#alt_field_required'] = $field_settings['alt_field_required'];
+ // Default image.
+ $default_image = $field_settings['default_image'];
+ if (empty($default_image['uuid'])) {
+ $default_image = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('default_image');
+ }
+ // Convert the stored UUID into a file ID.
+ if (!empty($default_image['uuid']) && $entity = \Drupal::service('entity.repository')->loadEntityByUuid('file', $default_image['uuid'])) {
+ $default_image['fid'] = $entity->id();
+ }
+ $element['#default_image'] = !empty($default_image['fid']) ? $default_image : [];
+ return $element;
+ }
+
+ /**
+ * Form API callback: Processes an image_image field element.
+ *
+ * Expands the image_image type to include the alt and title fields.
+ *
+ * This method is assigned as a #process callback in formElement() method.
+ */
+ public static function process($element, FormStateInterface $form_state, $form) {
+ $item = $element['#value'];
+
+ $item['fids'] = $element['fids']['#value'];
+
+ $element['#theme'] = 'image_widget';
+ // Add the image preview.
+ if (!empty($element['#files']) && $element['#preview_image_style']) {
+ $file = reset($element['#files']);
+ $variables = [
+ 'style_name' => $element['#preview_image_style'],
+ 'uri' => $file->getFileUri(),
+ ];
+
+ $dimension_key = $variables['uri'] . '.image_preview_dimensions';
+ // Determine image dimensions.
+ if (isset($element['#value']['width']) && isset($element['#value']['height'])) {
+ $variables['width'] = $element['#value']['width'];
+ $variables['height'] = $element['#value']['height'];
+ }
+ elseif ($form_state->has($dimension_key)) {
+ $variables += $form_state->get($dimension_key);
+ }
+ else {
+ $image = \Drupal::service('image.factory')->get($file->getFileUri());
+ if ($image->isValid()) {
+ $variables['width'] = $image->getWidth();
+ $variables['height'] = $image->getHeight();
+ }
+ else {
+ $variables['width'] = $variables['height'] = NULL;
+ }
+ }
+
+ $element['ai_container'] = [
+ '#type' => 'container',
+ '#attributes' => [
+ 'style' => ['text-align: right'],
+ ],
+ ];
+
+ $element['ai_container']['alt_ai_generate'] = [
+ '#type' => 'submit',
+ '#value' => new TranslatableMarkup('Re-generate ALT text'),
+ '#weight' => -12,
+ '#attributes' => [
+ 'class' => ['button--extrasmall', 'button', 'js-form-submit', 'form-submit'],
+ 'style' => ['padding: 2px 12px'],
+ ],
+ '#ajax' => [
+ 'callback' => static::class . '::generateAjaxCallback',
+ 'event' => 'click',
+ 'wrapper_id' => $element['#attributes']['data-drupal-selector'] . '-alt',
+ 'fids' => $item['fids'],
+ 'langcode' => $form_state->get('langcode') ?? ImageWidgetAi::getLangcode($form, $form_state),
+ 'progress' => [
+ 'type' => 'throbber',
+ 'message' => t('Generating alt text...'),
+ ],
+ ],
+ ];
+
+ // @todo Add DI
+ $disclaimer = "" . \Drupal::config('silverback_image_ai.settings')->get('alt_disclaimer') . "
";
+ $element['ai_container']['disclaimer'] = [
+ '#type' => 'item',
+ '#markup' => Markup::create($disclaimer),
+ ];
+
+ $element['preview'] = [
+ '#weight' => -10,
+ '#theme' => 'image_style',
+ '#width' => $variables['width'],
+ '#height' => $variables['height'],
+ '#style_name' => $variables['style_name'],
+ '#uri' => $variables['uri'],
+ ];
+
+ // Store the dimensions in the form so the file doesn't have to be
+ // accessed again. This is important for remote files.
+ $form_state->set($dimension_key, ['width' => $variables['width'], 'height' => $variables['height']]);
+
+ // [AI utilities]
+ if (!isset($item['alt'])) {
+ $langcode = ImageWidgetAi::getLangcode($form, $form_state);
+ $service = \Drupal::service('silverback_image_ai.utilities');
+ $item['alt'] = $service->generateImageAlt($file, $langcode ?? ImageWidgetAi::DEFAULT_LANGCODE);
+ }
+ // [end AI utilities]
+ }
+ elseif (!empty($element['#default_image'])) {
+ $default_image = $element['#default_image'];
+ $file = File::load($default_image['fid']);
+ if (!empty($file)) {
+ $element['preview'] = [
+ '#weight' => -10,
+ '#theme' => 'image_style',
+ '#width' => $default_image['width'],
+ '#height' => $default_image['height'],
+ '#style_name' => $element['#preview_image_style'],
+ '#uri' => $file->getFileUri(),
+ ];
+ }
+ }
+
+ $element['alt'] = [
+ '#title' => new TranslatableMarkup('Alternative text'),
+ '#type' => 'textfield',
+ '#default_value' => $item['alt'] ?? '',
+ '#description' => new TranslatableMarkup('Short description of the image used by screen readers and displayed when the image is not loaded. This is important for accessibility and SEO.'),
+ // @see https://www.drupal.org/node/465106#alt-text
+ '#maxlength' => 512,
+ '#weight' => -12,
+ '#access' => (bool) $item['fids'] && $element['#alt_field'],
+ '#required' => $element['#alt_field_required'],
+ '#element_validate' => $element['#alt_field_required'] == 1 ? [[static::class, 'validateRequiredFields']] : [],
+ '#attributes' => [
+ 'id' => [$element['#attributes']['data-drupal-selector'] . '-alt'],
+ ],
+ ];
+
+ $element['title'] = [
+ '#type' => 'textfield',
+ '#title' => new TranslatableMarkup('Title'),
+ '#default_value' => $item['title'] ?? '',
+ '#description' => new TranslatableMarkup('The title is used as a tool tip when the user hovers the mouse over the image.'),
+ '#maxlength' => 1024,
+ '#weight' => -11,
+ '#access' => (bool) $item['fids'] && $element['#title_field'],
+ '#required' => $element['#title_field_required'],
+ '#element_validate' => $element['#title_field_required'] == 1 ? [[static::class, 'validateRequiredFields']] : [],
+ ];
+ return parent::process($element, $form_state, $form);
+ }
+
+ /**
+ * Validate callback for alt and title field, if the user wants them required.
+ *
+ * This is separated in a validate function instead of a #required flag to
+ * avoid being validated on the process callback.
+ */
+ public static function validateRequiredFields($element, FormStateInterface $form_state) {
+ // Only do validation if the function is triggered from other places than
+ // the image process form.
+ $triggering_element = $form_state->getTriggeringElement();
+ if (!empty($triggering_element['#submit']) && in_array('file_managed_file_submit', $triggering_element['#submit'], TRUE)) {
+ $form_state->setLimitValidationErrors([]);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function calculateDependencies() {
+ $dependencies = parent::calculateDependencies();
+ $style_id = $this->getSetting('preview_image_style');
+ /** @var \Drupal\image\ImageStyleInterface $style */
+ if ($style_id && $style = ImageStyle::load($style_id)) {
+ // If this widget uses a valid image style to display the preview of the
+ // uploaded image, add that image style configuration entity as dependency
+ // of this widget.
+ $dependencies[$style->getConfigDependencyKey()][] = $style->getConfigDependencyName();
+ }
+ return $dependencies;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function onDependencyRemoval(array $dependencies) {
+ $changed = parent::onDependencyRemoval($dependencies);
+ $style_id = $this->getSetting('preview_image_style');
+ /** @var \Drupal\image\ImageStyleInterface $style */
+ if ($style_id && $style = ImageStyle::load($style_id)) {
+ if (!empty($dependencies[$style->getConfigDependencyKey()][$style->getConfigDependencyName()])) {
+ /** @var \Drupal\image\ImageStyleStorageInterface $storage */
+ $storage = \Drupal::entityTypeManager()->getStorage($style->getEntityTypeId());
+ $replacement_id = $storage->getReplacementId($style_id);
+ // If a valid replacement has been provided in the storage, replace the
+ // preview image style with the replacement.
+ if ($replacement_id && ImageStyle::load($replacement_id)) {
+ $this->setSetting('preview_image_style', $replacement_id);
+ }
+ // If there's no replacement or the replacement is invalid, disable the
+ // image preview.
+ else {
+ $this->setSetting('preview_image_style', '');
+ }
+ // Signal that the formatter plugin settings were updated.
+ $changed = TRUE;
+ }
+ }
+ return $changed;
+ }
+
+ /**
+ * The textbox with the selected text.
+ */
+ public static function generateAjaxCallback(array &$form, FormStateInterface $form_state) {
+ $triggering_element = $form_state->getTriggeringElement();
+ $wrapper_id = $triggering_element['#ajax']['wrapper_id'];
+ $fids = $triggering_element['#ajax']['fids'];
+ $langcode = $triggering_element['#ajax']['langcode'] ?? ImageWidgetAi::DEFAULT_LANGCODE;
+ // @todo get the file
+ $fid = reset($fids);
+ $file = NULL;
+ // $url = NULL;
+ if ($fid) {
+ $file = File::load($fid);
+ }
+ $response = new AjaxResponse();
+ if ($file) {
+ $service = \Drupal::service('silverback_image_ai.utilities');
+ $alt_text = $service->generateImageAlt($file, $langcode);
+ if ($alt_text) {
+ $response->addCommand(new InvokeCommand('#' . $wrapper_id, 'val', [$alt_text]));
+ }
+ }
+ return $response;
+ }
+
+ /**
+ *
+ */
+ public static function getLangcode(array &$form, FormStateInterface $form_state) {
+ // @todo Add DI
+ $langcode = ImageWidgetAi::DEFAULT_LANGCODE;
+
+ $input = $form_state->getUserInput();
+ $langcode = is_array($input['langcode']) ? reset($input['langcode']) : [];
+ $langcode = $langcode['value'] ?? NULL;
+ if (!empty($langcode)) {
+ $language_codes = \Drupal::languageManager()->getLanguages();
+ // Make sure the selected langcode exists and it is a real language.
+ if (empty($language_codes[$langcode])) {
+ $langcode = ImageWidgetAi::DEFAULT_LANGCODE;
+ }
+ }
+
+ if (!$langcode) {
+ // Try to fetch from entity language.
+ if ($prefixes = \Drupal::config('language.negotiation')->get('url.prefixes')) {
+ $language = \Drupal::languageManager()->getCurrentLanguage()->getId();
+ $langcode = $prefixes[$language] ?? NULL;
+ }
+ }
+
+ return $langcode;
+ }
+
+}
diff --git a/packages/drupal/silverback_ai/silverback_ai.info.yml b/packages/drupal/silverback_ai/silverback_ai.info.yml
new file mode 100644
index 000000000..e08d93929
--- /dev/null
+++ b/packages/drupal/silverback_ai/silverback_ai.info.yml
@@ -0,0 +1,7 @@
+name: 'Silverback AI'
+type: module
+description: 'Silverback AI base module'
+package: Silverback
+core_version_requirement: ^10 || ^11
+dependencies:
+ - webform:webform
diff --git a/packages/drupal/silverback_ai/silverback_ai.install b/packages/drupal/silverback_ai/silverback_ai.install
new file mode 100644
index 000000000..8f927c10c
--- /dev/null
+++ b/packages/drupal/silverback_ai/silverback_ai.install
@@ -0,0 +1,127 @@
+schema();
+ if ($db_schema->tableExists('silverback_ai_usage')) {
+ $db_schema->dropTable('silverback_ai_usage');
+ }
+
+ $schema['silverback_ai_usage'] = [
+ 'description' => 'Usage for the Silverback AI module.',
+ 'fields' => [
+ 'id' => [
+ 'type' => 'serial',
+ 'not null' => TRUE,
+ 'description' => 'Primary Key.',
+ ],
+ 'uid' => [
+ 'description' => 'Foreign key to {users}.uid; uniquely identifies a Drupal user executed the ai fetch action.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ],
+ 'timestamp' => [
+ 'description' => 'Date/time when the ai request, as Unix timestamp.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ],
+ 'langcode' => [
+ 'description' => 'The language of this request.',
+ 'type' => 'varchar_ascii',
+ 'length' => 12,
+ 'not null' => TRUE,
+ 'default' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
+ ],
+ 'target_entity_type_id' => [
+ 'type' => 'varchar_ascii',
+ 'length' => EntityTypeInterface::ID_MAX_LENGTH,
+ 'not null' => FALSE,
+ 'default' => '',
+ 'description' => 'The ID of the associated entity type.',
+ ],
+ 'target_entity_id' => [
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => FALSE,
+ 'description' => 'The ID of the associated entity.',
+ ],
+ 'target_entity_revision_id' => [
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => FALSE,
+ 'description' => 'The revision ID of the associated entity.',
+ ],
+ 'tokens_in' => [
+ 'description' => 'The total number of input tokens.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'big',
+ ],
+ 'tokens_out' => [
+ 'description' => 'The total number of output tokens.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'big',
+ ],
+ 'total_count' => [
+ 'description' => 'The total number of tokens used.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'big',
+ ],
+ 'provider' => [
+ 'type' => 'varchar_ascii',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'The AI provider.',
+ ],
+ 'model' => [
+ 'type' => 'varchar_ascii',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'The model used.',
+ ],
+ 'module' => [
+ 'type' => 'varchar_ascii',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'The module used.',
+ ],
+ 'response' => [
+ 'type' => 'text',
+ 'not null' => FALSE,
+ 'size' => 'big',
+ 'description' => 'The response from the AI provider.',
+ ],
+ ],
+ 'primary key' => ['id'],
+ 'indexes' => [
+ 'uid' => ['uid'],
+ 'timestamp' => ['timestamp'],
+ ],
+ ];
+
+ return $schema;
+}
diff --git a/packages/drupal/silverback_ai/silverback_ai.links.menu.yml b/packages/drupal/silverback_ai/silverback_ai.links.menu.yml
new file mode 100644
index 000000000..5e424470d
--- /dev/null
+++ b/packages/drupal/silverback_ai/silverback_ai.links.menu.yml
@@ -0,0 +1,17 @@
+silverback.ai.reports_usage:
+ title: 'Silverback AI usage'
+ parent: system.admin_reports
+ description: 'Overview of usage of Silverback AI plugins.'
+ route_name: silverback_ai.ai_usage
+silverback_ai.admin_config_ai:
+ title: Silverback AI
+ parent: system.admin_config
+ description: 'Silverback AI settings.'
+ route_name: system.admin_config
+ weight: 0
+silverback_ai.ai_settings:
+ title: Silverback AI settings
+ description: Settings for the Silverback AI module.
+ parent: silverback_ai.admin_config_ai
+ route_name: silverback_ai.ai_settings
+ weight: 0
diff --git a/packages/drupal/silverback_ai/silverback_ai.module b/packages/drupal/silverback_ai/silverback_ai.module
new file mode 100644
index 000000000..307ac1ba2
--- /dev/null
+++ b/packages/drupal/silverback_ai/silverback_ai.module
@@ -0,0 +1,28 @@
+' . t('About') . '';
+ $output .= '' . t('..');
+ $output .= '
' . t('Uses') . '
';
+ $output .= '';
+ $output .= '- ' . t('Monitoring tokens usage') . '
';
+ $output .= '
';
+ return $output;
+
+ case 'silverback_ai.overview':
+ return '' . t('The Silverback AI module provides ...') . '
';
+ }
+}
diff --git a/packages/drupal/silverback_ai/silverback_ai.permissions.yml b/packages/drupal/silverback_ai/silverback_ai.permissions.yml
new file mode 100644
index 000000000..d5d99df0c
--- /dev/null
+++ b/packages/drupal/silverback_ai/silverback_ai.permissions.yml
@@ -0,0 +1,3 @@
+access token usage:
+ title: 'Access token usage'
+ description: 'Allows a user to access the site AI services token usage.'
diff --git a/packages/drupal/silverback_ai/silverback_ai.routing.yml b/packages/drupal/silverback_ai/silverback_ai.routing.yml
new file mode 100644
index 000000000..9c13da26c
--- /dev/null
+++ b/packages/drupal/silverback_ai/silverback_ai.routing.yml
@@ -0,0 +1,23 @@
+silverback_ai.ai_usage:
+ path: '/admin/reports/silverback-ai-usage'
+ defaults:
+ _title: 'Silverback AI usage'
+ _controller: '\Drupal\silverback_ai\Controller\AiUsageController'
+ requirements:
+ _permission: 'access token usage'
+
+silverback_ai.ai_usage.details:
+ path: '/admin/reports/silverback-ai-usage/{record}/details'
+ defaults:
+ _title: 'Silverback AI usage details'
+ _controller: '\Drupal\silverback_ai\Controller\UsageDetailsController'
+ requirements:
+ _permission: 'access token usage'
+
+silverback_ai.ai_settings:
+ path: '/admin/config/system/silverback-ai-settings'
+ defaults:
+ _title: 'Silverback AI settings'
+ _form: 'Drupal\silverback_ai\Form\SilverbackAiSettingsForm'
+ requirements:
+ _permission: 'administer site configuration'
diff --git a/packages/drupal/silverback_ai/silverback_ai.services.yml b/packages/drupal/silverback_ai/silverback_ai.services.yml
new file mode 100644
index 000000000..3ed140758
--- /dev/null
+++ b/packages/drupal/silverback_ai/silverback_ai.services.yml
@@ -0,0 +1,10 @@
+services:
+ silverback_ai.token.usage:
+ class: Drupal\silverback_ai\TokenUsage
+ arguments: ['@database', '@current_user', '@logger.factory', '@config.factory', '@entity_type.manager']
+ silverback_ai.openai_http_client:
+ class: Drupal\silverback_ai\HttpClient\OpenAiHttpClient
+ arguments: ['@http_client_factory', '@config.factory']
+ silverback_ai.service:
+ class: Drupal\silverback_ai\AiService
+ arguments: ['@current_route_match', '@current_user', '@entity_type.manager', '@logger.factory', '@config.factory', '@silverback_ai.openai_http_client','@silverback_ai.token.usage']
diff --git a/packages/drupal/silverback_ai/src/AiService.php b/packages/drupal/silverback_ai/src/AiService.php
new file mode 100644
index 000000000..69628f8e2
--- /dev/null
+++ b/packages/drupal/silverback_ai/src/AiService.php
@@ -0,0 +1,124 @@
+ $model,
+ 'messages' => [
+ [
+ 'role' => 'user',
+ 'content' => [
+ [
+ 'type' => 'text',
+ 'text' => $prompt,
+ ],
+ ],
+ ],
+ ],
+ ];
+
+ try {
+ $response = $this->silverbackAiOpenaiHttpClient->post('chat/completions', [
+ 'json' => $payload,
+ ]);
+ } catch (\Exception $e) {
+ throw new \Exception('HTTP request failed: ' . $e->getMessage());
+ }
+
+ $responseBodyContents = $response->getBody()->getContents();
+ $response = json_decode($responseBodyContents, TRUE, 512, JSON_THROW_ON_ERROR);
+ $this->logUsage($response, $context);
+
+ return $response;
+ }
+
+ /**
+ * List OpenAI available models.
+ *
+ * @return array
+ * @throws \GuzzleHttp\Exception\GuzzleException
+ */
+ public function listModels() {
+ try {
+ $response = $this->silverbackAiOpenaiHttpClient->get('models');
+ $responseBodyContents = $response->getBody()->getContents();
+ $response = json_decode($responseBodyContents, TRUE, 512, JSON_THROW_ON_ERROR);
+ return $response['data'];
+ } catch (\Exception $e) {
+ throw new \Exception('HTTP request failed: ' . $e->getMessage());
+ }
+ return [];
+ }
+
+ /**
+ * Logs the tokens usage.
+ *
+ * This method updates the response body with module and entity details and
+ * creates a new usage entry using the Silverback AI Token Usage service.
+ *
+ * @param array $response_body
+ * An associative array that will be enhanced with module and entity information.
+ * @param \Drupal\Core\Entity\EntityInterface|null $entity
+ * The entity for which to log usage details. If provided, its id, type,
+ * and revision id will be added to the response body if the entity is revisionable.
+ *
+ * @throws \Exception
+ */
+ private function logUsage(array $response_body, array $context) {
+ $response_body['module'] = $context['module'] ?? 'silverback_ai';
+ if (isset($context['entity'])) {
+ $entity = $context['entity'];
+ $response_body['entity_id'] = (string) $entity->id();
+ $response_body['entity_type_id'] = (string) $entity->getEntityTypeId();
+ if ($entity->getEntityType()->isRevisionable()) {
+ $response_body['entity_revision_id'] = (string) $entity->getRevisionId();
+ }
+ }
+ $this->tokenUsage->createUsageEntry($response_body);
+ }
+}
diff --git a/packages/drupal/silverback_ai/src/Controller/AiUsageController.php b/packages/drupal/silverback_ai/src/Controller/AiUsageController.php
new file mode 100644
index 000000000..4122a05f5
--- /dev/null
+++ b/packages/drupal/silverback_ai/src/Controller/AiUsageController.php
@@ -0,0 +1,99 @@
+get('entity_type.manager'),
+ $container->get('silverback_ai.token.usage'),
+ $container->get('current_user'),
+ );
+ }
+
+ /**
+ * Builds the response.
+ */
+ public function __invoke(): array {
+
+ $header = [
+ 'timestamp' => $this->t('Timestamp'),
+ 'username' => $this->t('User'),
+ 'entity_id' => $this->t('Entity type'),
+ 'tokens_total' => $this->t('Tokens used'),
+ 'ai_provider' => $this->t('Provider / Model'),
+ 'module_name' => $this->t('Module'),
+ 'info' => $this->t('Information'),
+ ];
+
+ // @todo Add DI
+ $entries = \Drupal::service('silverback_ai.token.usage')->getEntries();
+ $entries = array_map(function ($item) {
+ unset($item['response']);
+ return $item;
+ }, $entries);
+
+ $build['table'] = [
+ '#type' => 'table',
+ '#header' => $header,
+ '#rows' => $entries,
+ '#sticky' => TRUE,
+ '#empty' => $this->t('No records found'),
+ ];
+
+ $build['pager'] = [
+ '#type' => 'pager',
+ ];
+
+ return $build;
+ }
+
+}
diff --git a/packages/drupal/silverback_ai/src/Controller/UsageDetailsController.php b/packages/drupal/silverback_ai/src/Controller/UsageDetailsController.php
new file mode 100644
index 000000000..a261977a5
--- /dev/null
+++ b/packages/drupal/silverback_ai/src/Controller/UsageDetailsController.php
@@ -0,0 +1,106 @@
+connection = $connection;
+ $this->tokenUsage = $token_usage;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container): self {
+ return new self(
+ $container->get('database'),
+ $container->get('silverback_ai.token.usage'),
+ );
+ }
+
+ /**
+ * Generates an overview table of revisions for an entity.
+ *
+ * @param \Drupal\Core\Routing\RouteMatchInterface $routeMatch
+ * The route match.
+ *
+ * @return array
+ * A render array.
+ *
+ * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+ * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+ */
+ public function __invoke(RouteMatchInterface $routeMatch): array {
+
+ $id = $routeMatch->getParameter('record');
+
+ $query = $this->connection->select('silverback_ai_usage', 's')
+ ->condition('s.id', $id)
+ ->fields('s', [
+ 'id',
+ 'uid',
+ 'timestamp',
+ 'target_entity_id',
+ 'target_entity_type_id',
+ 'target_entity_revision_id',
+ 'tokens_in',
+ 'tokens_out',
+ 'total_count',
+ 'provider',
+ 'model',
+ 'module',
+ 'response',
+ ]);
+ $records = $query->execute();
+ foreach ($records->fetchAll() as $row) {
+ $info = $this->tokenUsage->buildRow($row);
+ $build['render_array'] = [
+ '#type' => 'details',
+ '#open' => TRUE,
+ '#title' => $this->t('Response details'),
+ 'source' => [
+ '#theme' => 'webform_codemirror',
+ '#type' => 'yaml',
+ '#code' => Yaml::encode($info['response']),
+ ],
+ ];
+
+ }
+
+ return $build;
+ }
+
+}
diff --git a/packages/drupal/silverback_ai/src/Form/SilverbackAiSettingsForm.php b/packages/drupal/silverback_ai/src/Form/SilverbackAiSettingsForm.php
new file mode 100644
index 000000000..7cdd3b1cb
--- /dev/null
+++ b/packages/drupal/silverback_ai/src/Form/SilverbackAiSettingsForm.php
@@ -0,0 +1,82 @@
+ 'details',
+ '#title' => $this->t('Open AI credentials'),
+ '#open' => TRUE,
+ ];
+
+ $form['credentials']['open_ai_base_uri'] = [
+ '#type' => 'textfield',
+ '#title' => $this->t('Base URI'),
+ '#default_value' => $this->t('https://api.openai.com/v1/'),
+ '#description' => $this->t("The OPEN AI API endpoint.") ,
+ ];
+
+ // Try to fetch ket from open ai module.
+ $api_key = $this->config('openai.settings')->get('api_key');
+ $api_org = $this->config('openai.settings')->get('api_org');
+
+ $form['credentials']['open_ai_key'] = [
+ '#type' => 'password',
+ '#title' => $this->t('Open AI key'),
+ '#description' => $this->t("The OPEN AI key for this project.") . '
' .
+ $this->t('Install the Open AI module to use the defined key from the module settings.', [
+ '@href' => 'https://www.drupal.org/project/openai',
+ ]),
+ '#default_value' => $this->config('silverback_ai.settings')->get('open_ai_api_key'),
+ ];
+
+ return parent::buildForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function validateForm(array &$form, FormStateInterface $form_state): void {
+ parent::validateForm($form, $form_state);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state): void {
+ $this->config('silverback_ai.settings')
+ ->set('open_ai_base_uri', $form_state->getValue('open_ai_base_uri'))
+ ->set('open_ai_api_key', $form_state->getValue('open_ai_api_key'))
+ ->save();
+ parent::submitForm($form, $form_state);
+ }
+
+}
diff --git a/packages/drupal/silverback_ai/src/HttpClient/OpenAiHttpClient.php b/packages/drupal/silverback_ai/src/HttpClient/OpenAiHttpClient.php
new file mode 100644
index 000000000..948793c0f
--- /dev/null
+++ b/packages/drupal/silverback_ai/src/HttpClient/OpenAiHttpClient.php
@@ -0,0 +1,37 @@
+get('silverback_ai.settings');
+ $open_ai_api_key = $config->get('open_ai_api_key') ?? '';
+ $open_ai_base_uri = $config->get('open_ai_base_uri') ?: 'https://api.openai.com/v1/';
+
+ $options = [
+ 'base_uri' => $open_ai_base_uri,
+ 'headers' => [
+ 'Authorization' => 'Bearer ' . $open_ai_api_key,
+ 'Content-Type' => 'application/json',
+ ],
+ ];
+
+ parent::__construct($options);
+ }
+}
diff --git a/packages/drupal/silverback_ai/src/TokenUsage.php b/packages/drupal/silverback_ai/src/TokenUsage.php
new file mode 100644
index 000000000..6111b0115
--- /dev/null
+++ b/packages/drupal/silverback_ai/src/TokenUsage.php
@@ -0,0 +1,199 @@
+currentUser) {
+ $uid = $this->currentUser->id();
+ }
+
+ // @todo Validate input array
+ try {
+ $this->connection
+ ->insert('silverback_ai_usage')
+ ->fields([
+ 'uid' => $uid,
+ 'timestamp' => (new DrupalDateTime())->getTimestamp(),
+ 'target_entity_type_id' => $context['entity_type_id'] ?? NULL,
+ 'target_entity_id' => $context['entity_id'] ?? NULL,
+ 'target_entity_revision_id' => $context['entity_revision_id'] ?? NULL,
+ 'tokens_in' => $tokens_in,
+ 'tokens_out' => $tokens_out,
+ 'total_count' => $tokens_total,
+ 'provider' => 'Open AI',
+ 'model' => $context['model'],
+ 'module' => $context['module'],
+ 'response' => json_encode($context),
+ ])
+ ->execute();
+ }
+ catch (\Exception $e) {
+ $this->loggerFactory->get('silverback_ai')->error($e->getMessage());
+ }
+ }
+
+ /**
+ * Retrieves a list of entries from the 'silverback_ai_usage' table.
+ *
+ * This function queries the database to select fields related to AI usage
+ * and orders them by ID in descending order. It paginates the result
+ * according to a predefined limit. Each row fetched from the database
+ * is processed using the `buildRow` method before being added to the result set.
+ *
+ * @return array
+ * An array of processed database records.
+ * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+ * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+ */
+ public function getEntries() {
+ $query = $this->connection->select('silverback_ai_usage', 's')
+ ->fields('s', [
+ 'id',
+ 'uid',
+ 'timestamp',
+ 'target_entity_id',
+ 'target_entity_type_id',
+ 'target_entity_revision_id',
+ 'tokens_in',
+ 'tokens_out',
+ 'total_count',
+ 'provider',
+ 'model',
+ 'module',
+ 'response',
+ ])
+ ->orderBy('id', 'DESC');
+ $pager = $query->extend('Drupal\Core\Database\Query\PagerSelectExtender')->limit(self::PAGER_LIMIT);
+ $rsc = $pager->execute();
+ $rows = [];
+
+ foreach ($rsc->fetchAll() as $row) {
+ $rows[] = $this->buildRow($row);
+ }
+ return $rows;
+ }
+
+ /**
+ * Builds a renderable array representing a row of data.
+ *
+ * This method constructs an array of information based on the data from
+ * the provided row, including entity details, user information, and additional
+ * metadata such as timestamps and provider information.
+ *
+ * @param object $row
+ * The data row object containing properties such as 'target_entity_id',
+ * 'target_entity_type_id', 'uid', 'timestamp', 'total_count', 'provider',
+ * 'model', and 'module'.
+ *
+ * @return array
+ * A renderable array with the following elements:
+ * - 'timestamp': The formatted timestamp of when the entry was created.
+ * - 'username': The display name of the user associated with the entry.
+ * - 'entity_id': The capitalized entity bundle string or empty string if
+ * the entity is not found.
+ * - 'tokens_total': The total token count from the row's data.
+ * - 'ai_provider': A string indicating the AI provider and model used.
+ * - 'module_name': The name of the module associated with the entry.
+ * - 'info': A renderable link to detailed usage information displayed in
+ * a modal dialog.
+ *
+ * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
+ * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
+ */
+ public function buildRow($row) {
+ $entity_info = '';
+ if ($row->target_entity_id && $row->target_entity_type_id) {
+ // @todo Aldo check revision
+ $entity = $this->entityTypeManager->getStorage($row->target_entity_type_id)->load($row->target_entity_id);
+ $entity_info = $entity ? $entity->bundle() : '';
+ // @todo Add url to entity. Problem is the e.g. File entities
+ // they return exception calling this method.
+ }
+
+ $user = User::load($row->uid);
+ $username = '';
+ if ($user) {
+ $username = $user->getDisplayName();
+ }
+
+ $icon_info = '';
+
+ $link = Link::createFromRoute(
+ Markup::create($icon_info),
+ 'silverback_ai.ai_usage.details',
+ ['record' => $row->id],
+ [
+ 'attributes' => [
+ 'class' => ['use-ajax'],
+ 'data-dialog-type' => 'modal',
+ 'data-dialog-options' => Json::encode([
+ 'width' => 800,
+ ]),
+ ],
+ 'attached' => [
+ 'library' => ['core/drupal.dialog.ajax'],
+ ],
+ ]
+ );
+
+ return [
+ 'timestamp' => DrupalDateTime::createFromTimestamp($row->timestamp)->format('d.m.Y H:i'),
+ 'username' => $username,
+ 'entity_id' => ucfirst($entity_info),
+ 'tokens_total' => $row->total_count,
+ 'ai_provider' => $row->provider . ' / ' . ($row->model ?: 'gpt-4o-mini'),
+ 'module_name' => $row->module,
+ 'info' => $link,
+ 'response' => $row->response,
+ ];
+ }
+
+}
diff --git a/packages/drupal/silverback_ai/src/TokenUsageInterface.php b/packages/drupal/silverback_ai/src/TokenUsageInterface.php
new file mode 100644
index 000000000..1ff6bc258
--- /dev/null
+++ b/packages/drupal/silverback_ai/src/TokenUsageInterface.php
@@ -0,0 +1,33 @@
+=18'}
+ dev: false
+
+ /@csstools/css-calc@2.1.1(@csstools/css-parser-algorithms@3.0.4)(@csstools/css-tokenizer@3.0.3):
+ resolution: {integrity: sha512-rL7kaUnTkL9K+Cvo2pnCieqNpTKgQzy5f+N+5Iuko9HAoasP+xgprVh7KN/MaJVvVL1l0EzQq2MoqBHKSrDrag==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^3.0.4
+ '@csstools/css-tokenizer': ^3.0.3
+ dependencies:
+ '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
+ '@csstools/css-tokenizer': 3.0.3
+ dev: false
+
+ /@csstools/css-color-parser@3.0.7(@csstools/css-parser-algorithms@3.0.4)(@csstools/css-tokenizer@3.0.3):
+ resolution: {integrity: sha512-nkMp2mTICw32uE5NN+EsJ4f5N+IGFeCFu4bGpiKgb2Pq/7J/MpyLBeQ5ry4KKtRFZaYs6sTmcMYrSRIyj5DFKA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^3.0.4
+ '@csstools/css-tokenizer': ^3.0.3
+ dependencies:
+ '@csstools/color-helpers': 5.0.1
+ '@csstools/css-calc': 2.1.1(@csstools/css-parser-algorithms@3.0.4)(@csstools/css-tokenizer@3.0.3)
+ '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3)
+ '@csstools/css-tokenizer': 3.0.3
+ dev: false
+
+ /@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3):
+ resolution: {integrity: sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-tokenizer': ^3.0.3
+ dependencies:
+ '@csstools/css-tokenizer': 3.0.3
+ dev: false
+
+ /@csstools/css-tokenizer@3.0.3:
+ resolution: {integrity: sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==}
+ engines: {node: '>=18'}
+ dev: false
+
/@dabh/diagnostics@2.0.3:
resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==}
dependencies:
@@ -4164,6 +4286,19 @@ packages:
levn: 0.4.1
dev: true
+ /@extractus/article-extractor@8.0.16:
+ resolution: {integrity: sha512-amxCKO2uerY0UPxDVSoTDdcTny0otpKsAIGC2q2CUDEhUX6EfxmpURttlKLx9uWFT9DRlNX9LSyMSP/2p7kFLg==}
+ engines: {node: '>= 18'}
+ dependencies:
+ '@mozilla/readability': 0.5.0
+ bellajs: 11.2.0
+ cross-fetch: 4.1.0
+ linkedom: 0.18.6
+ sanitize-html: 2.13.1
+ transitivePeerDependencies:
+ - encoding
+ dev: false
+
/@fastify/accept-negotiator@1.1.0:
resolution: {integrity: sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==}
engines: {node: '>=14'}
@@ -5992,6 +6127,7 @@ packages:
/@mapbox/node-pre-gyp@1.0.11:
resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==}
hasBin: true
+ requiresBuild: true
dependencies:
detect-libc: 2.0.3
https-proxy-agent: 5.0.1
@@ -6052,6 +6188,10 @@ packages:
'@lezer/lr': 1.4.0
json5: 2.2.3
+ /@mixmark-io/domino@2.2.0:
+ resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==}
+ dev: false
+
/@mole-inc/bin-wrapper@8.0.1:
resolution: {integrity: sha512-sTGoeZnjI8N4KS+sW2AN95gDBErhAguvkw/tWdCjeM8bvxpz5lqrnd0vOJABA1A+Ic3zED7PYoLP/RANLgVotA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -6065,6 +6205,11 @@ packages:
got: 11.8.6
os-filter-obj: 2.0.0
+ /@mozilla/readability@0.5.0:
+ resolution: {integrity: sha512-Z+CZ3QaosfFaTqvhQsIktyGrjFjSC0Fa4EMph4mqKnWhmyoGICsV/8QK+8HpXut6zV7zwfWwqDmEjtk1Qf6EgQ==}
+ engines: {node: '>=14.0.0'}
+ dev: false
+
/@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.2:
resolution: {integrity: sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ==}
cpu: [arm64]
@@ -6838,6 +6983,18 @@ packages:
'@octokit/openapi-types': 18.1.1
dev: false
+ /@opendocsg/pdf2md@0.2.1:
+ resolution: {integrity: sha512-k/yvfrTb+GPTIIm/bMm5IsenTqAFl+IqvkBgFwFlmflS5TT7FOfyRLp8MypVWLAG4G9AnT7AZFbdQYgN/CR5BA==}
+ hasBin: true
+ dependencies:
+ enumify: 1.0.4
+ minimist: 1.2.8
+ unpdf: 0.12.1
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+ dev: false
+
/@opentelemetry/api@1.8.0:
resolution: {integrity: sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==}
engines: {node: '>=8.0.0'}
@@ -8818,6 +8975,26 @@ packages:
'@testing-library/dom': 10.4.0
dev: true
+ /@textlint/ast-node-types@14.4.2:
+ resolution: {integrity: sha512-e8/drNznaZHS/qGDC83k6Ht1wDWNHzGQ0RHcXD+72YMFercEFvp6WVfW5XbCbxGbSITEO5NBCOCTyeccS9lxEA==}
+ dev: false
+
+ /@textlint/markdown-to-ast@14.4.2:
+ resolution: {integrity: sha512-hj2xR9hz5/Tu7Hlrn6VORJgdAfUhAd5j6cBkEVpnKAU4LaERkNyVCgK/da2JHK2w84YHmaDjER4D6zUUkllwag==}
+ dependencies:
+ '@textlint/ast-node-types': 14.4.2
+ debug: 4.4.0
+ mdast-util-gfm-autolink-literal: 0.1.3
+ neotraverse: 0.6.18
+ remark-footnotes: 3.0.0
+ remark-frontmatter: 3.0.0
+ remark-gfm: 1.0.0
+ remark-parse: 9.0.0
+ unified: 9.2.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
/@tokenizer/token@0.3.0:
resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==}
@@ -9084,7 +9261,6 @@ packages:
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
dependencies:
'@types/unist': 3.0.2
- dev: true
/@types/hoist-non-react-statics@3.3.5:
resolution: {integrity: sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==}
@@ -9155,6 +9331,10 @@ packages:
resolution: {integrity: sha512-R5M+SYhMbwBeQcNXYWNCZkl09vkVfAtcPIaCGdzIkkbeaTrVbGQ7HVgi4s+EmM/M1K4ZuWQH0jGcvMvNePfxYA==}
dev: true
+ /@types/katex@0.16.7:
+ resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==}
+ dev: false
+
/@types/keyv@3.1.4:
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
dependencies:
@@ -9173,7 +9353,6 @@ packages:
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
dependencies:
'@types/unist': 3.0.2
- dev: true
/@types/mdx@2.0.13:
resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==}
@@ -9211,13 +9390,19 @@ packages:
/@types/node-fetch@2.6.11:
resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==}
dependencies:
- '@types/node': 20.11.17
+ '@types/node': 22.7.2
form-data: 4.0.0
/@types/node@17.0.45:
resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
dev: false
+ /@types/node@18.19.70:
+ resolution: {integrity: sha512-RE+K0+KZoEpDUbGGctnGdkrLFwi1eYKTlIHNl2Um98mUkGsm1u2Ff6Ltd0e8DktTtC98uy7rSj+hO8t/QuLoVQ==}
+ dependencies:
+ undici-types: 5.26.5
+ dev: false
+
/@types/node@20.11.17:
resolution: {integrity: sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==}
dependencies:
@@ -9227,7 +9412,6 @@ packages:
resolution: {integrity: sha512-866lXSrpGpgyHBZUa2m9YNWqHDjjM0aBTJlNtYaGEw4rqY/dcD7deRVTbBBAJelfA7oaGDbNftXF/TL/A6RgoA==}
dependencies:
undici-types: 6.19.8
- dev: true
/@types/node@8.10.66:
resolution: {integrity: sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==}
@@ -9404,7 +9588,6 @@ packages:
/@types/unist@3.0.2:
resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==}
- dev: true
/@types/uuid@9.0.8:
resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==}
@@ -10985,6 +11168,11 @@ packages:
p-event: 5.0.1
dev: false
+ /@xmldom/xmldom@0.8.10:
+ resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==}
+ engines: {node: '>=10.0.0'}
+ dev: false
+
/@xtuc/ieee754@1.2.0:
resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
@@ -11124,6 +11312,7 @@ packages:
/agent-base@6.0.2:
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
engines: {node: '>= 6.0.0'}
+ requiresBuild: true
dependencies:
debug: 4.3.7
transitivePeerDependencies:
@@ -11146,7 +11335,11 @@ packages:
debug: 4.3.7
transitivePeerDependencies:
- supports-color
- dev: true
+
+ /agent-base@7.1.3:
+ resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==}
+ engines: {node: '>= 14'}
+ dev: false
/agentkeepalive@4.5.0:
resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==}
@@ -11155,7 +11348,6 @@ packages:
dependencies:
humanize-ms: 1.2.1
dev: false
- optional: true
/aggregate-error@3.1.0:
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
@@ -11314,6 +11506,7 @@ packages:
/ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
+ requiresBuild: true
/ansi-regex@6.1.0:
resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==}
@@ -11462,6 +11655,7 @@ packages:
/aproba@2.0.0:
resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==}
+ requiresBuild: true
dev: false
/arch@2.2.0:
@@ -11499,6 +11693,7 @@ packages:
resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==}
engines: {node: '>=10'}
deprecated: This package is no longer supported.
+ requiresBuild: true
dependencies:
delegates: 1.0.0
readable-stream: 3.6.2
@@ -11586,6 +11781,14 @@ packages:
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dev: false
+ /array-parallel@0.1.3:
+ resolution: {integrity: sha512-TDPTwSWW5E4oiFiKmz6RGJ/a80Y91GuLgUYuLd49+XBS75tYo8PNgaT2K/OxuQYqkoI852MDGBorg9OcUSTQ8w==}
+ dev: false
+
+ /array-series@0.1.5:
+ resolution: {integrity: sha512-L0XlBwfx9QetHOsbLDrE/vh2t018w9462HM3iaFfxRiK83aJjAt/Ja3NMkOW7FICwWTlQBa3ZbL5FKhuQWkDrg==}
+ dev: false
+
/array-timsort@1.0.3:
resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==}
dev: false
@@ -11920,6 +12123,16 @@ packages:
transitivePeerDependencies:
- debug
+ /axios@1.7.9:
+ resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==}
+ dependencies:
+ follow-redirects: 1.15.6
+ form-data: 4.0.0
+ proxy-from-env: 1.1.0
+ transitivePeerDependencies:
+ - debug
+ dev: false
+
/axobject-query@3.2.1:
resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==}
dependencies:
@@ -12220,6 +12433,7 @@ packages:
/balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+ requiresBuild: true
/bare-events@2.2.2:
resolution: {integrity: sha512-h7z00dWdG0PYOQEvChhOSWvOfkIKsdZGkWr083FgN/HyoQuebSew/cgirYqh9SCuy/hRvxc5Vy6Fw8xAmYHLkQ==}
@@ -12266,6 +12480,11 @@ packages:
resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==}
dev: false
+ /bellajs@11.2.0:
+ resolution: {integrity: sha512-Wjss+Bc674ZABPr+SCKWTqA4V1pyYFhzDTjNBJy4jdmgOv0oGIGXeKBRJyINwP5tIy+iIZD9SfgZpztduzQ5QA==}
+ engines: {node: '>= 18.4'}
+ dev: false
+
/better-ajv-errors@1.2.0(ajv@8.12.0):
resolution: {integrity: sha512-UW+IsFycygIo7bclP9h5ugkNH8EjCSgqyFB/yQ4Hqqa1OEYDtb0uFIkYE0b6+CjkgJYVM5UKI/pJPxjYe9EZlA==}
engines: {node: '>= 12.13.0'}
@@ -12342,6 +12561,10 @@ packages:
readable-stream: 3.6.2
dev: false
+ /bluebird@3.4.7:
+ resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==}
+ dev: false
+
/bluebird@3.7.2:
resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==}
@@ -12387,6 +12610,26 @@ packages:
transitivePeerDependencies:
- supports-color
+ /body-parser@1.20.3:
+ resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==}
+ engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
+ 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.0
+ iconv-lite: 0.4.24
+ on-finished: 2.4.1
+ qs: 6.13.0
+ raw-body: 2.5.2
+ type-is: 1.6.18
+ unpipe: 1.0.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
/boolbase@1.0.0:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
@@ -12437,6 +12680,7 @@ packages:
/brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
+ requiresBuild: true
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
@@ -12719,6 +12963,20 @@ packages:
/caniuse-lite@1.0.30001608:
resolution: {integrity: sha512-cjUJTQkk9fQlJR2s4HMuPMvTiRggl0rAVMtthQuyOlDWuqHXqN8azLq+pi8B2TjwKJ32diHjUqRIKeFX4z1FoA==}
+ /canvas@2.11.2:
+ resolution: {integrity: sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==}
+ engines: {node: '>=6'}
+ requiresBuild: true
+ dependencies:
+ '@mapbox/node-pre-gyp': 1.0.11
+ nan: 2.19.0
+ simple-get: 3.1.1
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+ dev: false
+ optional: true
+
/capital-case@1.0.4:
resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==}
dependencies:
@@ -12885,6 +13143,10 @@ packages:
resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==}
dev: false
+ /character-reference-invalid@2.0.1:
+ resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==}
+ dev: false
+
/chardet@0.7.0:
resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
@@ -12962,6 +13224,7 @@ packages:
/chownr@2.0.0:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
engines: {node: '>=10'}
+ requiresBuild: true
dev: false
/chrome-trace-event@1.0.3:
@@ -13219,6 +13482,7 @@ packages:
/color-support@1.1.3:
resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==}
hasBin: true
+ requiresBuild: true
dev: false
/color@3.2.1:
@@ -13315,6 +13579,11 @@ packages:
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
engines: {node: '>= 10'}
+ /commander@8.3.0:
+ resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
+ engines: {node: '>= 12'}
+ dev: false
+
/commander@9.5.0:
resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==}
engines: {node: ^12.20.0 || >=14}
@@ -13396,6 +13665,7 @@ packages:
/concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+ requiresBuild: true
/concat-stream@1.6.2:
resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==}
@@ -13474,6 +13744,7 @@ packages:
/console-control-strings@1.1.0:
resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
+ requiresBuild: true
dev: false
/consolidated-events@2.0.2:
@@ -13573,6 +13844,11 @@ packages:
resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
engines: {node: '>= 0.6'}
+ /cookie@0.7.1:
+ resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==}
+ engines: {node: '>= 0.6'}
+ dev: false
+
/copy-descriptor@0.1.1:
resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==}
engines: {node: '>=0.10.0'}
@@ -13788,12 +14064,27 @@ packages:
transitivePeerDependencies:
- encoding
+ /cross-fetch@4.1.0:
+ resolution: {integrity: sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==}
+ dependencies:
+ node-fetch: 2.7.0
+ transitivePeerDependencies:
+ - encoding
+ dev: false
+
/cross-inspect@1.0.0:
resolution: {integrity: sha512-4PFfn4b5ZN6FMNGSZlyb7wUhuN8wvj8t/VQHZdM4JsDcruGJ8L2kf9zao98QIrBPFCpdk27qst/AGTl7pL3ypQ==}
engines: {node: '>=16.0.0'}
dependencies:
tslib: 2.6.2
+ /cross-spawn@4.0.2:
+ resolution: {integrity: sha512-yAXz/pA1tD8Gtg2S98Ekf/sewp3Lcp3YoFKJ4Hkp5h5yLWnKVTDU0kwjKJ8NDCYcfTLfyGkzTikst+jWypT1iA==}
+ dependencies:
+ lru-cache: 4.1.5
+ which: 1.3.1
+ dev: false
+
/cross-spawn@5.1.0:
resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==}
dependencies:
@@ -14092,6 +14383,18 @@ packages:
dependencies:
css-tree: 2.2.1
+ /cssom@0.5.0:
+ resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==}
+ dev: false
+
+ /cssstyle@4.2.1:
+ resolution: {integrity: sha512-9+vem03dMXG7gDmZ62uqmRiMRNtinIZ9ZyuF6BdxzfOD+FdN5hretzynkn0ReS2DO2GSw76RWHs0UmJPI2zUjw==}
+ engines: {node: '>=18'}
+ dependencies:
+ '@asamuzakjp/css-color': 2.8.2
+ rrweb-cssom: 0.8.0
+ dev: false
+
/csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
@@ -14127,6 +14430,14 @@ packages:
engines: {node: '>= 12'}
dev: false
+ /data-urls@5.0.0:
+ resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
+ engines: {node: '>=18'}
+ dependencies:
+ whatwg-mimetype: 4.0.0
+ whatwg-url: 14.1.0
+ dev: false
+
/data-view-buffer@1.0.1:
resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==}
engines: {node: '>= 0.4'}
@@ -14235,6 +14546,18 @@ packages:
supports-color: 9.4.0
dev: false
+ /debug@4.4.0:
+ resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+ dependencies:
+ ms: 2.1.3
+ dev: false
+
/decache@4.6.2:
resolution: {integrity: sha512-2LPqkLeu8XWHU8qNCS3kcF6sCcb5zIzvWaAHYSvPfwhdd7mHuah29NssMzrTYyHN4F5oFy2ko9OBYxegtU0FEw==}
dependencies:
@@ -14974,6 +15297,10 @@ packages:
- '@types/react'
dev: false
+ /decimal.js@10.4.3:
+ resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
+ dev: false
+
/decode-named-character-reference@1.0.2:
resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==}
dependencies:
@@ -14988,6 +15315,15 @@ packages:
engines: {node: '>=14.16'}
dev: false
+ /decompress-response@4.2.1:
+ resolution: {integrity: sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==}
+ engines: {node: '>=8'}
+ requiresBuild: true
+ dependencies:
+ mimic-response: 2.1.0
+ dev: false
+ optional: true
+
/decompress-response@6.0.0:
resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
engines: {node: '>=10'}
@@ -15140,6 +15476,7 @@ packages:
/delegates@1.0.0:
resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
+ requiresBuild: true
dev: false
/depd@1.1.2:
@@ -15329,7 +15666,6 @@ packages:
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
dependencies:
dequal: 2.0.3
- dev: true
/diacritics@1.3.0:
resolution: {integrity: sha512-wlwEkqcsaxvPJML+rDh/2iS824jbREk6DUMUKkEaSlxdYHeS43cClJtsWglvw2RfeXGm6ohKDqsXteJ5sP5enA==}
@@ -15358,6 +15694,10 @@ packages:
htmlparser2: 3.10.1
dev: true
+ /dingbat-to-unicode@1.0.1:
+ resolution: {integrity: sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w==}
+ dev: false
+
/dir-glob@3.0.1:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'}
@@ -15545,6 +15885,11 @@ packages:
resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==}
engines: {node: '>=12'}
+ /dotenv@16.4.7:
+ resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==}
+ engines: {node: '>=12'}
+ dev: false
+
/dotenv@7.0.0:
resolution: {integrity: sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g==}
engines: {node: '>=6'}
@@ -15574,6 +15919,12 @@ packages:
resolution: {integrity: sha512-20TuZZHCEZ2O71q9/+8BwKwZ0QtD9D8ObhrihJPr+vLLYlSuAU3/zL4cSlgbfeoGHTjCSJBa7NGcrF9/Bx/WJQ==}
engines: {node: '>=4'}
+ /duck@0.1.12:
+ resolution: {integrity: sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==}
+ dependencies:
+ underscore: 1.13.7
+ dev: false
+
/duplexer@0.1.2:
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
@@ -15627,6 +15978,11 @@ packages:
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
engines: {node: '>= 0.8'}
+ /encodeurl@2.0.0:
+ resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
+ engines: {node: '>= 0.8'}
+ dev: false
+
/encoding@0.1.13:
resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==}
requiresBuild: true
@@ -15700,6 +16056,10 @@ packages:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
+ /enumify@1.0.4:
+ resolution: {integrity: sha512-5mwWXaVzJaqyUdOW/PDH5QySRgmQ8VvujmxmvXoXj9w0n+6omhVuyD56eI37FMqy/LxueJzsQ4DrHVQzuT/TXg==}
+ dev: false
+
/env-paths@2.2.1:
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
engines: {node: '>=6'}
@@ -17334,6 +17694,45 @@ packages:
transitivePeerDependencies:
- supports-color
+ /express@4.21.2:
+ resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==}
+ engines: {node: '>= 0.10.0'}
+ dependencies:
+ accepts: 1.3.8
+ array-flatten: 1.1.1
+ body-parser: 1.20.3
+ content-disposition: 0.5.4
+ content-type: 1.0.5
+ 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.13.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
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
/ext-list@2.2.2:
resolution: {integrity: sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==}
engines: {node: '>=0.10.0'}
@@ -17533,6 +17932,12 @@ packages:
dependencies:
reusify: 1.0.4
+ /fault@1.0.4:
+ resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==}
+ dependencies:
+ format: 0.2.2
+ dev: false
+
/fb-watchman@2.0.2:
resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==}
dependencies:
@@ -17768,6 +18173,21 @@ packages:
transitivePeerDependencies:
- supports-color
+ /finalhandler@1.3.1:
+ resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==}
+ engines: {node: '>= 0.8'}
+ 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.1
+ unpipe: 1.0.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
/find-cache-dir@3.3.2:
resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==}
engines: {node: '>=8'}
@@ -18030,6 +18450,10 @@ packages:
typescript: 5.6.3
webpack: 5.91.0
+ /form-data-encoder@1.7.2:
+ resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==}
+ dev: false
+
/form-data-encoder@2.1.4:
resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==}
engines: {node: '>= 14.17'}
@@ -18042,6 +18466,19 @@ packages:
combined-stream: 1.0.8
mime-types: 2.1.35
+ /format@0.2.2:
+ resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==}
+ engines: {node: '>=0.4.x'}
+ dev: false
+
+ /formdata-node@4.4.1:
+ resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==}
+ engines: {node: '>= 12.20'}
+ dependencies:
+ node-domexception: 1.0.0
+ web-streams-polyfill: 4.0.0-beta.3
+ dev: false
+
/formdata-polyfill@4.0.10:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'}
@@ -18155,6 +18592,7 @@ packages:
/fs-minipass@2.1.0:
resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==}
engines: {node: '>= 8'}
+ requiresBuild: true
dependencies:
minipass: 3.3.6
dev: false
@@ -18164,6 +18602,7 @@ packages:
/fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
+ requiresBuild: true
/fs@0.0.1-security:
resolution: {integrity: sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==}
@@ -19385,6 +19824,7 @@ packages:
resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==}
engines: {node: '>=10'}
deprecated: This package is no longer supported.
+ requiresBuild: true
dependencies:
aproba: 2.0.0
color-support: 1.1.3
@@ -19641,6 +20081,8 @@ packages:
/glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+ deprecated: Glob versions prior to v9 are no longer supported
+ requiresBuild: true
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
@@ -19788,6 +20230,18 @@ packages:
unicorn-magic: 0.1.0
dev: true
+ /gm@1.25.0:
+ resolution: {integrity: sha512-4kKdWXTtgQ4biIo7hZA396HT062nDVVHPjQcurNZ3o/voYN+o5FUC5kOwuORbpExp3XbTJ3SU7iRipiIhQtovw==}
+ engines: {node: '>=14'}
+ dependencies:
+ array-parallel: 0.1.3
+ array-series: 0.1.5
+ cross-spawn: 4.0.2
+ debug: 3.2.7
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
/gonzales-pe@4.3.0:
resolution: {integrity: sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==}
engines: {node: '>=0.6.0'}
@@ -20083,6 +20537,7 @@ packages:
/has-unicode@2.0.1:
resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==}
+ requiresBuild: true
dev: false
/has-value@0.3.1:
@@ -20308,6 +20763,22 @@ packages:
zwitch: 2.0.4
dev: false
+ /hast-util-to-html@9.0.4:
+ resolution: {integrity: sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA==}
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/unist': 3.0.2
+ ccount: 2.0.1
+ comma-separated-tokens: 2.0.3
+ hast-util-whitespace: 3.0.0
+ html-void-elements: 3.0.0
+ mdast-util-to-hast: 13.2.0
+ property-information: 6.5.0
+ space-separated-tokens: 2.0.2
+ stringify-entities: 4.0.4
+ zwitch: 2.0.4
+ dev: false
+
/hast-util-to-mdast@7.1.3:
resolution: {integrity: sha512-3vER9p8B8mCs5b2qzoBiWlC9VnTkFmr8Ufb1eKdcvhVY+nipt52YfMRshk5r9gOE1IZ9/xtlSxebGCv1ig9uKA==}
dependencies:
@@ -20364,6 +20835,12 @@ packages:
resolution: {integrity: sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==}
dev: false
+ /hast-util-whitespace@3.0.0:
+ resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
+ dependencies:
+ '@types/hast': 3.0.4
+ dev: false
+
/hastscript@5.1.2:
resolution: {integrity: sha512-WlztFuK+Lrvi3EggsqOkQ52rKbxkXL3RwB6t5lwoa8QLMemoWfBuL43eDrwOamJyR7uKQKdmKYaBH1NZBiIRrQ==}
dependencies:
@@ -20449,6 +20926,13 @@ packages:
resolution: {integrity: sha512-ycJQMRaRPBcfnoT1gS5I1XCvbbw9KO94Y0vkwksuOjcJMqNZtb03MF2tCItLI2mQbkZWSSeFinoRDPmjzv4tKg==}
dev: true
+ /html-encoding-sniffer@4.0.0:
+ resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
+ engines: {node: '>=18'}
+ dependencies:
+ whatwg-encoding: 3.1.1
+ dev: false
+
/html-entities@2.5.2:
resolution: {integrity: sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==}
@@ -20456,6 +20940,10 @@ packages:
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
dev: true
+ /html-escaper@3.0.3:
+ resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==}
+ dev: false
+
/html-tags@3.3.1:
resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==}
engines: {node: '>=8'}
@@ -20469,6 +20957,10 @@ packages:
resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==}
dev: false
+ /html-void-elements@3.0.0:
+ resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
+ dev: false
+
/htmlparser2@3.10.1:
resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==}
dependencies:
@@ -20495,7 +20987,15 @@ packages:
domhandler: 5.0.3
domutils: 3.1.0
entities: 4.5.0
- dev: true
+
+ /htmlparser2@9.1.0:
+ resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==}
+ dependencies:
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+ domutils: 3.1.0
+ entities: 4.5.0
+ dev: false
/http-cache-semantics@4.1.1:
resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==}
@@ -20542,7 +21042,6 @@ packages:
debug: 4.3.7
transitivePeerDependencies:
- supports-color
- dev: true
/http-proxy-middleware@2.0.6(debug@4.3.4):
resolution: {integrity: sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==}
@@ -20619,6 +21118,7 @@ packages:
/https-proxy-agent@5.0.1:
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
engines: {node: '>= 6'}
+ requiresBuild: true
dependencies:
agent-base: 6.0.2
debug: 4.3.7
@@ -20646,6 +21146,16 @@ packages:
- supports-color
dev: true
+ /https-proxy-agent@7.0.6:
+ resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
+ engines: {node: '>= 14'}
+ dependencies:
+ agent-base: 7.1.3
+ debug: 4.3.7
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
/human-signals@2.1.0:
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
engines: {node: '>=10.17.0'}
@@ -20665,7 +21175,6 @@ packages:
dependencies:
ms: 2.1.3
dev: false
- optional: true
/husky@8.0.3:
resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==}
@@ -20740,6 +21249,13 @@ packages:
dependencies:
queue: 6.0.2
+ /image-type@5.2.0:
+ resolution: {integrity: sha512-f0+6qHeGfyEh1HhFGPUWZb+Dqqm6raKeeAR6Opt01wBBIQL32/1wpZkPQm8gcliB/Ws6oiX2ofFYXB57+CV0iQ==}
+ engines: {node: '>=14.16'}
+ dependencies:
+ file-type: 18.7.0
+ dev: false
+
/imagetools-core@6.0.4:
resolution: {integrity: sha512-N1qs5qn7u9nR3kboISkYuvJm8MohiphCfBa+wx1UOropVaFis9/mh6wuDPLHJNhl6/64C7q2Pch5NASVKAaSrg==}
engines: {node: '>=12.0.0'}
@@ -20818,6 +21334,7 @@ packages:
/inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
+ requiresBuild: true
dependencies:
once: 1.4.0
wrappy: 1.0.2
@@ -20914,6 +21431,11 @@ packages:
kind-of: 6.0.3
dev: false
+ /install@0.13.0:
+ resolution: {integrity: sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==}
+ engines: {node: '>= 0.10'}
+ dev: false
+
/internal-slot@1.0.7:
resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==}
engines: {node: '>= 0.4'}
@@ -21023,6 +21545,10 @@ packages:
resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==}
dev: false
+ /is-alphabetical@2.0.1:
+ resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==}
+ dev: false
+
/is-alphanumeric@1.0.0:
resolution: {integrity: sha512-ZmRL7++ZkcMOfDuWZuMJyIVLr2keE1o/DeNWh1EmgqGhUcV+9BIVsx0BcSBOHTZqzjs4+dISzr2KAeBEWGgXeA==}
engines: {node: '>=0.10.0'}
@@ -21035,6 +21561,13 @@ packages:
is-decimal: 1.0.4
dev: false
+ /is-alphanumerical@2.0.1:
+ resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==}
+ dependencies:
+ is-alphabetical: 2.0.1
+ is-decimal: 2.0.1
+ dev: false
+
/is-arguments@1.1.1:
resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==}
engines: {node: '>= 0.4'}
@@ -21148,6 +21681,10 @@ packages:
resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==}
dev: false
+ /is-decimal@2.0.1:
+ resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==}
+ dev: false
+
/is-descriptor@0.1.7:
resolution: {integrity: sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==}
engines: {node: '>= 0.4'}
@@ -21206,6 +21743,7 @@ packages:
/is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
+ requiresBuild: true
/is-fullwidth-code-point@4.0.0:
resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==}
@@ -21239,6 +21777,10 @@ packages:
resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==}
dev: false
+ /is-hexadecimal@2.0.1:
+ resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==}
+ dev: false
+
/is-hotkey@0.1.8:
resolution: {integrity: sha512-qs3NZ1INIS+H+yeo7cD9pDfwYV/jqRh1JG9S9zYrNudkoUQg7OL7ziXqRKu+InFjUIDoP2o6HIkLYMh1pcWgyQ==}
dev: false
@@ -21370,6 +21912,10 @@ packages:
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dev: true
+ /is-potential-custom-element-name@1.0.1:
+ resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
+ dev: false
+
/is-promise@2.2.2:
resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==}
@@ -22313,6 +22859,42 @@ packages:
engines: {node: '>=12.0.0'}
dev: true
+ /jsdom@25.0.1:
+ resolution: {integrity: sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ canvas: ^2.11.2
+ peerDependenciesMeta:
+ canvas:
+ optional: true
+ dependencies:
+ cssstyle: 4.2.1
+ data-urls: 5.0.0
+ decimal.js: 10.4.3
+ form-data: 4.0.0
+ html-encoding-sniffer: 4.0.0
+ http-proxy-agent: 7.0.2
+ https-proxy-agent: 7.0.6
+ is-potential-custom-element-name: 1.0.1
+ nwsapi: 2.2.16
+ parse5: 7.1.2
+ rrweb-cssom: 0.7.1
+ saxes: 6.0.0
+ symbol-tree: 3.2.4
+ tough-cookie: 5.1.0
+ w3c-xmlserializer: 5.0.0
+ webidl-conversions: 7.0.0
+ whatwg-encoding: 3.1.1
+ whatwg-mimetype: 4.0.0
+ whatwg-url: 14.1.0
+ ws: 8.18.0
+ xml-name-validator: 5.0.0
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+ dev: false
+
/jsesc@0.5.0:
resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==}
hasBin: true
@@ -22429,6 +23011,15 @@ packages:
object.assign: 4.1.5
object.values: 1.2.0
+ /jszip@3.10.1:
+ resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==}
+ dependencies:
+ lie: 3.3.0
+ pako: 1.0.11
+ readable-stream: 2.3.8
+ setimmediate: 1.0.5
+ dev: false
+
/junit-report-builder@3.2.1:
resolution: {integrity: sha512-IMCp5XyDQ4YESDE4Za7im3buM0/7cMnRfe17k2X8B05FnUl9vqnaliX6cgOEmPIeWKfJrEe/gANRq/XgqttCqQ==}
engines: {node: '>=8'}
@@ -22463,6 +23054,13 @@ packages:
resolution: {integrity: sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==}
dev: false
+ /katex@0.16.20:
+ resolution: {integrity: sha512-jjuLaMGD/7P8jUTpdKhA9IoqnH+yMFB3sdAFtq5QdAqeP2PjiSbnC3EaguKPNtv6dXXanHxp1ckwvF4a86LBig==}
+ hasBin: true
+ dependencies:
+ commander: 8.3.0
+ dev: false
+
/kebab-hash@0.1.2:
resolution: {integrity: sha512-BTZpq3xgISmQmAVzkISy4eUutsUA7s4IEFlCwOBJjvSFOwyR7I+fza+tBc/rzYWK/NrmFHjfU1IhO3lu29Ib/w==}
dependencies:
@@ -22580,6 +23178,12 @@ packages:
dependencies:
immediate: 3.0.6
+ /lie@3.3.0:
+ resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==}
+ dependencies:
+ immediate: 3.0.6
+ dev: false
+
/light-my-request@5.12.0:
resolution: {integrity: sha512-P526OX6E7aeCIfw/9UyJNsAISfcFETghysaWHQAlQYayynShT08MOj4c6fBCvTWBrHXSvqBAKDp3amUPSCQI4w==}
dependencies:
@@ -22606,9 +23210,25 @@ packages:
/lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+ /linkedom@0.18.6:
+ resolution: {integrity: sha512-6G8euAJ84s7MTXTli5JIOO5tzEpyoUBw2/zcqAunSurbCtC83YcgrK+VTcO8HZ/rdR3eaaZM573FP9rNo1uXIA==}
+ dependencies:
+ css-select: 5.1.0
+ cssom: 0.5.0
+ html-escaper: 3.0.3
+ htmlparser2: 9.1.0
+ uhyphen: 0.2.0
+ dev: false
+
/linkfs@2.1.0:
resolution: {integrity: sha512-kmsGcmpvjStZ0ATjuHycBujtNnXiZR28BTivEu0gAMDTT7GEyodcK6zSRtu6xsrdorrPZEIN380x7BD7xEYkew==}
+ /linkify-it@5.0.0:
+ resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
+ dependencies:
+ uc.micro: 2.1.0
+ dev: false
+
/listhen@1.7.2:
resolution: {integrity: sha512-7/HamOm5YD9Wb7CFgAZkKgVPA96WwhcTQoqtm2VTZGVbVVn3IWKRBTgrU7cchA3Q8k9iCsG8Osoi9GX4JsGM9g==}
hasBin: true
@@ -22971,6 +23591,14 @@ packages:
dependencies:
js-tokens: 4.0.0
+ /lop@0.4.2:
+ resolution: {integrity: sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw==}
+ dependencies:
+ duck: 0.1.12
+ option: 0.2.4
+ underscore: 1.13.7
+ dev: false
+
/loupe@2.3.7:
resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==}
dependencies:
@@ -23007,6 +23635,11 @@ packages:
/lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+ /lru-cache@11.0.2:
+ resolution: {integrity: sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==}
+ engines: {node: 20 || >=22}
+ dev: false
+
/lru-cache@4.0.0:
resolution: {integrity: sha512-WKhDkjlLwzE8jAQdQlsxLUQTPXLCKX/4cJk6s5AlRtJkDBk0IKH5O51bVDH61K9N4bhbbyvLM6EiOuE8ovApPA==}
dependencies:
@@ -23080,6 +23713,7 @@ packages:
/make-dir@3.1.0:
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
engines: {node: '>=8'}
+ requiresBuild: true
dependencies:
semver: 6.3.1
@@ -23126,6 +23760,23 @@ packages:
tmpl: 1.0.5
dev: true
+ /mammoth@1.9.0:
+ resolution: {integrity: sha512-F+0NxzankQV9XSUAuVKvkdQK0GbtGGuqVnND9aVf9VSeUA82LQa29GjLqYU6Eez8LHqSJG3eGiDW3224OKdpZg==}
+ engines: {node: '>=12.0.0'}
+ hasBin: true
+ dependencies:
+ '@xmldom/xmldom': 0.8.10
+ argparse: 1.0.10
+ base64-js: 1.5.1
+ bluebird: 3.4.7
+ dingbat-to-unicode: 1.0.1
+ jszip: 3.10.1
+ lop: 0.4.2
+ path-is-absolute: 1.0.1
+ underscore: 1.13.7
+ xmlbuilder: 10.1.1
+ dev: false
+
/map-age-cleaner@0.1.3:
resolution: {integrity: sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==}
engines: {node: '>=6'}
@@ -23171,6 +23822,18 @@ packages:
resolution: {integrity: sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==}
dev: false
+ /markdown-it@14.1.0:
+ resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
+ hasBin: true
+ dependencies:
+ argparse: 2.0.1
+ entities: 4.5.0
+ linkify-it: 5.0.0
+ mdurl: 2.0.0
+ punycode.js: 2.3.1
+ uc.micro: 2.1.0
+ dev: false
+
/markdown-table@1.1.3:
resolution: {integrity: sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==}
dev: false
@@ -23194,6 +23857,23 @@ packages:
react: 18.2.0
dev: true
+ /markdownlint@0.37.3:
+ resolution: {integrity: sha512-eoQqH0291YCCjd+Pe1PUQ9AmWthlVmS0XWgcionkZ8q34ceZyRI+pYvsWksXJJL8OBkWCPwp1h/pnXxrPFC4oA==}
+ engines: {node: '>=18'}
+ dependencies:
+ markdown-it: 14.1.0
+ micromark: 4.0.1
+ micromark-core-commonmark: 2.0.2
+ micromark-extension-directive: 3.0.2
+ micromark-extension-gfm-autolink-literal: 2.1.0
+ micromark-extension-gfm-footnote: 2.1.0
+ micromark-extension-gfm-table: 2.1.0
+ micromark-extension-math: 3.1.0
+ micromark-util-types: 2.0.1
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
/material-colors@1.2.6:
resolution: {integrity: sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==}
dev: false
@@ -23261,6 +23941,15 @@ packages:
unist-util-visit-parents: 6.0.1
dev: true
+ /mdast-util-footnote@0.1.7:
+ resolution: {integrity: sha512-QxNdO8qSxqbO2e3m09KwDKfWiLgqyCurdWTQ198NpbZ2hxntdc+VKS4fDJCmNWbAroUdYnSthu+XbZ8ovh8C3w==}
+ dependencies:
+ mdast-util-to-markdown: 0.6.5
+ micromark: 2.11.4
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
/mdast-util-from-markdown@0.8.5:
resolution: {integrity: sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==}
dependencies:
@@ -23311,6 +24000,31 @@ packages:
- supports-color
dev: true
+ /mdast-util-from-markdown@2.0.2:
+ resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==}
+ dependencies:
+ '@types/mdast': 4.0.4
+ '@types/unist': 3.0.2
+ decode-named-character-reference: 1.0.2
+ devlop: 1.1.0
+ mdast-util-to-string: 4.0.0
+ micromark: 4.0.0
+ micromark-util-decode-numeric-character-reference: 2.0.1
+ micromark-util-decode-string: 2.0.0
+ micromark-util-normalize-identifier: 2.0.0
+ micromark-util-symbol: 2.0.0
+ micromark-util-types: 2.0.0
+ unist-util-stringify-position: 4.0.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /mdast-util-frontmatter@0.2.0:
+ resolution: {integrity: sha512-FHKL4w4S5fdt1KjJCwB0178WJ0evnyyQr5kXTM3wrOVpytD0hrkvd+AOOjU9Td8onOejCkmZ+HQRT3CZ3coHHQ==}
+ dependencies:
+ micromark-extension-frontmatter: 0.2.2
+ dev: false
+
/mdast-util-gfm-autolink-literal@0.1.3:
resolution: {integrity: sha512-GjmLjWrXg1wqMIO9+ZsRik/s7PLwTaeCHVB7vRxUwLntZc8mzmTsLVr6HW1yLokcnhfURsn5zmSVdi3/xWWu1A==}
dependencies:
@@ -23475,6 +24189,20 @@ packages:
unist-util-visit: 4.1.2
dev: false
+ /mdast-util-to-hast@13.2.0:
+ resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==}
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ '@ungap/structured-clone': 1.2.0
+ devlop: 1.1.0
+ micromark-util-sanitize-uri: 2.0.0
+ trim-lines: 3.0.1
+ unist-util-position: 5.0.0
+ unist-util-visit: 5.0.0
+ vfile: 6.0.3
+ dev: false
+
/mdast-util-to-hast@4.0.0:
resolution: {integrity: sha512-yOTZSxR1aPvWRUxVeLaLZ1sCYrK87x2Wusp1bDM/Ao2jETBhYUKITI3nHvgy+HkZW54HuCAhHnS0mTcbECD5Ig==}
dependencies:
@@ -23546,7 +24274,6 @@ packages:
resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==}
dependencies:
'@types/mdast': 4.0.4
- dev: true
/mdn-data@2.0.14:
resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==}
@@ -23561,6 +24288,10 @@ packages:
resolution: {integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==}
dev: false
+ /mdurl@2.0.0:
+ resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
+ dev: false
+
/meant@1.0.3:
resolution: {integrity: sha512-88ZRGcNxAq4EH38cQ4D85PM57pikCwS8Z99EWHODxN7KBY+UuPiqzRTtZzS8KTXO/ywSWbdjjJST2Hly/EQxLw==}
@@ -23667,6 +24398,10 @@ packages:
/merge-descriptors@1.0.1:
resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==}
+ /merge-descriptors@1.0.3:
+ resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
+ dev: false
+
/merge-options@3.0.4:
resolution: {integrity: sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==}
engines: {node: '>=10'}
@@ -23750,7 +24485,52 @@ packages:
micromark-util-subtokenize: 2.0.1
micromark-util-symbol: 2.0.0
micromark-util-types: 2.0.0
- dev: true
+
+ /micromark-core-commonmark@2.0.2:
+ resolution: {integrity: sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==}
+ dependencies:
+ decode-named-character-reference: 1.0.2
+ devlop: 1.1.0
+ micromark-factory-destination: 2.0.0
+ micromark-factory-label: 2.0.0
+ micromark-factory-space: 2.0.0
+ micromark-factory-title: 2.0.0
+ micromark-factory-whitespace: 2.0.0
+ micromark-util-character: 2.1.0
+ micromark-util-chunked: 2.0.0
+ micromark-util-classify-character: 2.0.0
+ micromark-util-html-tag-name: 2.0.0
+ micromark-util-normalize-identifier: 2.0.0
+ micromark-util-resolve-all: 2.0.0
+ micromark-util-subtokenize: 2.0.1
+ micromark-util-symbol: 2.0.0
+ micromark-util-types: 2.0.1
+
+ /micromark-extension-directive@3.0.2:
+ resolution: {integrity: sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==}
+ dependencies:
+ devlop: 1.1.0
+ micromark-factory-space: 2.0.0
+ micromark-factory-whitespace: 2.0.0
+ micromark-util-character: 2.1.0
+ micromark-util-symbol: 2.0.0
+ micromark-util-types: 2.0.1
+ parse-entities: 4.0.2
+ dev: false
+
+ /micromark-extension-footnote@0.3.2:
+ resolution: {integrity: sha512-gr/BeIxbIWQoUm02cIfK7mdMZ/fbroRpLsck4kvFtjbzP4yi+OPVbnukTc/zy0i7spC2xYE/dbX1Sur8BEDJsQ==}
+ dependencies:
+ micromark: 2.11.4
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /micromark-extension-frontmatter@0.2.2:
+ resolution: {integrity: sha512-q6nPLFCMTLtfsctAuS0Xh4vaolxSFUWUWR6PZSrXXiRy+SANGllpcqdXFv2z07l0Xz/6Hl40hK0ffNCJPH2n1A==}
+ dependencies:
+ fault: 1.0.4
+ dev: false
/micromark-extension-gfm-autolink-literal@0.5.7:
resolution: {integrity: sha512-ePiDGH0/lhcngCe8FtH4ARFoxKTUelMp4L7Gg2pujYD5CSMb9PbblnyL+AAMud/SNMyusbS2XDSiPIRcQoNFAw==}
@@ -23766,21 +24546,19 @@ packages:
micromark-util-character: 2.1.0
micromark-util-sanitize-uri: 2.0.0
micromark-util-symbol: 2.0.0
- micromark-util-types: 2.0.0
- dev: true
+ micromark-util-types: 2.0.1
/micromark-extension-gfm-footnote@2.1.0:
resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==}
dependencies:
devlop: 1.1.0
- micromark-core-commonmark: 2.0.1
+ micromark-core-commonmark: 2.0.2
micromark-factory-space: 2.0.0
micromark-util-character: 2.1.0
micromark-util-normalize-identifier: 2.0.0
micromark-util-sanitize-uri: 2.0.0
micromark-util-symbol: 2.0.0
- micromark-util-types: 2.0.0
- dev: true
+ micromark-util-types: 2.0.1
/micromark-extension-gfm-strikethrough@0.6.5:
resolution: {integrity: sha512-PpOKlgokpQRwUesRwWEp+fHjGGkZEejj83k9gU5iXCbDG+XBA92BqnRKYJdfqfkrRcZRgGuPuXb7DaK/DmxOhw==}
@@ -23816,8 +24594,7 @@ packages:
micromark-factory-space: 2.0.0
micromark-util-character: 2.1.0
micromark-util-symbol: 2.0.0
- micromark-util-types: 2.0.0
- dev: true
+ micromark-util-types: 2.0.1
/micromark-extension-gfm-tagfilter@0.3.0:
resolution: {integrity: sha512-9GU0xBatryXifL//FJH+tAZ6i240xQuFrSL7mYi8f4oZSbc+NvXjkrHemeYP0+L4ZUT+Ptz3b95zhUZnMtoi/Q==}
@@ -23873,6 +24650,18 @@ packages:
micromark-util-types: 2.0.0
dev: true
+ /micromark-extension-math@3.1.0:
+ resolution: {integrity: sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==}
+ dependencies:
+ '@types/katex': 0.16.7
+ devlop: 1.1.0
+ katex: 0.16.20
+ micromark-factory-space: 2.0.0
+ micromark-util-character: 2.1.0
+ micromark-util-symbol: 2.0.0
+ micromark-util-types: 2.0.1
+ dev: false
+
/micromark-factory-destination@1.1.0:
resolution: {integrity: sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==}
dependencies:
@@ -23887,7 +24676,6 @@ packages:
micromark-util-character: 2.1.0
micromark-util-symbol: 2.0.0
micromark-util-types: 2.0.0
- dev: true
/micromark-factory-label@1.1.0:
resolution: {integrity: sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==}
@@ -23905,7 +24693,6 @@ packages:
micromark-util-character: 2.1.0
micromark-util-symbol: 2.0.0
micromark-util-types: 2.0.0
- dev: true
/micromark-factory-space@1.1.0:
resolution: {integrity: sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==}
@@ -23919,7 +24706,6 @@ packages:
dependencies:
micromark-util-character: 2.1.0
micromark-util-types: 2.0.0
- dev: true
/micromark-factory-title@1.1.0:
resolution: {integrity: sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==}
@@ -23937,7 +24723,6 @@ packages:
micromark-util-character: 2.1.0
micromark-util-symbol: 2.0.0
micromark-util-types: 2.0.0
- dev: true
/micromark-factory-whitespace@1.1.0:
resolution: {integrity: sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==}
@@ -23955,7 +24740,6 @@ packages:
micromark-util-character: 2.1.0
micromark-util-symbol: 2.0.0
micromark-util-types: 2.0.0
- dev: true
/micromark-util-character@1.2.0:
resolution: {integrity: sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==}
@@ -23969,7 +24753,6 @@ packages:
dependencies:
micromark-util-symbol: 2.0.0
micromark-util-types: 2.0.0
- dev: true
/micromark-util-chunked@1.1.0:
resolution: {integrity: sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==}
@@ -23981,7 +24764,6 @@ packages:
resolution: {integrity: sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==}
dependencies:
micromark-util-symbol: 2.0.0
- dev: true
/micromark-util-classify-character@1.1.0:
resolution: {integrity: sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==}
@@ -23997,7 +24779,6 @@ packages:
micromark-util-character: 2.1.0
micromark-util-symbol: 2.0.0
micromark-util-types: 2.0.0
- dev: true
/micromark-util-combine-extensions@1.1.0:
resolution: {integrity: sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==}
@@ -24011,7 +24792,6 @@ packages:
dependencies:
micromark-util-chunked: 2.0.0
micromark-util-types: 2.0.0
- dev: true
/micromark-util-decode-numeric-character-reference@1.1.0:
resolution: {integrity: sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==}
@@ -24023,7 +24803,6 @@ packages:
resolution: {integrity: sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==}
dependencies:
micromark-util-symbol: 2.0.0
- dev: true
/micromark-util-decode-string@1.1.0:
resolution: {integrity: sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==}
@@ -24041,7 +24820,6 @@ packages:
micromark-util-character: 2.1.0
micromark-util-decode-numeric-character-reference: 2.0.1
micromark-util-symbol: 2.0.0
- dev: true
/micromark-util-encode@1.1.0:
resolution: {integrity: sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==}
@@ -24049,7 +24827,6 @@ packages:
/micromark-util-encode@2.0.0:
resolution: {integrity: sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==}
- dev: true
/micromark-util-html-tag-name@1.2.0:
resolution: {integrity: sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==}
@@ -24057,7 +24834,6 @@ packages:
/micromark-util-html-tag-name@2.0.0:
resolution: {integrity: sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==}
- dev: true
/micromark-util-normalize-identifier@1.1.0:
resolution: {integrity: sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==}
@@ -24069,7 +24845,6 @@ packages:
resolution: {integrity: sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==}
dependencies:
micromark-util-symbol: 2.0.0
- dev: true
/micromark-util-resolve-all@1.1.0:
resolution: {integrity: sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==}
@@ -24081,7 +24856,6 @@ packages:
resolution: {integrity: sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==}
dependencies:
micromark-util-types: 2.0.0
- dev: true
/micromark-util-sanitize-uri@1.2.0:
resolution: {integrity: sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==}
@@ -24097,7 +24871,6 @@ packages:
micromark-util-character: 2.1.0
micromark-util-encode: 2.0.0
micromark-util-symbol: 2.0.0
- dev: true
/micromark-util-subtokenize@1.1.0:
resolution: {integrity: sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==}
@@ -24115,7 +24888,6 @@ packages:
micromark-util-chunked: 2.0.0
micromark-util-symbol: 2.0.0
micromark-util-types: 2.0.0
- dev: true
/micromark-util-symbol@1.1.0:
resolution: {integrity: sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==}
@@ -24123,7 +24895,6 @@ packages:
/micromark-util-symbol@2.0.0:
resolution: {integrity: sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==}
- dev: true
/micromark-util-types@1.1.0:
resolution: {integrity: sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==}
@@ -24131,12 +24902,14 @@ packages:
/micromark-util-types@2.0.0:
resolution: {integrity: sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==}
- dev: true
+
+ /micromark-util-types@2.0.1:
+ resolution: {integrity: sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==}
/micromark@2.11.4:
resolution: {integrity: sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==}
dependencies:
- debug: 4.3.7
+ debug: 4.4.0
parse-entities: 2.0.0
transitivePeerDependencies:
- supports-color
@@ -24188,7 +24961,30 @@ packages:
micromark-util-types: 2.0.0
transitivePeerDependencies:
- supports-color
- dev: true
+
+ /micromark@4.0.1:
+ resolution: {integrity: sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==}
+ dependencies:
+ '@types/debug': 4.1.12
+ debug: 4.3.7
+ decode-named-character-reference: 1.0.2
+ devlop: 1.1.0
+ micromark-core-commonmark: 2.0.2
+ micromark-factory-space: 2.0.0
+ micromark-util-character: 2.1.0
+ micromark-util-chunked: 2.0.0
+ micromark-util-combine-extensions: 2.0.0
+ micromark-util-decode-numeric-character-reference: 2.0.1
+ micromark-util-encode: 2.0.0
+ micromark-util-normalize-identifier: 2.0.0
+ micromark-util-resolve-all: 2.0.0
+ micromark-util-sanitize-uri: 2.0.0
+ micromark-util-subtokenize: 2.0.1
+ micromark-util-symbol: 2.0.0
+ micromark-util-types: 2.0.1
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
/micromatch@3.1.10:
resolution: {integrity: sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==}
@@ -24284,6 +25080,13 @@ packages:
resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==}
engines: {node: '>=4'}
+ /mimic-response@2.1.0:
+ resolution: {integrity: sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==}
+ engines: {node: '>=8'}
+ requiresBuild: true
+ dev: false
+ optional: true
+
/mimic-response@3.1.0:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
engines: {node: '>=10'}
@@ -24320,6 +25123,7 @@ packages:
/minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+ requiresBuild: true
dependencies:
brace-expansion: 1.1.11
@@ -24412,6 +25216,7 @@ packages:
/minipass@3.3.6:
resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}
engines: {node: '>=8'}
+ requiresBuild: true
dependencies:
yallist: 4.0.0
dev: false
@@ -24419,6 +25224,7 @@ packages:
/minipass@5.0.0:
resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==}
engines: {node: '>=8'}
+ requiresBuild: true
dev: false
/minipass@7.0.4:
@@ -24433,6 +25239,7 @@ packages:
/minizlib@2.1.2:
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
engines: {node: '>= 8'}
+ requiresBuild: true
dependencies:
minipass: 3.3.6
yallist: 4.0.0
@@ -24463,6 +25270,7 @@ packages:
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
engines: {node: '>=10'}
hasBin: true
+ requiresBuild: true
/mlly@1.6.1:
resolution: {integrity: sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==}
@@ -24669,6 +25477,11 @@ packages:
/neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
+ /neotraverse@0.6.18:
+ resolution: {integrity: sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==}
+ engines: {node: '>= 10'}
+ dev: false
+
/nested-error-stacks@2.1.1:
resolution: {integrity: sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==}
dev: false
@@ -24902,6 +25715,10 @@ packages:
engines: {node: '>=10.5.0'}
dev: false
+ /node-ensure@0.0.0:
+ resolution: {integrity: sha512-DRI60hzo2oKN1ma0ckc6nQWlHU69RH6xN0sjQTjMpChPfTYvKZdcQFfdYK2RWbJcKyUizSIy/l8OTGxMAM1QDw==}
+ dev: false
+
/node-fetch-native@1.6.4:
resolution: {integrity: sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==}
dev: false
@@ -25049,6 +25866,7 @@ packages:
resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
engines: {node: '>=6'}
hasBin: true
+ requiresBuild: true
dependencies:
abbrev: 1.1.1
dev: false
@@ -25141,6 +25959,7 @@ packages:
/npmlog@5.0.1:
resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==}
deprecated: This package is no longer supported.
+ requiresBuild: true
dependencies:
are-we-there-yet: 2.0.0
console-control-strings: 1.1.0
@@ -25179,6 +25998,10 @@ packages:
/nullthrows@1.1.1:
resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==}
+ /nwsapi@2.2.16:
+ resolution: {integrity: sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==}
+ dev: false
+
/nyc@15.1.0:
resolution: {integrity: sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==}
engines: {node: '>=8.9'}
@@ -25218,6 +26041,7 @@ packages:
/object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
+ requiresBuild: true
/object-copy@0.1.0:
resolution: {integrity: sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==}
@@ -25371,6 +26195,7 @@ packages:
/once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+ requiresBuild: true
dependencies:
wrappy: 1.0.2
@@ -25413,6 +26238,26 @@ packages:
is-docker: 2.2.1
is-wsl: 2.2.0
+ /openai@4.78.1:
+ resolution: {integrity: sha512-drt0lHZBd2lMyORckOXFPQTmnGLWSLt8VK0W9BhOKWpMFBEoHMoz5gxMPmVq5icp+sOrsbMnsmZTVHUlKvD1Ow==}
+ hasBin: true
+ peerDependencies:
+ zod: ^3.23.8
+ peerDependenciesMeta:
+ zod:
+ optional: true
+ dependencies:
+ '@types/node': 18.19.70
+ '@types/node-fetch': 2.6.11
+ abort-controller: 3.0.0
+ agentkeepalive: 4.5.0
+ form-data-encoder: 1.7.2
+ formdata-node: 4.4.1
+ node-fetch: 2.7.0
+ transitivePeerDependencies:
+ - encoding
+ dev: false
+
/opentracing@0.14.7:
resolution: {integrity: sha512-vz9iS7MJ5+Bp1URw8Khvdyw1H/hGvzHWlKQ7eRrQojSCDL1/SrWfrY9QebLw97n2deyRtzHRC3MkQfVNUCo91Q==}
engines: {node: '>=0.10'}
@@ -25422,6 +26267,10 @@ packages:
dependencies:
'@wry/context': 0.4.4
+ /option@0.2.4:
+ resolution: {integrity: sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A==}
+ dev: false
+
/optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
@@ -25716,6 +26565,10 @@ packages:
registry-url: 6.0.1
semver: 7.6.3
+ /pako@1.0.11:
+ resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
+ dev: false
+
/pako@2.1.0:
resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==}
dev: false
@@ -25762,6 +26615,18 @@ packages:
is-hexadecimal: 1.0.4
dev: false
+ /parse-entities@4.0.2:
+ resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==}
+ dependencies:
+ '@types/unist': 2.0.10
+ character-entities-legacy: 3.0.0
+ character-reference-invalid: 2.0.1
+ decode-named-character-reference: 1.0.2
+ is-alphanumerical: 2.0.1
+ is-decimal: 2.0.1
+ is-hexadecimal: 2.0.1
+ dev: false
+
/parse-filepath@1.0.2:
resolution: {integrity: sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==}
engines: {node: '>=0.8'}
@@ -25818,6 +26683,10 @@ packages:
dependencies:
protocols: 2.0.1
+ /parse-srcset@1.0.2:
+ resolution: {integrity: sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==}
+ dev: false
+
/parse-url@8.1.0:
resolution: {integrity: sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==}
dependencies:
@@ -25842,7 +26711,6 @@ packages:
resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
dependencies:
entities: 4.5.0
- dev: true
/parseurl@1.3.3:
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
@@ -25932,6 +26800,10 @@ packages:
lru-cache: 10.4.3
minipass: 7.1.2
+ /path-to-regexp@0.1.12:
+ resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==}
+ dev: false
+
/path-to-regexp@0.1.7:
resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==}
@@ -25977,6 +26849,15 @@ packages:
resolve-protobuf-schema: 2.1.0
dev: false
+ /pdf2pic@3.1.3:
+ resolution: {integrity: sha512-KbW4Qb7iHw2fBRWtA9FTc4pZg9cokiFIzc6cE7dzelTrhXWolfQuG1fYVC0E2BRmK/w7xfBjQ+OEsPZPO3QEew==}
+ engines: {node: '>=14'}
+ dependencies:
+ gm: 1.25.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
/peek-readable@4.1.0:
resolution: {integrity: sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==}
engines: {node: '>=8'}
@@ -27243,6 +28124,11 @@ packages:
end-of-stream: 1.4.4
once: 1.4.0
+ /punycode.js@2.3.1:
+ resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
+ engines: {node: '>=6'}
+ dev: false
+
/punycode@1.4.1:
resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==}
@@ -27292,6 +28178,13 @@ packages:
side-channel: 1.0.6
dev: false
+ /qs@6.13.0:
+ resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
+ engines: {node: '>=0.6'}
+ dependencies:
+ side-channel: 1.0.6
+ dev: false
+
/query-string@6.14.1:
resolution: {integrity: sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==}
engines: {node: '>=6'}
@@ -28527,6 +29420,22 @@ packages:
es6-error: 4.1.1
dev: true
+ /remark-footnotes@3.0.0:
+ resolution: {integrity: sha512-ZssAvH9FjGYlJ/PBVKdSmfyPc3Cz4rTWgZLI4iE/SX8Nt5l3o3oEjv3wwG5VD7xOjktzdwp5coac+kJV9l4jgg==}
+ dependencies:
+ mdast-util-footnote: 0.1.7
+ micromark-extension-footnote: 0.3.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
+ /remark-frontmatter@3.0.0:
+ resolution: {integrity: sha512-mSuDd3svCHs+2PyO29h7iijIZx4plX0fheacJcAoYAASfgzgVIcXGYSq9GFyYocFLftQs8IOmmkgtOovs6d4oA==}
+ dependencies:
+ mdast-util-frontmatter: 0.2.0
+ micromark-extension-frontmatter: 0.2.2
+ dev: false
+
/remark-gfm@1.0.0:
resolution: {integrity: sha512-KfexHJCiqvrdBZVbQ6RopMZGwaXz6wFJEfByIuEwGf0arvITHjiKKZ1dpXujjH9KZdm1//XJQwgfnJ3lmXaDPA==}
dependencies:
@@ -28920,6 +29829,14 @@ packages:
'@rollup/rollup-win32-x64-msvc': 4.14.1
fsevents: 2.3.3
+ /rrweb-cssom@0.7.1:
+ resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==}
+ dev: false
+
+ /rrweb-cssom@0.8.0:
+ resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==}
+ dev: false
+
/run-async@2.4.1:
resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
engines: {node: '>=0.12.0'}
@@ -29006,10 +29923,28 @@ packages:
dependencies:
truncate-utf8-bytes: 1.0.2
+ /sanitize-html@2.13.1:
+ resolution: {integrity: sha512-ZXtKq89oue4RP7abL9wp/9URJcqQNABB5GGJ2acW1sdO8JTVl92f4ygD7Yc9Ze09VAZhnt2zegeU0tbNsdcLYg==}
+ dependencies:
+ deepmerge: 4.3.1
+ escape-string-regexp: 4.0.0
+ htmlparser2: 8.0.2
+ is-plain-object: 5.0.0
+ parse-srcset: 1.0.2
+ postcss: 8.4.49
+ dev: false
+
/sax@1.3.0:
resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==}
dev: false
+ /saxes@6.0.0:
+ resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
+ engines: {node: '>=v12.22.7'}
+ dependencies:
+ xmlchars: 2.2.0
+ dev: false
+
/scheduler@0.20.2:
resolution: {integrity: sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==}
dependencies:
@@ -29111,6 +30046,7 @@ packages:
/semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
+ requiresBuild: true
/semver@7.5.4:
resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==}
@@ -29131,6 +30067,7 @@ packages:
resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==}
engines: {node: '>=10'}
hasBin: true
+ requiresBuild: true
/send@0.18.0:
resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
@@ -29152,6 +30089,27 @@ packages:
transitivePeerDependencies:
- supports-color
+ /send@0.19.0:
+ resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}
+ engines: {node: '>= 0.8.0'}
+ dependencies:
+ debug: 2.6.9
+ depd: 2.0.0
+ destroy: 1.2.0
+ encodeurl: 1.0.2
+ escape-html: 1.0.3
+ etag: 1.8.1
+ fresh: 0.5.2
+ http-errors: 2.0.0
+ mime: 1.6.0
+ ms: 2.1.3
+ on-finished: 2.4.1
+ range-parser: 1.2.1
+ statuses: 2.0.1
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
/sentence-case@3.0.4:
resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==}
dependencies:
@@ -29252,6 +30210,18 @@ packages:
transitivePeerDependencies:
- supports-color
+ /serve-static@1.16.2:
+ resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
+ engines: {node: '>= 0.8.0'}
+ dependencies:
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ parseurl: 1.3.3
+ send: 0.19.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: false
+
/serve@14.2.1:
resolution: {integrity: sha512-48er5fzHh7GCShLnNyPBRPEjs2I6QBozeGr02gaacROiyS/8ARADlj595j39iZXAqBbJHH/ivJJyPRWY9sQWZA==}
engines: {node: '>= 14'}
@@ -29282,6 +30252,7 @@ packages:
/set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
+ requiresBuild: true
/set-cookie-parser@2.6.0:
resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==}
@@ -29409,6 +30380,7 @@ packages:
/signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
+ requiresBuild: true
/signal-exit@4.1.0:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
@@ -29419,8 +30391,19 @@ packages:
/simple-concat@1.0.1:
resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
+ requiresBuild: true
dev: false
+ /simple-get@3.1.1:
+ resolution: {integrity: sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==}
+ requiresBuild: true
+ dependencies:
+ decompress-response: 4.2.1
+ once: 1.4.0
+ simple-concat: 1.0.1
+ dev: false
+ optional: true
+
/simple-get@4.0.1:
resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
dependencies:
@@ -30118,6 +31101,7 @@ packages:
/string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
+ requiresBuild: true
dependencies:
emoji-regex: 8.0.0
is-fullwidth-code-point: 3.0.0
@@ -30238,6 +31222,7 @@ packages:
/strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
+ requiresBuild: true
dependencies:
ansi-regex: 5.0.1
@@ -30472,6 +31457,10 @@ packages:
resolution: {integrity: sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==}
engines: {node: '>=0.10.0'}
+ /symbol-tree@3.2.4:
+ resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
+ dev: false
+
/system-architecture@0.1.0:
resolution: {integrity: sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==}
engines: {node: '>=18'}
@@ -30610,6 +31599,7 @@ packages:
/tar@6.2.1:
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
engines: {node: '>=10'}
+ requiresBuild: true
dependencies:
chownr: 2.0.0
fs-minipass: 2.1.0
@@ -30921,6 +31911,17 @@ packages:
dependencies:
tslib: 2.6.2
+ /tldts-core@6.1.71:
+ resolution: {integrity: sha512-LRbChn2YRpic1KxY+ldL1pGXN/oVvKfCVufwfVzEQdFYNo39uF7AJa/WXdo+gYO7PTvdfkCPCed6Hkvz/kR7jg==}
+ dev: false
+
+ /tldts@6.1.71:
+ resolution: {integrity: sha512-LQIHmHnuzfZgZWAf2HzL83TIIrD8NhhI0DVxqo9/FdOd4ilec+NTNZOlDZf7EwrTNoutccbsHjvWHYXLAtvxjw==}
+ hasBin: true
+ dependencies:
+ tldts-core: 6.1.71
+ dev: false
+
/tmp-promise@3.0.3:
resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==}
dependencies:
@@ -31020,6 +32021,13 @@ packages:
engines: {node: '>=6'}
dev: true
+ /tough-cookie@5.1.0:
+ resolution: {integrity: sha512-rvZUv+7MoBYTiDmFPBrhL7Ujx9Sk+q9wwm22x8c8T5IJaR+Wsyc7TNxbVxo84kZoRJZZMazowFLqpankBEQrGg==}
+ engines: {node: '>=16'}
+ dependencies:
+ tldts: 6.1.71
+ dev: false
+
/tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
@@ -31029,6 +32037,13 @@ packages:
punycode: 2.3.1
dev: true
+ /tr46@5.0.0:
+ resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==}
+ engines: {node: '>=18'}
+ dependencies:
+ punycode: 2.3.1
+ dev: false
+
/tree-kill@1.2.2:
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
hasBin: true
@@ -31327,6 +32342,12 @@ packages:
turbo-windows-arm64: 2.0.6
dev: true
+ /turndown@7.2.0:
+ resolution: {integrity: sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A==}
+ dependencies:
+ '@mixmark-io/domino': 2.2.0
+ dev: false
+
/type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
@@ -31476,6 +32497,10 @@ packages:
/ua-parser-js@1.0.37:
resolution: {integrity: sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==}
+ /uc.micro@2.1.0:
+ resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
+ dev: false
+
/ufo@1.5.3:
resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==}
@@ -31487,6 +32512,10 @@ packages:
dev: true
optional: true
+ /uhyphen@0.2.0:
+ resolution: {integrity: sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==}
+ dev: false
+
/uid-safe@2.1.5:
resolution: {integrity: sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==}
engines: {node: '>= 0.8'}
@@ -31522,12 +32551,15 @@ packages:
resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==}
dev: false
+ /underscore@1.13.7:
+ resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==}
+ dev: false
+
/undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
/undici-types@6.19.8:
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
- dev: true
/unenv@1.9.0:
resolution: {integrity: sha512-QKnFNznRxmbOF1hDgzpqrlIf6NC5sbZ2OJ+5Wl3OX8uM+LUJXbj4TXvLJCtwbPTmbMHCLIz6JLKNinNsMShK9g==}
@@ -31692,7 +32724,6 @@ packages:
resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==}
dependencies:
'@types/unist': 3.0.2
- dev: true
/unist-util-position@3.1.0:
resolution: {integrity: sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA==}
@@ -31704,6 +32735,12 @@ packages:
'@types/unist': 2.0.10
dev: false
+ /unist-util-position@5.0.0:
+ resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==}
+ dependencies:
+ '@types/unist': 3.0.2
+ dev: false
+
/unist-util-remove-position@1.1.4:
resolution: {integrity: sha512-tLqd653ArxJIPnKII6LMZwH+mb5q+n/GtXQZo6S6csPRs5zB0u79Yw8ouR3wTw8wxvdJFhpP6Y7jorWdCgLO0A==}
dependencies:
@@ -31726,7 +32763,6 @@ packages:
resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
dependencies:
'@types/unist': 3.0.2
- dev: true
/unist-util-visit-parents@2.1.2:
resolution: {integrity: sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==}
@@ -31753,7 +32789,6 @@ packages:
dependencies:
'@types/unist': 3.0.2
unist-util-is: 6.0.0
- dev: true
/unist-util-visit@1.4.1:
resolution: {integrity: sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==}
@@ -31783,7 +32818,6 @@ packages:
'@types/unist': 3.0.2
unist-util-is: 6.0.0
unist-util-visit-parents: 6.0.1
- dev: true
/universal-user-agent@6.0.1:
resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==}
@@ -31809,6 +32843,15 @@ packages:
dependencies:
normalize-path: 2.1.1
+ /unpdf@0.12.1:
+ resolution: {integrity: sha512-ktP8+TTLDBrlu/j8rQVNbHoMMpFXzkVAkb1rt/JdshFC3jOHdZjuGCNl/voPL0kraUrUOH7ZC88kVxMvlvDBzA==}
+ optionalDependencies:
+ canvas: 2.11.2
+ transitivePeerDependencies:
+ - encoding
+ - supports-color
+ dev: false
+
/unpipe@1.0.0:
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
engines: {node: '>= 0.8'}
@@ -32185,7 +33228,6 @@ packages:
dependencies:
'@types/unist': 3.0.2
unist-util-stringify-position: 4.0.0
- dev: true
/vfile@4.2.1:
resolution: {integrity: sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA==}
@@ -32210,7 +33252,6 @@ packages:
dependencies:
'@types/unist': 3.0.2
vfile-message: 4.0.2
- dev: true
/vite-imagetools@6.2.9:
resolution: {integrity: sha512-C4ZYhgj2vAj43/TpZ06XlDNP0p/7LIeYbgUYr+xG44nM++4HGX6YZBKAYpiBNgiCFUTJ6eXkRppWBrfPMevgmg==}
@@ -32657,6 +33698,13 @@ packages:
- terser
dev: true
+ /w3c-xmlserializer@5.0.0:
+ resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
+ engines: {node: '>=18'}
+ dependencies:
+ xml-name-validator: 5.0.0
+ dev: false
+
/wait-on@7.2.0:
resolution: {integrity: sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==}
engines: {node: '>=12.0.0'}
@@ -32753,6 +33801,11 @@ packages:
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
engines: {node: '>= 8'}
+ /web-streams-polyfill@4.0.0-beta.3:
+ resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==}
+ engines: {node: '>= 14'}
+ dev: false
+
/web-worker@1.3.0:
resolution: {integrity: sha512-BSR9wyRsy/KOValMgd5kMyr3JzpdeoR9KVId8u5GVlTTAtNChlsE4yTxeY7zMdNSyOmoKBv8NH2qeRY9Tg+IaA==}
dev: false
@@ -33029,6 +34082,13 @@ packages:
dependencies:
iconv-lite: 0.6.3
+ /whatwg-encoding@3.1.1:
+ resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
+ engines: {node: '>=18'}
+ dependencies:
+ iconv-lite: 0.6.3
+ dev: false
+
/whatwg-fetch@3.6.20:
resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==}
dev: false
@@ -33037,6 +34097,19 @@ packages:
resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==}
engines: {node: '>=12'}
+ /whatwg-mimetype@4.0.0:
+ resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
+ engines: {node: '>=18'}
+ dev: false
+
+ /whatwg-url@14.1.0:
+ resolution: {integrity: sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==}
+ engines: {node: '>=18'}
+ dependencies:
+ tr46: 5.0.0
+ webidl-conversions: 7.0.0
+ dev: false
+
/whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
dependencies:
@@ -33122,6 +34195,7 @@ packages:
/wide-align@1.1.5:
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
+ requiresBuild: true
dependencies:
string-width: 4.2.3
dev: false
@@ -33221,6 +34295,7 @@ packages:
/wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+ requiresBuild: true
/write-file-atomic@3.0.3:
resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==}
@@ -33295,6 +34370,19 @@ packages:
utf-8-validate:
optional: true
+ /ws@8.18.0:
+ resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==}
+ 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
+ dev: false
+
/xdg-basedir@4.0.0:
resolution: {integrity: sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==}
engines: {node: '>=8'}
@@ -33304,6 +34392,11 @@ packages:
engines: {node: '>=12'}
dev: false
+ /xml-name-validator@5.0.0:
+ resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
+ engines: {node: '>=18'}
+ dev: false
+
/xml-utils@1.8.0:
resolution: {integrity: sha512-1TY5yLw8DApowZAUsWCniNr8HH6Ebt6O7UQvmIwziGKwUNsQx6e+4NkfOvCfnqmYIcPjCeoI6dh1JenPJ9a1hQ==}
dev: false
@@ -33312,11 +34405,20 @@ packages:
resolution: {integrity: sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==}
dev: true
+ /xmlbuilder@10.1.1:
+ resolution: {integrity: sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==}
+ engines: {node: '>=4.0'}
+ dev: false
+
/xmlbuilder@15.1.1:
resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==}
engines: {node: '>=8.0'}
dev: true
+ /xmlchars@2.2.0:
+ resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
+ dev: false
+
/xmlhttprequest-ssl@2.0.0:
resolution: {integrity: sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==}
engines: {node: '>=0.4.0'}
@@ -33355,6 +34457,7 @@ packages:
/yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
+ requiresBuild: true
/yaml-ast-parser@0.0.43:
resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==}
@@ -33556,3 +34659,15 @@ packages:
/zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
+
+ github.com/iamh2o/pdf-parse/d7a41d5aaed1503bee2d7ea50bf89588d3b2d2cf:
+ resolution: {tarball: https://codeload.github.com/iamh2o/pdf-parse/tar.gz/d7a41d5aaed1503bee2d7ea50bf89588d3b2d2cf}
+ name: pdf-parse
+ version: 1.1.3
+ engines: {node: '>=6.8.1'}
+ dependencies:
+ debug: 3.2.7
+ node-ensure: 0.0.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: false