diff --git a/.changeset/quick-meals-send.md b/.changeset/quick-meals-send.md
new file mode 100644
index 00000000..c112993e
--- /dev/null
+++ b/.changeset/quick-meals-send.md
@@ -0,0 +1,26 @@
+---
+'quickmock': minor
+'@lemoncode/quickmock-mcp': minor
+---
+
+First public release of the QuickMock VS Code extension and its MCP server.
+
+**`quickmock` (VS Code extension)**
+
+- Custom editor for `.qm` files backed by the QuickMock web app, served inside a webview.
+
+- `quickmock.appUrl` setting (default `https://quickmock.net/editor.html`) to point the editor and the MCP renderer at any QuickMock instance. Changes refresh open editors and respawn the MCP server.
+
+- Automatic MCP server registration for VS Code / GitHub Copilot, Claude Code, Cursor, Windsurf and Claude Desktop, plus a dynamic `McpServerDefinitionProvider`. Existing entries are refreshed on activation so users always end up pointing at the right MCP invocation.
+
+- The MCP server is no longer bundled inside the `.vsix`. In production the extension spawns it on demand via `npx -y @lemoncode/quickmock-mcp`, so users always run the latest published MCP without waiting for an extension release. In development it resolves the local workspace build.
+
+**`@lemoncode/quickmock-mcp` (MCP server)**
+
+- MCP tools to explore and render wireframes: `list_wireframes`, `get_wireframe_json`, `get_wireframe_pages`, `get_wireframe_assets` and `capture_wireframe`.
+
+- Headless screenshot pipeline via `puppeteer-core` against the QuickMock app, using a postMessage bridge.
+
+- On-demand Chromium download via `@puppeteer/browsers`, cached under `~/.quickmock/browsers`, so headless rendering works without relying on the user's local browser install.
+
+- Reads the target app URL from `~/.quickmock/app-url` (written by the extension) with a production fallback, so the MCP works out of the box regardless of how it is spawned.
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 08f7bec1..8fc4865d 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -5,5 +5,6 @@
"editor.codeActionsOnSave": {
"source.organizeImports": "always",
"source.removeUnusedImports": "always"
- }
+ },
+ "quickmock.appUrl": "http://localhost:5173/editor.html"
}
diff --git a/apps/web/package.json b/apps/web/package.json
index fefcefe2..ef2f2b64 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -20,6 +20,7 @@
},
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "1.7.10",
+ "@lemoncode/quickmock-bridge-protocol": "*",
"@fontsource-variable/montserrat": "5.0.20",
"@fontsource/balsamiq-sans": "5.0.21",
"@uiw/react-color-chrome": "2.10.1",
diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx
index d2d93317..977a45d8 100644
--- a/apps/web/src/App.tsx
+++ b/apps/web/src/App.tsx
@@ -1,7 +1,10 @@
import { ModalDialogComponent } from './common/components/modal-dialog';
+import { useVSCodeSync } from '#core/vscode/use-vscode-sync.hook';
import { MainScene } from './scenes/main.scene';
function App() {
+ useVSCodeSync();
+
return (
<>
diff --git a/apps/web/src/common/utils/compute-content-bbox.utils.ts b/apps/web/src/common/utils/compute-content-bbox.utils.ts
new file mode 100644
index 00000000..f6a51709
--- /dev/null
+++ b/apps/web/src/common/utils/compute-content-bbox.utils.ts
@@ -0,0 +1,29 @@
+import type { ContentBbox } from '@lemoncode/quickmock-bridge-protocol';
+import type { useCanvasContext } from '#core/providers';
+
+const CONTENT_PADDING = 16;
+
+export function computeContentBbox(
+ shapes: ReturnType['shapes'],
+ stageRef: ReturnType['stageRef']
+): ContentBbox | undefined {
+ const stage = stageRef.current;
+ if (!stage || shapes.length === 0) return undefined;
+
+ const scale = stage.scaleX();
+ const stageX = stage.x();
+ const stageY = stage.y();
+ const container = stage.container().getBoundingClientRect();
+
+ const minX = Math.min(...shapes.map(s => s.x));
+ const minY = Math.min(...shapes.map(s => s.y));
+ const maxX = Math.max(...shapes.map(s => s.x + s.width));
+ const maxY = Math.max(...shapes.map(s => s.y + s.height));
+
+ return {
+ x: Math.max(0, container.left + stageX + minX * scale - CONTENT_PADDING),
+ y: Math.max(0, container.top + stageY + minY * scale - CONTENT_PADDING),
+ width: (maxX - minX) * scale + CONTENT_PADDING * 2,
+ height: (maxY - minY) * scale + CONTENT_PADDING * 2,
+ };
+}
diff --git a/apps/web/src/common/utils/env.utils.ts b/apps/web/src/common/utils/env.utils.ts
new file mode 100644
index 00000000..f0ce1e96
--- /dev/null
+++ b/apps/web/src/common/utils/env.utils.ts
@@ -0,0 +1,7 @@
+export const isVSCodeEnv = (): boolean => {
+ return new URLSearchParams(window.location.search).get('env') === 'vscode';
+};
+
+export const isHeadlessEnv = (): boolean => {
+ return new URLSearchParams(window.location.search).get('headless') === '1';
+};
diff --git a/apps/web/src/common/utils/vscode-bridge.utils.ts b/apps/web/src/common/utils/vscode-bridge.utils.ts
new file mode 100644
index 00000000..b6b41376
--- /dev/null
+++ b/apps/web/src/common/utils/vscode-bridge.utils.ts
@@ -0,0 +1,52 @@
+import type {
+ AppMessage,
+ HostMessage,
+ PayloadOf,
+} from '@lemoncode/quickmock-bridge-protocol';
+import { isVSCodeEnv } from './env.utils';
+
+type HandlerFor = (
+ payload: PayloadOf
+) => void;
+
+type AnyHandler = (payload: unknown) => void;
+
+const handlers = new Map>();
+
+export const sendToExtension = (msg: AppMessage): void => {
+ if (!isVSCodeEnv()) return;
+ window.parent.postMessage(msg, '*');
+};
+
+export const onMessage = (
+ type: T,
+ handler: HandlerFor
+): (() => void) => {
+ if (!isVSCodeEnv()) return () => {};
+
+ const existing = handlers.get(type) ?? new Set();
+ existing.add(handler as AnyHandler);
+ handlers.set(type, existing);
+
+ return () => {
+ const set = handlers.get(type);
+ if (!set) return;
+ set.delete(handler as AnyHandler);
+ if (set.size === 0) handlers.delete(type);
+ };
+};
+
+if (isVSCodeEnv()) {
+ window.addEventListener('message', (event: MessageEvent) => {
+ if (event.source !== window.parent) return;
+
+ const msg = event.data as Partial | undefined;
+ if (!msg?.type) return;
+
+ const set = handlers.get(msg.type);
+ if (!set) return;
+
+ const payload = (msg as { payload?: unknown }).payload;
+ for (const handler of set) handler(payload);
+ });
+}
diff --git a/apps/web/src/core/vscode/use-headless-render-complete.hook.ts b/apps/web/src/core/vscode/use-headless-render-complete.hook.ts
new file mode 100644
index 00000000..c271ce6f
--- /dev/null
+++ b/apps/web/src/core/vscode/use-headless-render-complete.hook.ts
@@ -0,0 +1,34 @@
+import { computeContentBbox } from '#common/utils/compute-content-bbox.utils.ts';
+import { isHeadlessEnv } from '#common/utils/env.utils.ts';
+import { sendToExtension } from '#common/utils/vscode-bridge.utils.ts';
+import { useCanvasContext } from '#core/providers';
+import { APP_MESSAGE_TYPE } from '@lemoncode/quickmock-bridge-protocol';
+import { useEffect } from 'react';
+
+export function useHeadlessRenderComplete(hasReceivedFileRef: {
+ current: boolean;
+}): void {
+ const { howManyLoadedDocuments, shapes, stageRef } = useCanvasContext();
+
+ useEffect(() => {
+ if (!isHeadlessEnv() || !hasReceivedFileRef.current) return;
+
+ let innerRafId = 0;
+ // Double rAF: the first frame runs after React commits; the second waits
+ // for Konva to paint the updated canvas, so Puppeteer's screenshot reflects it.
+ // There was a previous issue when the canvas was blank because the screenshot ran before Konva painted.
+ const outerRafId = requestAnimationFrame(() => {
+ innerRafId = requestAnimationFrame(() => {
+ sendToExtension({
+ type: APP_MESSAGE_TYPE.RENDER_COMPLETE,
+ payload: computeContentBbox(shapes, stageRef),
+ });
+ });
+ });
+
+ return () => {
+ cancelAnimationFrame(outerRafId);
+ cancelAnimationFrame(innerRafId);
+ };
+ }, [howManyLoadedDocuments]);
+}
diff --git a/apps/web/src/core/vscode/use-vscode-auto-save.hook.ts b/apps/web/src/core/vscode/use-vscode-auto-save.hook.ts
new file mode 100644
index 00000000..47a9437d
--- /dev/null
+++ b/apps/web/src/core/vscode/use-vscode-auto-save.hook.ts
@@ -0,0 +1,49 @@
+import { isHeadlessEnv, isVSCodeEnv } from '#common/utils/env.utils.ts';
+import { sendToExtension } from '#common/utils/vscode-bridge.utils.ts';
+import { useCanvasContext } from '#core/providers';
+import { APP_MESSAGE_TYPE } from '@lemoncode/quickmock-bridge-protocol';
+import { useEffect, useRef } from 'react';
+import { serializeDocument } from './vscode-sync.utils';
+
+const AUTO_SAVE_DEBOUNCE_MS = 500;
+
+export function useVSCodeAutoSave(hasReceivedFileRef: {
+ current: boolean;
+}): void {
+ const { fullDocument, howManyLoadedDocuments } = useCanvasContext();
+
+ const prevLoadCountRef = useRef(howManyLoadedDocuments);
+ const lastSavedContentRef = useRef('');
+ const debounceTimerRef = useRef | null>(null);
+
+ useEffect(() => {
+ if (!isVSCodeEnv() || isHeadlessEnv() || !hasReceivedFileRef.current)
+ return;
+
+ if (prevLoadCountRef.current !== howManyLoadedDocuments) {
+ prevLoadCountRef.current = howManyLoadedDocuments;
+ lastSavedContentRef.current = serializeDocument(fullDocument);
+ return;
+ }
+
+ const content = serializeDocument(fullDocument);
+
+ if (content === lastSavedContentRef.current) return;
+
+ debounceTimerRef.current = setTimeout(() => {
+ sendToExtension({
+ type: APP_MESSAGE_TYPE.SAVE,
+ payload: { content },
+ });
+ lastSavedContentRef.current = content;
+ debounceTimerRef.current = null;
+ }, AUTO_SAVE_DEBOUNCE_MS);
+
+ return () => {
+ if (debounceTimerRef.current !== null) {
+ clearTimeout(debounceTimerRef.current);
+ debounceTimerRef.current = null;
+ }
+ };
+ }, [fullDocument, howManyLoadedDocuments]);
+}
diff --git a/apps/web/src/core/vscode/use-vscode-file-load.hook.ts b/apps/web/src/core/vscode/use-vscode-file-load.hook.ts
new file mode 100644
index 00000000..dd4c0975
--- /dev/null
+++ b/apps/web/src/core/vscode/use-vscode-file-load.hook.ts
@@ -0,0 +1,52 @@
+import { isHeadlessEnv, isVSCodeEnv } from '#common/utils/env.utils.ts';
+import {
+ onMessage,
+ sendToExtension,
+} from '#common/utils/vscode-bridge.utils.ts';
+import { QuickMockFileContract } from '#core/local-disk/local-disk.model';
+import { useCanvasContext } from '#core/providers';
+import {
+ APP_MESSAGE_TYPE,
+ HOST_MESSAGE_TYPE,
+ type LoadFilePayload,
+} from '@lemoncode/quickmock-bridge-protocol';
+import { useEffect, useRef } from 'react';
+import { deserializeDocument } from './vscode-sync.utils';
+
+export function useVSCodeFileLoad(): { current: boolean } {
+ const { loadDocument, setFileName } = useCanvasContext();
+
+ const loadDocumentRef = useRef(loadDocument);
+ const setFileNameRef = useRef(setFileName);
+ useEffect(() => {
+ loadDocumentRef.current = loadDocument;
+ setFileNameRef.current = setFileName;
+ });
+
+ const hasReceivedFileRef = useRef(false);
+
+ useEffect(() => {
+ if (!isVSCodeEnv()) return;
+
+ const unsubscribe = onMessage(
+ HOST_MESSAGE_TYPE.LOAD_FILE,
+ (payload: LoadFilePayload) => {
+ hasReceivedFileRef.current = true;
+ setFileNameRef.current(payload.fileName);
+ loadDocumentRef.current(
+ deserializeDocument(payload.data as QuickMockFileContract)
+ );
+ }
+ );
+
+ sendToExtension({
+ type: isHeadlessEnv()
+ ? APP_MESSAGE_TYPE.READY
+ : APP_MESSAGE_TYPE.WEBVIEW_READY,
+ });
+
+ return unsubscribe;
+ }, []);
+
+ return hasReceivedFileRef;
+}
diff --git a/apps/web/src/core/vscode/use-vscode-sync.hook.ts b/apps/web/src/core/vscode/use-vscode-sync.hook.ts
new file mode 100644
index 00000000..ce07fd9e
--- /dev/null
+++ b/apps/web/src/core/vscode/use-vscode-sync.hook.ts
@@ -0,0 +1,13 @@
+import { useHeadlessRenderComplete } from './use-headless-render-complete.hook';
+import { useVSCodeAutoSave } from './use-vscode-auto-save.hook';
+import { useVSCodeFileLoad } from './use-vscode-file-load.hook';
+
+/**
+ * Wires the full VS Code webview bridge. Each inner hook no-ops when not
+ * running inside a webview, so this can be called unconditionally.
+ */
+export function useVSCodeSync(): void {
+ const hasReceivedFileRef = useVSCodeFileLoad();
+ useVSCodeAutoSave(hasReceivedFileRef);
+ useHeadlessRenderComplete(hasReceivedFileRef);
+}
diff --git a/apps/web/src/core/vscode/vscode-sync.utils.ts b/apps/web/src/core/vscode/vscode-sync.utils.ts
new file mode 100644
index 00000000..9a41fb24
--- /dev/null
+++ b/apps/web/src/core/vscode/vscode-sync.utils.ts
@@ -0,0 +1,17 @@
+import { QuickMockFileContract } from '#core/local-disk/local-disk.model';
+import {
+ mapFromQuickMockFileDocumentToApplicationDocument,
+ mapFromQuickMockFileDocumentToApplicationDocumentV0_1,
+ mapFromShapesArrayToQuickMockFileDocument,
+} from '#core/local-disk/shapes-to-document.mapper';
+import { DocumentModel } from '#core/providers/canvas/canvas.model';
+
+export function deserializeDocument(data: QuickMockFileContract) {
+ return data.version === '0.1'
+ ? mapFromQuickMockFileDocumentToApplicationDocumentV0_1(data)
+ : mapFromQuickMockFileDocumentToApplicationDocument(data);
+}
+
+export function serializeDocument(document: DocumentModel): string {
+ return JSON.stringify(mapFromShapesArrayToQuickMockFileDocument(document));
+}
diff --git a/apps/web/src/scenes/main.scene.tsx b/apps/web/src/scenes/main.scene.tsx
index aa2eef41..8cb9fc2c 100644
--- a/apps/web/src/scenes/main.scene.tsx
+++ b/apps/web/src/scenes/main.scene.tsx
@@ -1,27 +1,36 @@
import { MainLayout } from '#layout/main.layout';
import classes from './main.module.css';
+import { isHeadlessEnv } from '#common/utils/env.utils.ts';
+import { useInteractionModeContext } from '#core/providers';
import {
+ BasicShapesGalleryPod,
CanvasPod,
- ToolbarPod,
- ContainerGalleryPod,
ComponentGalleryPod,
- BasicShapesGalleryPod,
+ ContainerGalleryPod,
+ LowWireframeGalleryPod,
RichComponentsGalleryPod,
TextComponetGalleryPod,
- LowWireframeGalleryPod,
+ ToolbarPod,
} from '#pods';
-import { PropertiesPod } from '#pods/properties';
import { FooterPod } from '#pods/footer/footer.pod';
+import { PropertiesPod } from '#pods/properties';
import { ThumbPagesPod } from '#pods/thumb-pages';
import { useAccordionSectionVisibility } from './accordion-section-visibility.hook';
-import { useInteractionModeContext } from '#core/providers';
export const MainScene = () => {
const { isThumbPagesPodOpen, thumbPagesPodRef } =
useAccordionSectionVisibility();
const { interactionMode } = useInteractionModeContext();
+ if (isHeadlessEnv()) {
+ return (
+
+
+
+ );
+ }
+
return (
{interactionMode === 'view' && (
diff --git a/local-npm-registry/config/config.yaml b/local-npm-registry/config/config.yaml
index 1baba65b..02a5f8a8 100644
--- a/local-npm-registry/config/config.yaml
+++ b/local-npm-registry/config/config.yaml
@@ -1,4 +1,9 @@
-storage: ./storage
+storage: /verdaccio/storage
+
+auth:
+ htpasswd:
+ file: /verdaccio/storage/htpasswd
+ max_users: 1000
uplinks:
npmjs:
@@ -6,9 +11,9 @@ uplinks:
packages:
'@lemoncode/*':
- access: $anonymous
- publish: $anonymous
+ access: $all
+ publish: $authenticated
'**':
- access: $anonymous
- publish: $anonymous
+ access: $all
+ publish: $authenticated
proxy: npmjs
diff --git a/package-lock.json b/package-lock.json
index 27781100..e6d8ccb9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -33,6 +33,7 @@
"@atlaskit/pragmatic-drag-and-drop": "1.7.10",
"@fontsource-variable/montserrat": "5.0.20",
"@fontsource/balsamiq-sans": "5.0.21",
+ "@lemoncode/quickmock-bridge-protocol": "*",
"@uiw/react-color-chrome": "2.10.1",
"html2canvas": "1.4.1",
"immer": "10.1.1",
@@ -405,6 +406,7 @@
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=6.9.0"
}
@@ -736,40 +738,6 @@
"sisteransi": "^1.0.5"
}
},
- "node_modules/@emnapi/core": {
- "version": "1.9.2",
- "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz",
- "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/wasi-threads": "1.2.1",
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@emnapi/runtime": {
- "version": "1.9.2",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz",
- "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@emnapi/wasi-threads": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
- "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
- "dev": true,
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.27.7",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz",
@@ -1224,6 +1192,18 @@
"integrity": "sha512-LAFerSBxVKNHFy3B9kyKgkQUIG6Om2RLQ6vDayd4IQFlRmhuxdV9nOarUjoVHwKWYk8VqT+C6fBMW+EGMJ1eFA==",
"license": "OFL-1.1"
},
+ "node_modules/@hono/node-server": {
+ "version": "1.19.14",
+ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz",
+ "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.14.1"
+ },
+ "peerDependencies": {
+ "hono": "^4"
+ }
+ },
"node_modules/@inquirer/external-editor": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz",
@@ -1295,10 +1275,18 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@lemoncode/quickmock-bridge-protocol": {
+ "resolved": "packages/bridge-protocol",
+ "link": true
+ },
"node_modules/@lemoncode/quickmock-mcp": {
"resolved": "packages/mcp",
"link": true
},
+ "node_modules/@lemoncode/quickmock-registry-protocol": {
+ "resolved": "packages/registry-protocol",
+ "link": true
+ },
"node_modules/@lemoncode/tsdown-config": {
"resolved": "tooling/tsdown",
"link": true
@@ -1387,6 +1375,46 @@
"node": ">=6 <7 || >=8"
}
},
+ "node_modules/@modelcontextprotocol/sdk": {
+ "version": "1.29.0",
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz",
+ "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@hono/node-server": "^1.19.9",
+ "ajv": "^8.17.1",
+ "ajv-formats": "^3.0.1",
+ "content-type": "^1.0.5",
+ "cors": "^2.8.5",
+ "cross-spawn": "^7.0.5",
+ "eventsource": "^3.0.2",
+ "eventsource-parser": "^3.0.0",
+ "express": "^5.2.1",
+ "express-rate-limit": "^8.2.1",
+ "hono": "^4.11.4",
+ "jose": "^6.1.3",
+ "json-schema-typed": "^8.0.2",
+ "pkce-challenge": "^5.0.0",
+ "raw-body": "^3.0.0",
+ "zod": "^3.25 || ^4.0",
+ "zod-to-json-schema": "^3.25.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@cfworker/json-schema": "^4.1.1",
+ "zod": "^3.25 || ^4.0"
+ },
+ "peerDependenciesMeta": {
+ "@cfworker/json-schema": {
+ "optional": true
+ },
+ "zod": {
+ "optional": false
+ }
+ }
+ },
"node_modules/@napi-rs/wasm-runtime": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
@@ -1800,6 +1828,67 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@puppeteer/browsers": {
+ "version": "2.13.0",
+ "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.0.tgz",
+ "integrity": "sha512-46BZJYJjc/WwmKjsvDFykHtXrtomsCIrwYQPOP7VfMJoZY2bsDF9oROBABR3paDjDcmkUye1Pb1BqdcdiipaWA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "extract-zip": "^2.0.1",
+ "progress": "^2.0.3",
+ "proxy-agent": "^6.5.0",
+ "semver": "^7.7.4",
+ "tar-fs": "^3.1.1",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "browsers": "lib/cjs/main-cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@puppeteer/browsers/node_modules/b4a": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz",
+ "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "react-native-b4a": "*"
+ },
+ "peerDependenciesMeta": {
+ "react-native-b4a": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@puppeteer/browsers/node_modules/tar-fs": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz",
+ "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==",
+ "license": "MIT",
+ "dependencies": {
+ "pump": "^3.0.0",
+ "tar-stream": "^3.1.5"
+ },
+ "optionalDependencies": {
+ "bare-fs": "^4.0.1",
+ "bare-path": "^3.0.0"
+ }
+ },
+ "node_modules/@puppeteer/browsers/node_modules/tar-stream": {
+ "version": "3.1.8",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz",
+ "integrity": "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==",
+ "license": "MIT",
+ "dependencies": {
+ "b4a": "^1.6.4",
+ "bare-fs": "^4.5.5",
+ "fast-fifo": "^1.2.0",
+ "streamx": "^2.15.0"
+ }
+ },
"node_modules/@quansync/fs": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@quansync/fs/-/fs-1.0.0.tgz",
@@ -2443,6 +2532,12 @@
"@textlint/ast-node-types": "15.5.4"
}
},
+ "node_modules/@tootallnate/quickjs-emscripten": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
+ "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==",
+ "license": "MIT"
+ },
"node_modules/@turbo/darwin-64": {
"version": "2.9.6",
"resolved": "https://registry.npmjs.org/@turbo/darwin-64/-/darwin-64-2.9.6.tgz",
@@ -2591,7 +2686,7 @@
"version": "24.12.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz",
"integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.16.0"
@@ -2615,6 +2710,7 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
"integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.2.2"
@@ -2653,13 +2749,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@types/vscode": {
- "version": "1.116.0",
- "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.116.0.tgz",
- "integrity": "sha512-sYHp4MO6BqJ2PD7Hjt0hlIS3tMaYsVPJrd0RUjDJ8HtOYnyJIEej0bLSccM8rE77WrC+Xox/kdBwEFDO8MsxNA==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/@types/wicg-file-system-access": {
"version": "2023.10.7",
"resolved": "https://registry.npmjs.org/@types/wicg-file-system-access/-/wicg-file-system-access-2023.10.7.tgz",
@@ -2667,6 +2756,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/yauzl": {
+ "version": "2.10.3",
+ "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
+ "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@typespec/ts-http-runtime": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.5.tgz",
@@ -2926,6 +3025,7 @@
"integrity": "sha512-q3PchVhZINX23Pv+RERgAtDlp6wzVkID/smOPnZ5YGWpeWUe3jMNYppeVh15j4il3G7JIJty1d1Kicpm0HSMig==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@vitest/browser": "4.1.4",
"@vitest/mocker": "4.1.4",
@@ -2950,6 +3050,7 @@
"integrity": "sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@bcoe/v8-coverage": "^1.0.2",
"@vitest/utils": "4.1.4",
@@ -3079,6 +3180,7 @@
"integrity": "sha512-EgFR7nlj5iTDYZYCvavjFokNYwr3c3ry0sFiCg+N7B233Nwp+NNx7eoF/XvMWDCKY71xXAG3kFkt97ZHBJVL8A==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@vitest/utils": "4.1.4",
"fflate": "^0.8.2",
@@ -3312,11 +3414,48 @@
"node": ">=18"
}
},
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/accepts/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/accepts/node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 14"
@@ -3326,7 +3465,6 @@
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
- "dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
@@ -3339,6 +3477,23 @@
"url": "https://github.com/sponsors/epoberezkin"
}
},
+ "node_modules/ajv-formats": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
+ "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
"node_modules/ansi-colors": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
@@ -3369,7 +3524,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -3503,6 +3657,18 @@
"node": "^20.19.0 || >=22.12.0"
}
},
+ "node_modules/ast-types": {
+ "version": "0.13.4",
+ "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
+ "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/ast-v8-to-istanbul": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz",
@@ -3550,6 +3716,97 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/bare-events": {
+ "version": "2.8.2",
+ "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz",
+ "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "bare-abort-controller": "*"
+ },
+ "peerDependenciesMeta": {
+ "bare-abort-controller": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/bare-fs": {
+ "version": "4.7.1",
+ "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.1.tgz",
+ "integrity": "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bare-events": "^2.5.4",
+ "bare-path": "^3.0.0",
+ "bare-stream": "^2.6.4",
+ "bare-url": "^2.2.2",
+ "fast-fifo": "^1.3.2"
+ },
+ "engines": {
+ "bare": ">=1.16.0"
+ },
+ "peerDependencies": {
+ "bare-buffer": "*"
+ },
+ "peerDependenciesMeta": {
+ "bare-buffer": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/bare-os": {
+ "version": "3.8.7",
+ "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.8.7.tgz",
+ "integrity": "sha512-G4Gr1UsGeEy2qtDTZwL7JFLo2wapUarz7iTMcYcMFdS89AIQuBoyjgXZz0Utv7uHs3xA9LckhVbeBi8lEQrC+w==",
+ "license": "Apache-2.0",
+ "engines": {
+ "bare": ">=1.14.0"
+ }
+ },
+ "node_modules/bare-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz",
+ "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bare-os": "^3.0.1"
+ }
+ },
+ "node_modules/bare-stream": {
+ "version": "2.13.0",
+ "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.0.tgz",
+ "integrity": "sha512-3zAJRZMDFGjdn+RVnNpF9kuELw+0Fl3lpndM4NcEOhb9zwtSo/deETfuIwMSE5BXanA0FrN1qVjffGwAg2Y7EA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "streamx": "^2.25.0",
+ "teex": "^1.0.1"
+ },
+ "peerDependencies": {
+ "bare-abort-controller": "*",
+ "bare-buffer": "*",
+ "bare-events": "*"
+ },
+ "peerDependenciesMeta": {
+ "bare-abort-controller": {
+ "optional": true
+ },
+ "bare-buffer": {
+ "optional": true
+ },
+ "bare-events": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/bare-url": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.0.tgz",
+ "integrity": "sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bare-path": "^3.0.0"
+ }
+ },
"node_modules/base64-arraybuffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
@@ -3581,6 +3838,15 @@
"license": "MIT",
"optional": true
},
+ "node_modules/basic-ftp": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.0.tgz",
+ "integrity": "sha512-5K9eNNn7ywHPsYnFwjKgYH8Hf8B5emh7JKcPaVjjrMJFQQwGpwowEnZNEtHs7DfR7hCZsmaK3VA4HUK0YarT+w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
"node_modules/better-path-resolve": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/better-path-resolve/-/better-path-resolve-1.0.0.tgz",
@@ -3639,6 +3905,30 @@
"readable-stream": "^3.4.0"
}
},
+ "node_modules/body-parser": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
+ "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^1.0.5",
+ "debug": "^4.4.3",
+ "http-errors": "^2.0.0",
+ "iconv-lite": "^0.7.0",
+ "on-finished": "^2.4.1",
+ "qs": "^6.14.1",
+ "raw-body": "^3.0.1",
+ "type-is": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -3707,7 +3997,6 @@
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": "*"
@@ -3736,6 +4025,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/cac": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/cac/-/cac-7.0.0.tgz",
@@ -3750,7 +4048,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@@ -3764,7 +4061,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
@@ -3879,6 +4175,28 @@
"license": "ISC",
"optional": true
},
+ "node_modules/chromium-bidi": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-14.0.0.tgz",
+ "integrity": "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "mitt": "^3.0.1",
+ "zod": "^3.24.1"
+ },
+ "peerDependencies": {
+ "devtools-protocol": "*"
+ }
+ },
+ "node_modules/chromium-bidi/node_modules/zod": {
+ "version": "3.25.76",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
+ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
"node_modules/cli-cursor": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
@@ -3912,37 +4230,110 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/cockatiel": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.2.1.tgz",
- "integrity": "sha512-gfrHV6ZPkquExvMh9IOkKsBzNDk6sDuZ6DdBGUBkvFnTCqCxzpuq48RySgP0AnaqQkw2zynOFj9yly6T1Q2G5Q==",
- "dev": true,
- "license": "MIT",
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
"engines": {
- "node": ">=16"
+ "node": ">=12"
}
},
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "dev": true,
+ "node_modules/cliui/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
- "color-name": "~1.1.4"
+ "color-convert": "^2.0.1"
},
"engines": {
- "node": ">=7.0.0"
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "dev": true,
+ "node_modules/cliui/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
- "node_modules/colorette": {
+ "node_modules/cliui/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/cockatiel": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.2.1.tgz",
+ "integrity": "sha512-gfrHV6ZPkquExvMh9IOkKsBzNDk6sDuZ6DdBGUBkvFnTCqCxzpuq48RySgP0AnaqQkw2zynOFj9yly6T1Q2G5Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "license": "MIT"
+ },
+ "node_modules/colorette": {
"version": "2.0.20",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
@@ -3979,6 +4370,28 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/content-disposition": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
+ "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@@ -3986,11 +4399,45 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/cors": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
@@ -4053,11 +4500,19 @@
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT"
},
+ "node_modules/data-uri-to-buffer": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
+ "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -4149,6 +4604,20 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/degenerator": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz",
+ "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ast-types": "^0.13.4",
+ "escodegen": "^2.1.0",
+ "esprima": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -4159,6 +4628,15 @@
"node": ">=0.4.0"
}
},
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/detect-indent": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz",
@@ -4183,6 +4661,13 @@
"resolved": "tooling/dev-cli",
"link": true
},
+ "node_modules/devtools-protocol": {
+ "version": "0.0.1595872",
+ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1595872.tgz",
+ "integrity": "sha512-kRfgp8vWVjBu/fbYCiVFiOqsCk3CrMKEo3WbgGT2NXK2dG7vawWPBljixajVgGK9II8rDO9G0oD0zLt3I1daRg==",
+ "license": "BSD-3-Clause",
+ "peer": true
+ },
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -4287,7 +4772,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
@@ -4325,6 +4809,12 @@
"url": "https://bevry.me/fund"
}
},
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
"node_modules/emoji-regex": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
@@ -4342,6 +4832,15 @@
"node": ">=14"
}
},
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/encoding-sniffer": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz",
@@ -4373,9 +4872,7 @@
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
- "dev": true,
"license": "MIT",
- "optional": true,
"dependencies": {
"once": "^1.4.0"
}
@@ -4424,7 +4921,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -4434,7 +4930,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -4451,7 +4946,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
@@ -4483,6 +4977,7 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
+ "peer": true,
"bin": {
"esbuild": "bin/esbuild"
},
@@ -4518,11 +5013,46 @@
"@esbuild/win32-x64": "0.27.7"
}
},
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/escodegen": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
+ "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esprima": "^4.0.1",
+ "estraverse": "^5.2.0",
+ "esutils": "^2.0.2"
+ },
+ "bin": {
+ "escodegen": "bin/escodegen.js",
+ "esgenerate": "bin/esgenerate.js"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "optionalDependencies": {
+ "source-map": "~0.6.1"
+ }
+ },
"node_modules/esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
- "dev": true,
"license": "BSD-2-Clause",
"bin": {
"esparse": "bin/esparse.js",
@@ -4532,6 +5062,15 @@
"node": ">=4"
}
},
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
"node_modules/estree-walker": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
@@ -4542,6 +5081,24 @@
"@types/estree": "^1.0.0"
}
},
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/eventemitter3": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
@@ -4549,6 +5106,36 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/events-universal": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
+ "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bare-events": "^2.7.0"
+ }
+ },
+ "node_modules/eventsource": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
+ "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
+ "license": "MIT",
+ "dependencies": {
+ "eventsource-parser": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/eventsource-parser": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz",
+ "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
"node_modules/expand-template": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
@@ -4570,6 +5157,92 @@
"node": ">=12.0.0"
}
},
+ "node_modules/express": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.1",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express-rate-limit": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.2.tgz",
+ "integrity": "sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==",
+ "license": "MIT",
+ "dependencies": {
+ "ip-address": "10.1.0"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/express-rate-limit"
+ },
+ "peerDependencies": {
+ "express": ">= 4.11"
+ }
+ },
+ "node_modules/express/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express/node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/extendable-error": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/extendable-error/-/extendable-error-0.1.7.tgz",
@@ -4577,11 +5250,46 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/extract-zip": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
+ "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "get-stream": "^5.1.0",
+ "yauzl": "^2.10.0"
+ },
+ "bin": {
+ "extract-zip": "cli.js"
+ },
+ "engines": {
+ "node": ">= 10.17.0"
+ },
+ "optionalDependencies": {
+ "@types/yauzl": "^2.9.1"
+ }
+ },
+ "node_modules/extract-zip/node_modules/yauzl": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
+ "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-crc32": "~0.2.3",
+ "fd-slicer": "~1.1.0"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-fifo": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
+ "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
"license": "MIT"
},
"node_modules/fast-glob": {
@@ -4620,7 +5328,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
- "dev": true,
"funding": [
{
"type": "github",
@@ -4652,6 +5359,15 @@
"reusify": "^1.0.4"
}
},
+ "node_modules/fd-slicer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
+ "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
+ "license": "MIT",
+ "dependencies": {
+ "pend": "~1.2.0"
+ }
+ },
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
@@ -4690,6 +5406,27 @@
"node": ">=8"
}
},
+ "node_modules/finalhandler": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
@@ -4745,6 +5482,24 @@
"node": ">= 6"
}
},
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
@@ -4787,12 +5542,20 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
"node_modules/get-east-asian-width": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz",
@@ -4810,7 +5573,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
@@ -4835,7 +5597,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
- "dev": true,
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
@@ -4845,6 +5606,21 @@
"node": ">= 0.4"
}
},
+ "node_modules/get-stream": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+ "license": "MIT",
+ "dependencies": {
+ "pump": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/get-tsconfig": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
@@ -4858,6 +5634,20 @@
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
+ "node_modules/get-uri": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz",
+ "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==",
+ "license": "MIT",
+ "dependencies": {
+ "basic-ftp": "^5.0.2",
+ "data-uri-to-buffer": "^6.0.2",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/github-from-package": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
@@ -4968,7 +5758,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -4998,7 +5787,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -5027,7 +5815,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
@@ -5036,6 +5823,16 @@
"node": ">= 0.4"
}
},
+ "node_modules/hono": {
+ "version": "4.12.14",
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.14.tgz",
+ "integrity": "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=16.9.0"
+ }
+ },
"node_modules/hookable": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/hookable/-/hookable-6.1.1.tgz",
@@ -5109,11 +5906,30 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/http-proxy-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
- "dev": true,
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.0",
@@ -5127,7 +5943,6 @@
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
@@ -5167,7 +5982,6 @@
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
"integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
@@ -5262,9 +6076,7 @@
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true,
- "license": "ISC",
- "optional": true
+ "license": "ISC"
},
"node_modules/ini": {
"version": "1.3.8",
@@ -5274,6 +6086,24 @@
"license": "ISC",
"optional": true
},
+ "node_modules/ip-address": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
+ "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/is-docker": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
@@ -5358,6 +6188,12 @@
"node": ">=0.12.0"
}
},
+ "node_modules/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "license": "MIT"
+ },
"node_modules/is-subdir": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/is-subdir/-/is-subdir-1.2.0.tgz",
@@ -5401,7 +6237,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "dev": true,
"license": "ISC"
},
"node_modules/istanbul-lib-coverage": {
@@ -5489,6 +6324,15 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/jose": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz",
+ "integrity": "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
"node_modules/js-tokens": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz",
@@ -5526,9 +6370,14 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
- "dev": true,
"license": "MIT"
},
+ "node_modules/json-schema-typed": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz",
+ "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==",
+ "license": "BSD-2-Clause"
+ },
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
@@ -5636,7 +6485,8 @@
"url": "https://github.com/sponsors/lavrton"
}
],
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/leven": {
"version": "3.1.0",
@@ -6207,7 +7057,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -6220,6 +7069,27 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/media-typer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -6364,6 +7234,12 @@
"node": ">=16 || 14 >=14.17"
}
},
+ "node_modules/mitt": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
+ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
+ "license": "MIT"
+ },
"node_modules/mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
@@ -6396,7 +7272,6 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true,
"license": "MIT"
},
"node_modules/mute-stream": {
@@ -6433,6 +7308,24 @@
"license": "MIT",
"optional": true
},
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/netmask": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz",
+ "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
"node_modules/node-abi": {
"version": "3.89.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.89.0.tgz",
@@ -6555,11 +7448,19 @@
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/object-inspect": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -6579,13 +7480,23 @@
],
"license": "MIT"
},
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "dev": true,
"license": "ISC",
- "optional": true,
"dependencies": {
"wrappy": "1"
}
@@ -6739,6 +7650,38 @@
"node": ">=6"
}
},
+ "node_modules/pac-proxy-agent": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz",
+ "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==",
+ "license": "MIT",
+ "dependencies": {
+ "@tootallnate/quickjs-emscripten": "^0.23.0",
+ "agent-base": "^7.1.2",
+ "debug": "^4.3.4",
+ "get-uri": "^6.0.1",
+ "http-proxy-agent": "^7.0.0",
+ "https-proxy-agent": "^7.0.6",
+ "pac-resolver": "^7.0.1",
+ "socks-proxy-agent": "^8.0.5"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/pac-resolver": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz",
+ "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==",
+ "license": "MIT",
+ "dependencies": {
+ "degenerator": "^5.0.0",
+ "netmask": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
@@ -6847,6 +7790,15 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -6861,7 +7813,6 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -6894,6 +7845,16 @@
"node": "20 || >=22"
}
},
+ "node_modules/path-to-regexp": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
+ "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
@@ -6915,7 +7876,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
- "dev": true,
"license": "MIT"
},
"node_modules/picocolors": {
@@ -6948,6 +7908,15 @@
"node": ">=6"
}
},
+ "node_modules/pkce-challenge": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz",
+ "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
"node_modules/playwright": {
"version": "1.59.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz",
@@ -7089,6 +8058,62 @@
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
+ "node_modules/progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/proxy-agent": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz",
+ "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "^4.3.4",
+ "http-proxy-agent": "^7.0.1",
+ "https-proxy-agent": "^7.0.6",
+ "lru-cache": "^7.14.1",
+ "pac-proxy-agent": "^7.1.0",
+ "proxy-from-env": "^1.1.0",
+ "socks-proxy-agent": "^8.0.5"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/proxy-agent/node_modules/lru-cache": {
+ "version": "7.18.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+ "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
"node_modules/publish": {
"resolved": "tooling/publish",
"link": true
@@ -7097,9 +8122,7 @@
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
"integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
- "dev": true,
"license": "MIT",
- "optional": true,
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
@@ -7115,11 +8138,28 @@
"node": ">=6"
}
},
+ "node_modules/puppeteer-core": {
+ "version": "24.42.0",
+ "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.42.0.tgz",
+ "integrity": "sha512-T4zXokk/izH01fYPhyyev1A4piWiOKrYq7CUFpdoYQxmOnXoV6YjUabmfIjCYkNspSoAXIxRid3Tw+Vg0fthYg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@puppeteer/browsers": "2.13.0",
+ "chromium-bidi": "14.0.0",
+ "debug": "^4.4.3",
+ "devtools-protocol": "0.0.1595872",
+ "typed-query-selector": "^2.12.1",
+ "webdriver-bidi-protocol": "0.4.1",
+ "ws": "^8.19.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/qs": {
"version": "6.15.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz",
"integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==",
- "dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.1.0"
@@ -7173,11 +8213,35 @@
"resolved": "packages/vscode-extension",
"link": true
},
- "node_modules/raf-schd": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
- "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==",
- "license": "MIT"
+ "node_modules/raf-schd": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz",
+ "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==",
+ "license": "MIT"
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.7.0",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
},
"node_modules/rc": {
"version": "1.2.8",
@@ -7214,6 +8278,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
@@ -7226,6 +8291,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
@@ -7253,6 +8319,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/react-reconciler": "^0.28.2",
"its-fine": "^1.1.1",
@@ -7399,11 +8466,19 @@
"node": ">=8"
}
},
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -7470,6 +8545,7 @@
"integrity": "sha512-rzi5WqKzEZw3SooTt7cgm4eqIoujPIyGcJNGFL7iPEuajQw7vxMHUkXylu4/vhCkJGXsgRmxqMKXUpT6FEgl0g==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@oxc-project/types": "=0.126.0",
"@rolldown/pluginutils": "1.0.0-rc.16"
@@ -7601,6 +8677,22 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
"node_modules/run-applescript": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz",
@@ -7663,7 +8755,6 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
- "dev": true,
"license": "MIT"
},
"node_modules/sax": {
@@ -7781,7 +8872,6 @@
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
- "dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -7790,11 +8880,86 @@
"node": ">=10"
}
},
+ "node_modules/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.1",
+ "mime-types": "^3.0.2",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/send/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/send/node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
@@ -7807,7 +8972,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -7817,7 +8981,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@@ -7837,7 +9000,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
"integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
- "dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@@ -7854,7 +9016,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
@@ -7873,7 +9034,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
- "dev": true,
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
@@ -8006,6 +9166,54 @@
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
}
},
+ "node_modules/smart-buffer": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
+ "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6.0.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/socks": {
+ "version": "2.8.7",
+ "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz",
+ "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==",
+ "license": "MIT",
+ "dependencies": {
+ "ip-address": "^10.0.1",
+ "smart-buffer": "^4.2.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/socks-proxy-agent": {
+ "version": "8.0.5",
+ "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
+ "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "^4.3.4",
+ "socks": "^2.8.3"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -8077,6 +9285,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/std-env": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz",
@@ -8084,6 +9301,17 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/streamx": {
+ "version": "2.25.0",
+ "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.25.0.tgz",
+ "integrity": "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==",
+ "license": "MIT",
+ "dependencies": {
+ "events-universal": "^1.0.0",
+ "fast-fifo": "^1.3.2",
+ "text-decoder": "^1.1.0"
+ }
+ },
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -8155,7 +9383,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
@@ -8353,6 +9580,15 @@
"node": ">=6"
}
},
+ "node_modules/teex": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
+ "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
+ "license": "MIT",
+ "dependencies": {
+ "streamx": "^2.12.5"
+ }
+ },
"node_modules/term-size": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz",
@@ -8383,6 +9619,29 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/text-decoder": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
+ "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "b4a": "^1.6.4"
+ }
+ },
+ "node_modules/text-decoder/node_modules/b4a": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz",
+ "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "react-native-b4a": "*"
+ },
+ "peerDependenciesMeta": {
+ "react-native-b4a": {
+ "optional": true
+ }
+ }
+ },
"node_modules/text-segmentation": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
@@ -8488,6 +9747,15 @@
"node": ">=8.0"
}
},
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
"node_modules/totalist": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
@@ -8578,7 +9846,6 @@
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "dev": true,
"license": "0BSD"
},
"node_modules/tsx": {
@@ -8587,6 +9854,7 @@
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"esbuild": "~0.27.0",
"get-tsconfig": "^4.7.5"
@@ -8656,6 +9924,51 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/type-is": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^1.0.5",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/type-is/node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/type-is/node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/typed-query-selector": {
+ "version": "2.12.1",
+ "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.1.tgz",
+ "integrity": "sha512-uzR+FzI8qrUEIu96oaeBJmd9E7CFEiQ3goA5qCVgc4s5llSubcfGHq9yUstZx/k4s9dXHVKsE35YWoFyvEqEHA==",
+ "license": "MIT"
+ },
"node_modules/typed-rest-client": {
"version": "1.8.11",
"resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz",
@@ -8674,6 +9987,7 @@
"integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==",
"dev": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -8741,7 +10055,7 @@
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/unicorn-magic": {
@@ -8767,6 +10081,15 @@
"node": ">= 4.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==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/unrun": {
"version": "0.2.36",
"resolved": "https://registry.npmjs.org/unrun/-/unrun-0.2.36.tgz",
@@ -8864,6 +10187,15 @@
"spdx-expression-parse": "^3.0.0"
}
},
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/version-range": {
"version": "4.15.0",
"resolved": "https://registry.npmjs.org/version-range/-/version-range-4.15.0.tgz",
@@ -8883,6 +10215,7 @@
"integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"lightningcss": "^1.32.0",
"picomatch": "^4.0.4",
@@ -9353,6 +10686,12 @@
}
}
},
+ "node_modules/webdriver-bidi-protocol": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.1.tgz",
+ "integrity": "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==",
+ "license": "Apache-2.0"
+ },
"node_modules/whatwg-encoding": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
@@ -9394,7 +10733,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
@@ -9492,15 +10830,12 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "dev": true,
- "license": "ISC",
- "optional": true
+ "license": "ISC"
},
"node_modules/ws": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
"integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=10.0.0"
@@ -9558,6 +10893,15 @@
"node": ">=4.0"
}
},
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
@@ -9571,6 +10915,7 @@
"integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==",
"dev": true,
"license": "ISC",
+ "peer": true,
"bin": {
"yaml": "bin.mjs"
},
@@ -9581,6 +10926,62 @@
"url": "https://github.com/sponsors/eemeli"
}
},
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
+ "node_modules/yargs/node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/yauzl": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.3.0.tgz",
@@ -9605,30 +11006,86 @@
"buffer-crc32": "~0.2.3"
}
},
+ "node_modules/zod": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
+ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
+ "license": "MIT",
+ "peer": true,
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-to-json-schema": {
+ "version": "3.25.2",
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz",
+ "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==",
+ "license": "ISC",
+ "peerDependencies": {
+ "zod": "^3.25.28 || ^4"
+ }
+ },
+ "packages/bridge-protocol": {
+ "name": "@lemoncode/quickmock-bridge-protocol",
+ "version": "0.0.0",
+ "devDependencies": {
+ "@lemoncode/typescript-config": "*"
+ }
+ },
"packages/mcp": {
"name": "@lemoncode/quickmock-mcp",
- "version": "0.0.1",
+ "version": "0.1.0",
+ "dependencies": {
+ "@modelcontextprotocol/sdk": "1.29.0",
+ "@puppeteer/browsers": "2.13.0",
+ "puppeteer-core": "24.42.0",
+ "zod": "4.3.6"
+ },
+ "bin": {
+ "quickmock-mcp": "dist/index.mjs"
+ },
"devDependencies": {
+ "@lemoncode/quickmock-bridge-protocol": "*",
+ "@lemoncode/quickmock-registry-protocol": "*",
"@lemoncode/tsdown-config": "*",
"@lemoncode/typescript-config": "*",
"@lemoncode/vitest-config": "*"
}
},
+ "packages/registry-protocol": {
+ "name": "@lemoncode/quickmock-registry-protocol",
+ "version": "0.0.0",
+ "devDependencies": {
+ "@lemoncode/typescript-config": "*"
+ }
+ },
"packages/vscode-extension": {
"name": "quickmock",
- "version": "0.0.1",
+ "version": "0.1.0",
"license": "MIT",
+ "dependencies": {
+ "@lemoncode/quickmock-mcp": "*"
+ },
"devDependencies": {
+ "@lemoncode/quickmock-bridge-protocol": "*",
+ "@lemoncode/quickmock-registry-protocol": "*",
"@lemoncode/tsdown-config": "*",
"@lemoncode/typescript-config": "*",
"@lemoncode/vitest-config": "*",
- "@types/vscode": "1.116.0",
+ "@types/vscode": "1.115.0",
"@vscode/vsce": "3.9.0"
},
"engines": {
- "vscode": "^1.116.0"
+ "vscode": "^1.115.0"
}
},
+ "packages/vscode-extension/node_modules/@types/vscode": {
+ "version": "1.115.0",
+ "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.115.0.tgz",
+ "integrity": "sha512-/M8cdznOlqtMqduHKKlIF00v4eum4ZWKgn8YoPRKcN6PDdvoWeeqDaQSnw63ipDbq1Uzz78Wndk/d0uSPwORfA==",
+ "dev": true,
+ "license": "MIT"
+ },
"tooling/dev-cli": {
"dependencies": {
"@clack/prompts": "1.2.0"
diff --git a/packages/bridge-protocol/package.json b/packages/bridge-protocol/package.json
new file mode 100644
index 00000000..52ddea32
--- /dev/null
+++ b/packages/bridge-protocol/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "@lemoncode/quickmock-bridge-protocol",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "exports": {
+ ".": "./src/index.ts"
+ },
+ "scripts": {
+ "check-types": "tsc --noEmit"
+ },
+ "devDependencies": {
+ "@lemoncode/typescript-config": "*"
+ }
+}
diff --git a/packages/bridge-protocol/src/constant.ts b/packages/bridge-protocol/src/constant.ts
new file mode 100644
index 00000000..85cdad50
--- /dev/null
+++ b/packages/bridge-protocol/src/constant.ts
@@ -0,0 +1,12 @@
+export const HOST_MESSAGE_TYPE = {
+ LOAD: 'qm:load',
+ SAVED: 'qm:saved',
+ LOAD_FILE: 'LOAD_FILE',
+} as const;
+
+export const APP_MESSAGE_TYPE = {
+ READY: 'qm:ready',
+ SAVE: 'qm:save',
+ RENDER_COMPLETE: 'qm:render-complete',
+ WEBVIEW_READY: 'WEBVIEW_READY',
+} as const;
diff --git a/packages/bridge-protocol/src/index.ts b/packages/bridge-protocol/src/index.ts
new file mode 100644
index 00000000..61dc567b
--- /dev/null
+++ b/packages/bridge-protocol/src/index.ts
@@ -0,0 +1,2 @@
+export * from './constant';
+export * from './model';
diff --git a/packages/bridge-protocol/src/model.ts b/packages/bridge-protocol/src/model.ts
new file mode 100644
index 00000000..055466e7
--- /dev/null
+++ b/packages/bridge-protocol/src/model.ts
@@ -0,0 +1,33 @@
+import type { APP_MESSAGE_TYPE, HOST_MESSAGE_TYPE } from './constant';
+
+export interface ContentBbox {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+}
+
+export interface LoadFilePayload {
+ data: unknown;
+ fileName: string;
+}
+
+export type HostMessage =
+ | {
+ type: typeof HOST_MESSAGE_TYPE.LOAD;
+ payload: { content: string; fileName: string };
+ }
+ | { type: typeof HOST_MESSAGE_TYPE.SAVED }
+ | { type: typeof HOST_MESSAGE_TYPE.LOAD_FILE; payload: LoadFilePayload };
+
+export type AppMessage =
+ | { type: typeof APP_MESSAGE_TYPE.READY }
+ | { type: typeof APP_MESSAGE_TYPE.WEBVIEW_READY }
+ | { type: typeof APP_MESSAGE_TYPE.SAVE; payload: { content: string } }
+ | {
+ type: typeof APP_MESSAGE_TYPE.RENDER_COMPLETE;
+ payload?: ContentBbox;
+ };
+
+export type PayloadOf =
+ Extract extends { payload: infer P } ? P : undefined;
diff --git a/packages/bridge-protocol/tsconfig.json b/packages/bridge-protocol/tsconfig.json
new file mode 100644
index 00000000..77ce3f8a
--- /dev/null
+++ b/packages/bridge-protocol/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "@lemoncode/typescript-config/node",
+ "include": ["src"]
+}
diff --git a/packages/mcp/CHANGELOG.md b/packages/mcp/CHANGELOG.md
index e7be37d0..e1af6e22 100644
--- a/packages/mcp/CHANGELOG.md
+++ b/packages/mcp/CHANGELOG.md
@@ -1,5 +1,24 @@
# @lemoncode/quickmock-mcp
+## 0.1.0
+
+### Minor Changes
+
+- Bump 0.1.0
+- 2282316: First public release of the QuickMock VS Code extension and its MCP server.
+
+ **`quickmock` (VS Code extension)**
+ - Custom editor for `.qm` files backed by the QuickMock web app, served inside a webview.
+ - `quickmock.appUrl` setting (default `https://quickmock.net/editor.html`) to point the editor and the MCP renderer at any QuickMock instance. Changes refresh open editors and respawn the MCP server.
+ - Automatic MCP server registration for VS Code / GitHub Copilot, Claude Code, Cursor, Windsurf and Claude Desktop, plus a dynamic `McpServerDefinitionProvider`. Existing entries are refreshed on activation so users always end up pointing at the right MCP invocation.
+ - The MCP server is no longer bundled inside the `.vsix`. In production the extension spawns it on demand via `npx -y @lemoncode/quickmock-mcp`, so users always run the latest published MCP without waiting for an extension release. In development it resolves the local workspace build.
+
+ **`@lemoncode/quickmock-mcp` (MCP server)**
+ - MCP tools to explore and render wireframes: `list_wireframes`, `get_wireframe_json`, `get_wireframe_pages`, `get_wireframe_assets` and `capture_wireframe`.
+ - Headless screenshot pipeline via `puppeteer-core` against the QuickMock app, using a postMessage bridge.
+ - On-demand Chromium download via `@puppeteer/browsers`, cached under `~/.quickmock/browsers`, so headless rendering works without relying on the user's local browser install.
+ - Reads the target app URL from `~/.quickmock/app-url` (written by the extension) with a production fallback, so the MCP works out of the box regardless of how it is spawned.
+
## 0.0.1
### Patch Changes
diff --git a/packages/mcp/package.json b/packages/mcp/package.json
index dff5fdff..c08a3f48 100644
--- a/packages/mcp/package.json
+++ b/packages/mcp/package.json
@@ -6,7 +6,8 @@
".": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
- }
+ },
+ "./package.json": "./package.json"
},
"imports": {
"#*": "./src/*"
@@ -14,16 +15,28 @@
"files": [
"dist"
],
+ "bin": {
+ "quickmock-mcp": "./dist/index.mjs"
+ },
"scripts": {
"build": "tsdown",
"check-types": "tsc --noEmit",
"test": "vitest run",
"test:watch": "vitest",
- "test:coverage": "vitest run --coverage"
+ "test:coverage": "vitest run --coverage",
+ "inspect": "npm run build && npx @modelcontextprotocol/inspector node dist/index.mjs"
+ },
+ "dependencies": {
+ "@modelcontextprotocol/sdk": "1.29.0",
+ "@puppeteer/browsers": "2.13.0",
+ "puppeteer-core": "24.42.0",
+ "zod": "4.3.6"
},
"devDependencies": {
- "@lemoncode/typescript-config": "*",
+ "@lemoncode/quickmock-bridge-protocol": "*",
+ "@lemoncode/quickmock-registry-protocol": "*",
"@lemoncode/tsdown-config": "*",
+ "@lemoncode/typescript-config": "*",
"@lemoncode/vitest-config": "*"
},
"repository": {
diff --git a/packages/mcp/src/commons/qm-file.models.ts b/packages/mcp/src/commons/qm-file.models.ts
new file mode 100644
index 00000000..8019f36d
--- /dev/null
+++ b/packages/mcp/src/commons/qm-file.models.ts
@@ -0,0 +1,28 @@
+export interface QmShape {
+ id: string;
+ type: string;
+ otherProps?: {
+ imageSrc?: string;
+ [key: string]: unknown;
+ };
+ [key: string]: unknown;
+}
+
+export interface QmPage {
+ id: string;
+ name: string;
+ shapes: QmShape[];
+}
+
+export interface QmFileContract {
+ version: string;
+ pages: QmPage[];
+ customColors: (string | null)[];
+ size: { width: number; height: number };
+}
+
+export interface QmFile {
+ absPath: string;
+ content: string;
+ parsed: QmFileContract;
+}
diff --git a/packages/mcp/src/commons/qm-file.utils.ts b/packages/mcp/src/commons/qm-file.utils.ts
new file mode 100644
index 00000000..9a6b0aa3
--- /dev/null
+++ b/packages/mcp/src/commons/qm-file.utils.ts
@@ -0,0 +1,31 @@
+import { readFile } from 'node:fs/promises';
+import { resolve } from 'node:path';
+import type { RegistryClient } from '../core/registry.models';
+import type { QmFile, QmFileContract } from './qm-file.models';
+
+export type { QmFile, QmFileContract };
+
+/**
+ * Reads a .qm file (live registry first, disk fallback) and returns the raw
+ * content string together with the parsed contract.
+ *
+ * Throws if the file cannot be read or the JSON is invalid.
+ */
+export async function readQmFile(
+ path: string,
+ registry: RegistryClient
+): Promise {
+ const root = process.env.QM_WORKSPACE_ROOT ?? process.cwd();
+ const absPath = resolve(root, path);
+
+ const live = await registry.getDocument(absPath);
+ const content = live ?? (await readFile(absPath, 'utf-8'));
+
+ const parsed = JSON.parse(content) as QmFileContract;
+
+ if (!Array.isArray(parsed.pages)) {
+ throw new Error(`"${path}" does not contain a valid pages array.`);
+ }
+
+ return { absPath, content, parsed };
+}
diff --git a/packages/mcp/src/commons/tool-response.helpers.ts b/packages/mcp/src/commons/tool-response.helpers.ts
new file mode 100644
index 00000000..88e89218
--- /dev/null
+++ b/packages/mcp/src/commons/tool-response.helpers.ts
@@ -0,0 +1,19 @@
+type TextContent = { type: 'text'; text: string };
+type ImageContent = { type: 'image'; data: string; mimeType: string };
+type ToolContent = TextContent | ImageContent;
+
+export function toolText(text: string) {
+ return { content: [{ type: 'text' as const, text }] };
+}
+
+export function toolImage(data: string, mimeType: string) {
+ return { content: [{ type: 'image' as const, data, mimeType }] };
+}
+
+export function toolMultiContent(items: ToolContent[]) {
+ return { content: items };
+}
+
+export function toolError(text: string) {
+ return { content: [{ type: 'text' as const, text }], isError: true as const };
+}
diff --git a/packages/mcp/src/commons/wireframe-file.service.ts b/packages/mcp/src/commons/wireframe-file.service.ts
new file mode 100644
index 00000000..0531e134
--- /dev/null
+++ b/packages/mcp/src/commons/wireframe-file.service.ts
@@ -0,0 +1,15 @@
+import type { RegistryClient } from '../core';
+import type { QmFile } from './qm-file.models';
+import { readQmFile } from './qm-file.utils';
+
+export interface WireframeFileService {
+ readFile(path: string): Promise;
+}
+
+export function createWireframeFileService(
+ registry: RegistryClient
+): WireframeFileService {
+ return {
+ readFile: (path: string) => readQmFile(path, registry),
+ };
+}
diff --git a/packages/mcp/src/core/index.ts b/packages/mcp/src/core/index.ts
new file mode 100644
index 00000000..b1f3231f
--- /dev/null
+++ b/packages/mcp/src/core/index.ts
@@ -0,0 +1,2 @@
+export * from './registry.client';
+export * from './registry.models';
diff --git a/packages/mcp/src/core/mcp.logger.ts b/packages/mcp/src/core/mcp.logger.ts
new file mode 100644
index 00000000..7ba896e3
--- /dev/null
+++ b/packages/mcp/src/core/mcp.logger.ts
@@ -0,0 +1,9 @@
+const PREFIX = '[quickmock-mcp]';
+
+export const logInfo = (message: string, ...rest: unknown[]): void => {
+ console.info(`${PREFIX} ${message}`, ...rest);
+};
+
+export const logError = (message: string, ...rest: unknown[]): void => {
+ console.error(`${PREFIX} ${message}`, ...rest);
+};
diff --git a/packages/mcp/src/core/registry.client.ts b/packages/mcp/src/core/registry.client.ts
new file mode 100644
index 00000000..c78eb9f7
--- /dev/null
+++ b/packages/mcp/src/core/registry.client.ts
@@ -0,0 +1,44 @@
+import { readFileSync } from 'node:fs';
+import {
+ buildPortFilePath,
+ DOCUMENT_ROUTE,
+ LOOPBACK_HOST,
+ parsePortFile,
+ TOKEN_HEADER,
+} from '@lemoncode/quickmock-registry-protocol';
+import { nullClient, type RegistryClient } from './registry.models';
+
+const REQUEST_TIMEOUT_MS = 2_000;
+
+function readPortFile(workspaceRoot: string) {
+ try {
+ const raw = readFileSync(buildPortFilePath(workspaceRoot), 'utf-8');
+ return parsePortFile(raw);
+ } catch {
+ return null;
+ }
+}
+
+/** HTTP client for the VSCode extension's registry server. Falls back to nullClient when the extension is not running. */
+export function createRegistryClient(): RegistryClient {
+ const workspaceRoot = process.env.QM_WORKSPACE_ROOT ?? process.cwd();
+ const portFile = readPortFile(workspaceRoot);
+ if (!portFile) return nullClient;
+ const { port, token } = portFile;
+
+ return {
+ async getDocument(fsPath: string): Promise {
+ try {
+ const url = `http://${LOOPBACK_HOST}:${port}${DOCUMENT_ROUTE}?path=${encodeURIComponent(fsPath)}`;
+ const res = await fetch(url, {
+ headers: { [TOKEN_HEADER]: token },
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
+ });
+ if (!res.ok) return null;
+ return await res.text();
+ } catch {
+ return null;
+ }
+ },
+ };
+}
diff --git a/packages/mcp/src/core/registry.models.ts b/packages/mcp/src/core/registry.models.ts
new file mode 100644
index 00000000..61b6e4e9
--- /dev/null
+++ b/packages/mcp/src/core/registry.models.ts
@@ -0,0 +1,8 @@
+export interface RegistryClient {
+ /** Returns live in-memory content for a file open in the editor, or null. */
+ getDocument(fsPath: string): Promise;
+}
+
+export const nullClient: RegistryClient = {
+ getDocument: async () => null,
+};
diff --git a/packages/mcp/src/index.ts b/packages/mcp/src/index.ts
index cb0ff5c3..d7c665ea 100644
--- a/packages/mcp/src/index.ts
+++ b/packages/mcp/src/index.ts
@@ -1 +1,70 @@
-export {};
+// The SDK's `exports` wildcard maps `./*` → `./dist/esm/*` without `.js`,
+// so Node ESM cannot resolve subpath imports at runtime.
+// See https://github.com/modelcontextprotocol/typescript-sdk/issues/440
+import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
+import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
+import { createWireframeFileService } from './commons/wireframe-file.service';
+import { createRegistryClient } from './core';
+import { logError } from './core/mcp.logger';
+import { captureWireframe } from './tools/capture-wireframe';
+import { getWireframeAssets } from './tools/get-wireframe-assets';
+import { getWireframeJson } from './tools/get-wireframe-json';
+import { getWireframePages } from './tools/get-wireframe-pages';
+import { listWireframes } from './tools/list-wireframes';
+
+const registry = createRegistryClient();
+const service = createWireframeFileService(registry);
+
+const server = new McpServer({ name: 'quickmock', version: '0.1.0' });
+
+server.registerTool(
+ listWireframes.name,
+ { description: listWireframes.description },
+ () => listWireframes.execute()
+);
+
+server.registerTool(
+ getWireframeJson.name,
+ {
+ description: getWireframeJson.description,
+ inputSchema: getWireframeJson.schema,
+ },
+ args => getWireframeJson.execute(args, service)
+);
+
+server.registerTool(
+ getWireframePages.name,
+ {
+ description: getWireframePages.description,
+ inputSchema: getWireframePages.schema,
+ },
+ args => getWireframePages.execute(args, service)
+);
+
+server.registerTool(
+ captureWireframe.name,
+ {
+ description: captureWireframe.description,
+ inputSchema: captureWireframe.schema,
+ },
+ args => captureWireframe.execute(args, service)
+);
+
+server.registerTool(
+ getWireframeAssets.name,
+ {
+ description: getWireframeAssets.description,
+ inputSchema: getWireframeAssets.schema,
+ },
+ args => getWireframeAssets.execute(args, service)
+);
+
+async function main() {
+ const transport = new StdioServerTransport();
+ await server.connect(transport);
+}
+
+main().catch(err => {
+ logError('fatal error:', err);
+ process.exit(1);
+});
diff --git a/packages/mcp/src/renderer/app-url.consts.ts b/packages/mcp/src/renderer/app-url.consts.ts
new file mode 100644
index 00000000..cb66ad5c
--- /dev/null
+++ b/packages/mcp/src/renderer/app-url.consts.ts
@@ -0,0 +1,28 @@
+import { readFileSync } from 'node:fs';
+import { homedir } from 'node:os';
+import { join } from 'node:path';
+
+const DEFAULT_APP_URL =
+ 'https://quickmock.net/editor.html?env=vscode&headless=1';
+
+const APP_URL_FILE = join(homedir(), '.quickmock', 'app-url');
+
+const readAppUrl = (): string => {
+ try {
+ const value = readFileSync(APP_URL_FILE, 'utf-8').trim();
+ if (value) return value;
+ } catch {
+ // fallback to default
+ }
+ return DEFAULT_APP_URL;
+};
+
+export const QUICKMOCK_URL = readAppUrl();
+
+export const QM_APP_ORIGIN = (() => {
+ try {
+ return new URL(QUICKMOCK_URL).origin;
+ } catch {
+ return QUICKMOCK_URL;
+ }
+})();
diff --git a/packages/mcp/src/renderer/bridge.server.ts b/packages/mcp/src/renderer/bridge.server.ts
new file mode 100644
index 00000000..01c758af
--- /dev/null
+++ b/packages/mcp/src/renderer/bridge.server.ts
@@ -0,0 +1,75 @@
+import { APP_MESSAGE_TYPE } from '@lemoncode/quickmock-bridge-protocol';
+import { createServer, type Server } from 'node:http';
+import type { AddressInfo } from 'node:net';
+import { QM_APP_ORIGIN, QUICKMOCK_URL } from './app-url.consts';
+
+export interface BridgeServer {
+ server: Server;
+ port: number;
+}
+
+/** HTTP server that serves the Puppeteer ↔ QuickMock iframe bridge page. */
+export function startBridgeServer(): Promise {
+ return new Promise((resolve, reject) => {
+ const server = createServer((_req, res) => {
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
+ res.end(buildBridgeHtml());
+ });
+
+ server.on('error', reject);
+
+ server.listen(0, '127.0.0.1', () => {
+ const { port } = server.address() as AddressInfo;
+ resolve({ server, port });
+ });
+ });
+}
+
+function buildBridgeHtml(): string {
+ const READY = JSON.stringify(APP_MESSAGE_TYPE.READY);
+ const RENDER_COMPLETE = JSON.stringify(APP_MESSAGE_TYPE.RENDER_COMPLETE);
+ const QM_ORIGIN = JSON.stringify(QM_APP_ORIGIN);
+
+ return /* html */ `
+
+
+
+
+
+
+
+
+
+`;
+}
diff --git a/packages/mcp/src/renderer/chromium.resolver.ts b/packages/mcp/src/renderer/chromium.resolver.ts
new file mode 100644
index 00000000..900d9b47
--- /dev/null
+++ b/packages/mcp/src/renderer/chromium.resolver.ts
@@ -0,0 +1,76 @@
+import {
+ Browser,
+ computeExecutablePath,
+ detectBrowserPlatform,
+ install,
+ resolveBuildId,
+} from '@puppeteer/browsers';
+import { existsSync } from 'node:fs';
+import { homedir, tmpdir } from 'node:os';
+import { join } from 'node:path';
+import { logError, logInfo } from '../core/mcp.logger';
+
+const CHROMIUM_CHANNEL = 'stable';
+const PROGRESS_LOG_STEP_PERCENT = 10;
+const FALLBACK_CACHE_DIR = join(tmpdir(), 'quickmock-browsers');
+const USER_CACHE_DIR_SEGMENTS = ['.quickmock', 'browsers'] as const;
+
+let cachedPath: Promise | undefined;
+
+export function resolveChromiumExecutable(): Promise {
+ cachedPath ??= doResolve();
+ return cachedPath;
+}
+
+async function doResolve(): Promise {
+ const cacheDir = getCacheDir();
+ const platform = detectBrowserPlatform();
+ if (!platform) throw new Error('Unsupported platform for Chromium download.');
+
+ const buildId = await resolveBuildId(
+ Browser.CHROME,
+ platform,
+ CHROMIUM_CHANNEL
+ );
+ const executablePath = computeExecutablePath({
+ browser: Browser.CHROME,
+ buildId,
+ cacheDir,
+ });
+
+ if (existsSync(executablePath)) return executablePath;
+
+ logInfo(
+ `Chromium not found in "${cacheDir}". Downloading ${Browser.CHROME}@${buildId} for ${platform}…`
+ );
+
+ let lastLoggedPercent = -1;
+ await install({
+ browser: Browser.CHROME,
+ buildId,
+ cacheDir,
+ downloadProgressCallback: (downloaded, total) => {
+ if (!total) return;
+ const percent = Math.floor((downloaded / total) * 100);
+ if (
+ percent === lastLoggedPercent ||
+ percent % PROGRESS_LOG_STEP_PERCENT !== 0
+ )
+ return;
+ lastLoggedPercent = percent;
+ logInfo(`Downloading Chromium: ${percent}%`);
+ },
+ }).catch(err => {
+ logError('Chromium download failed:', err);
+ throw err;
+ });
+
+ logInfo(`Chromium ready at ${executablePath}`);
+ return executablePath;
+}
+
+function getCacheDir(): string {
+ const home = homedir();
+ if (home) return join(home, ...USER_CACHE_DIR_SEGMENTS);
+ return FALLBACK_CACHE_DIR;
+}
diff --git a/packages/mcp/src/renderer/headless.renderer.ts b/packages/mcp/src/renderer/headless.renderer.ts
new file mode 100644
index 00000000..733b7c8e
--- /dev/null
+++ b/packages/mcp/src/renderer/headless.renderer.ts
@@ -0,0 +1,53 @@
+import type { Browser } from 'puppeteer-core';
+import puppeteer from 'puppeteer-core';
+import { startBridgeServer } from './bridge.server';
+import { resolveChromiumExecutable } from './chromium.resolver';
+import {
+ screenshotIframe,
+ sendFileToApp,
+ waitForAppReady,
+ waitForRenderComplete,
+ watchNetworkFailures,
+} from './page.session';
+
+/** Renders a .qm file in a headless Chromium instance and returns a PNG buffer. */
+export async function renderWireframe(
+ content: string,
+ fileName: string
+): Promise {
+ const { server, port } = await startBridgeServer();
+
+ try {
+ return await withBrowser(async browser => {
+ const page = await browser.newPage();
+ await page.setViewport({ width: 1440, height: 900 });
+ await page.goto(`http://127.0.0.1:${port}`, {
+ waitUntil: 'domcontentloaded',
+ });
+
+ const networkFailure = watchNetworkFailures(page);
+ await waitForAppReady(page, networkFailure);
+ await sendFileToApp(page, content, fileName);
+ const bbox = await waitForRenderComplete(page);
+
+ return screenshotIframe(page, bbox);
+ });
+ } finally {
+ server.close();
+ }
+}
+
+async function withBrowser(
+ fn: (browser: Browser) => Promise
+): Promise {
+ const executablePath = await resolveChromiumExecutable();
+ const browser = await puppeteer.launch({
+ headless: true,
+ executablePath,
+ });
+ try {
+ return await fn(browser);
+ } finally {
+ await browser.close();
+ }
+}
diff --git a/packages/mcp/src/renderer/index.ts b/packages/mcp/src/renderer/index.ts
new file mode 100644
index 00000000..74363deb
--- /dev/null
+++ b/packages/mcp/src/renderer/index.ts
@@ -0,0 +1 @@
+export * from './headless.renderer';
diff --git a/packages/mcp/src/renderer/page.session.ts b/packages/mcp/src/renderer/page.session.ts
new file mode 100644
index 00000000..f732c9d3
--- /dev/null
+++ b/packages/mcp/src/renderer/page.session.ts
@@ -0,0 +1,119 @@
+import type { Page } from 'puppeteer-core';
+import { QM_APP_ORIGIN, QUICKMOCK_URL } from './app-url.consts';
+import {
+ LOCAL_INSTANCE_HINT,
+ READY_TIMEOUT_MS,
+ RENDER_TIMEOUT_MS,
+} from './renderer.consts';
+
+export interface ContentBbox {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+}
+
+/** Rejects early on network failure — avoids waiting the full READY_TIMEOUT_MS. */
+export function watchNetworkFailures(page: Page): Promise {
+ return new Promise((_, reject) => {
+ page.on('requestfailed', request => {
+ if (request.url().startsWith(QM_APP_ORIGIN)) {
+ const reason = request.failure()?.errorText ?? 'network error';
+ reject(
+ new Error(
+ `Cannot reach QuickMock app at "${QUICKMOCK_URL}": ${reason}.\n${LOCAL_INSTANCE_HINT}`
+ )
+ );
+ }
+ });
+ });
+}
+
+/** Waits for `qm:ready`, racing against `networkFailure` for fast error reporting. */
+export async function waitForAppReady(
+ page: Page,
+ networkFailure: Promise
+): Promise {
+ try {
+ await Promise.race([
+ page.waitForFunction(
+ () => (window as Window & { __qmReady?: boolean }).__qmReady === true,
+ {
+ timeout: READY_TIMEOUT_MS,
+ }
+ ),
+ networkFailure,
+ ]);
+ } catch (err) {
+ if (err instanceof Error && err.message.startsWith('Cannot reach'))
+ throw err;
+
+ const iframeLoaded = await page
+ .evaluate(
+ () =>
+ (window as Window & { __iframeLoaded?: boolean }).__iframeLoaded ===
+ true
+ )
+ .catch(() => false);
+
+ if (iframeLoaded) {
+ throw new Error(
+ `QuickMock app loaded but did not emit qm:ready within ${READY_TIMEOUT_MS}ms — ` +
+ 'the app may have changed its postMessage protocol.'
+ );
+ }
+
+ throw new Error(
+ `Cannot reach QuickMock app at "${QUICKMOCK_URL}" — ` +
+ `the iframe did not load within ${READY_TIMEOUT_MS}ms.\n${LOCAL_INSTANCE_HINT}`
+ );
+ }
+}
+
+/** Sends the file content to the QuickMock app via postMessage → iframe. */
+export async function sendFileToApp(
+ page: Page,
+ content: string,
+ fileName: string
+): Promise {
+ await page.evaluate(
+ ({ content, fileName, origin }) => {
+ const iframe = document.querySelector('iframe') as HTMLIFrameElement;
+ iframe.contentWindow?.postMessage(
+ { type: 'LOAD_FILE', payload: { data: JSON.parse(content), fileName } },
+ origin
+ );
+ },
+ { content, fileName, origin: QM_APP_ORIGIN }
+ );
+}
+
+/** Waits for the app to emit `qm:render-complete` and returns the content bbox. */
+export async function waitForRenderComplete(
+ page: Page
+): Promise {
+ await page.waitForFunction(
+ () =>
+ (window as Window & { __renderComplete?: boolean }).__renderComplete ===
+ true,
+ { timeout: RENDER_TIMEOUT_MS }
+ );
+
+ return page.evaluate(
+ () =>
+ (window as Window & { __renderBbox?: ContentBbox }).__renderBbox ??
+ undefined
+ );
+}
+
+/** Screenshots the iframe, cropped to `bbox` when provided. */
+export async function screenshotIframe(
+ page: Page,
+ bbox: ContentBbox | undefined
+): Promise {
+ const iframe = await page.$('iframe');
+ if (!iframe) throw new Error('iframe element not found in renderer page');
+
+ const screenshot = await iframe.screenshot({ type: 'png', clip: bbox });
+ return Buffer.from(screenshot);
+}
diff --git a/packages/mcp/src/renderer/renderer.consts.ts b/packages/mcp/src/renderer/renderer.consts.ts
new file mode 100644
index 00000000..21a4ccb9
--- /dev/null
+++ b/packages/mcp/src/renderer/renderer.consts.ts
@@ -0,0 +1,5 @@
+export const READY_TIMEOUT_MS = 15_000;
+export const RENDER_TIMEOUT_MS = 20_000;
+
+export const LOCAL_INSTANCE_HINT =
+ 'Set quickmock.appUrl in VS Code settings (or edit ~/.quickmock/app-url) to point at a local QuickMock instance, e.g. http://localhost:5173/editor.html.';
diff --git a/packages/mcp/src/tools/capture-wireframe/capture-wireframe.handler.ts b/packages/mcp/src/tools/capture-wireframe/capture-wireframe.handler.ts
new file mode 100644
index 00000000..4e859003
--- /dev/null
+++ b/packages/mcp/src/tools/capture-wireframe/capture-wireframe.handler.ts
@@ -0,0 +1,43 @@
+import { toolError, toolImage } from '#/commons/tool-response.helpers';
+import type { WireframeFileService } from '#/commons/wireframe-file.service';
+import { renderWireframe } from '#/renderer/headless.renderer';
+import { QUICKMOCK_URL } from '#renderer/app-url.consts.js';
+import { basename } from 'node:path';
+
+export async function captureWireframeHandler(
+ args: { path: string; pageIndex?: number },
+ service: WireframeFileService
+) {
+ const { path, pageIndex = 0 } = args;
+
+ try {
+ const { absPath, content, parsed } = await service.readFile(path);
+ const fileName = basename(absPath);
+ const pageCount = parsed.pages.length;
+
+ if (pageIndex < 0 || pageIndex >= pageCount) {
+ return toolError(
+ `Page index ${pageIndex} is out of range. ` +
+ `"${fileName}" has ${pageCount} page${pageCount === 1 ? '' : 's'} (indices 0–${pageCount - 1}).`
+ );
+ }
+
+ const targetContent =
+ pageIndex === 0
+ ? content
+ : JSON.stringify({
+ ...parsed,
+ pages: [
+ parsed.pages[pageIndex],
+ ...parsed.pages.filter((_, i) => i !== pageIndex),
+ ],
+ });
+
+ const png = await renderWireframe(targetContent, fileName);
+ return toolImage(png.toString('base64'), 'image/png');
+ } catch (err) {
+ return toolError(
+ `Error capturing wireframe at "${path} with ${QUICKMOCK_URL}": ${String(err)}`
+ );
+ }
+}
diff --git a/packages/mcp/src/tools/capture-wireframe/capture-wireframe.schema.ts b/packages/mcp/src/tools/capture-wireframe/capture-wireframe.schema.ts
new file mode 100644
index 00000000..bfc974ce
--- /dev/null
+++ b/packages/mcp/src/tools/capture-wireframe/capture-wireframe.schema.ts
@@ -0,0 +1,13 @@
+import { z } from 'zod';
+
+export const captureWireframeSchema = {
+ path: z.string().describe('Relative or absolute path to the .qm file'),
+ pageIndex: z
+ .number()
+ .int()
+ .min(0)
+ .optional()
+ .describe(
+ 'Zero-based index of the page to capture (default: 0). Use get_wireframe_pages to see all available pages.'
+ ),
+};
diff --git a/packages/mcp/src/tools/capture-wireframe/capture-wireframe.tool.ts b/packages/mcp/src/tools/capture-wireframe/capture-wireframe.tool.ts
new file mode 100644
index 00000000..7e90be0e
--- /dev/null
+++ b/packages/mcp/src/tools/capture-wireframe/capture-wireframe.tool.ts
@@ -0,0 +1,11 @@
+import { captureWireframeHandler } from './capture-wireframe.handler';
+import { captureWireframeSchema } from './capture-wireframe.schema';
+
+export const captureWireframe = {
+ name: 'capture_wireframe' as const,
+ description:
+ 'Returns a PNG screenshot of a fully-rendered .qm wireframe file. ' +
+ 'Use get_wireframe_pages first to discover available pages and their indices. ',
+ schema: captureWireframeSchema,
+ execute: captureWireframeHandler,
+};
diff --git a/packages/mcp/src/tools/capture-wireframe/index.ts b/packages/mcp/src/tools/capture-wireframe/index.ts
new file mode 100644
index 00000000..4a90e035
--- /dev/null
+++ b/packages/mcp/src/tools/capture-wireframe/index.ts
@@ -0,0 +1 @@
+export * from './capture-wireframe.tool';
diff --git a/packages/mcp/src/tools/get-wireframe-assets/get-wireframe-assets.handler.ts b/packages/mcp/src/tools/get-wireframe-assets/get-wireframe-assets.handler.ts
new file mode 100644
index 00000000..4185dfbc
--- /dev/null
+++ b/packages/mcp/src/tools/get-wireframe-assets/get-wireframe-assets.handler.ts
@@ -0,0 +1,118 @@
+import { toolError, toolText } from '#/commons/tool-response.helpers';
+import type { WireframeFileService } from '#/commons/wireframe-file.service';
+import { createHash } from 'node:crypto';
+import { mkdir, writeFile } from 'node:fs/promises';
+import { basename, extname, join, resolve } from 'node:path';
+
+interface ParsedDataUrl {
+ mimeType: string;
+ base64: string;
+}
+
+function parseDataUrl(src: string): ParsedDataUrl | null {
+ const match = src.match(/^data:([^;]+);base64,(.+)$/);
+ if (!match?.[1] || !match[2]) {
+ return null;
+ }
+ return { mimeType: match[1], base64: match[2] };
+}
+
+function mimeTypeToExtension(mimeType: string): string {
+ const map: Record = {
+ 'image/png': 'png',
+ 'image/jpeg': 'jpg',
+ 'image/jpg': 'jpg',
+ 'image/gif': 'gif',
+ 'image/webp': 'webp',
+ 'image/svg+xml': 'svg',
+ };
+ return map[mimeType] ?? 'bin';
+}
+
+function sanitizeName(name: string): string {
+ return name.replace(/[^a-z0-9_-]/gi, '_').toLowerCase();
+}
+
+interface SavedAsset {
+ pageIndex: number;
+ pageName: string;
+ shapeId: string;
+ filePath: string;
+ mimeType: string;
+}
+
+export async function getWireframeAssetsHandler(
+ args: { path: string; outputDir?: string },
+ service: WireframeFileService
+) {
+ try {
+ const { absPath, parsed } = await service.readFile(args.path);
+ const workspaceRoot = process.env.QM_WORKSPACE_ROOT ?? process.cwd();
+ const wireframeName = sanitizeName(basename(absPath, extname(absPath)));
+
+ const targetDir = args.outputDir
+ ? resolve(workspaceRoot, args.outputDir)
+ : join(workspaceRoot, 'images', wireframeName);
+
+ await mkdir(targetDir, { recursive: true });
+
+ const seenHashes = new Set();
+ const saved: SavedAsset[] = [];
+
+ for (const [pageIndex, page] of parsed.pages.entries()) {
+ for (const shape of page.shapes) {
+ if (shape.type !== 'image') {
+ continue;
+ }
+ const src = shape.otherProps?.imageSrc;
+ if (!src) {
+ continue;
+ }
+
+ const dataUrl = parseDataUrl(src);
+ if (!dataUrl) {
+ continue;
+ }
+
+ const { mimeType, base64 } = dataUrl;
+ const hash = createHash('sha1').update(base64).digest('hex');
+ if (seenHashes.has(hash)) {
+ continue;
+ }
+ seenHashes.add(hash);
+
+ const ext = mimeTypeToExtension(mimeType);
+ const fileName = `${sanitizeName(page.name)}-${shape.id}.${ext}`;
+ const filePath = join(targetDir, fileName);
+
+ await writeFile(filePath, Buffer.from(base64, 'base64'));
+ saved.push({
+ pageIndex,
+ pageName: page.name,
+ shapeId: shape.id,
+ filePath,
+ mimeType,
+ });
+ }
+ }
+
+ if (saved.length === 0) {
+ return toolText('No image assets found in this wireframe.');
+ }
+
+ const summary = saved
+ .map(
+ a =>
+ `[${a.pageIndex}] "${a.pageName}" · ${a.shapeId} (${a.mimeType}) → ${a.filePath}`
+ )
+ .join('\n');
+
+ return toolText(
+ `Saved ${saved.length} asset(s) to "${targetDir}":\n\n${summary}`
+ );
+ } catch (err) {
+ return toolError(
+ `Error extracting assets from "${args.path}": ${String(err)}`
+ );
+ }
+}
diff --git a/packages/mcp/src/tools/get-wireframe-assets/get-wireframe-assets.models.ts b/packages/mcp/src/tools/get-wireframe-assets/get-wireframe-assets.models.ts
new file mode 100644
index 00000000..0fc06689
--- /dev/null
+++ b/packages/mcp/src/tools/get-wireframe-assets/get-wireframe-assets.models.ts
@@ -0,0 +1,6 @@
+export interface WireframeAsset {
+ shapeId: string;
+ pageIndex: number;
+ pageName: string;
+ filePath: string;
+}
diff --git a/packages/mcp/src/tools/get-wireframe-assets/get-wireframe-assets.schema.ts b/packages/mcp/src/tools/get-wireframe-assets/get-wireframe-assets.schema.ts
new file mode 100644
index 00000000..cf1d203d
--- /dev/null
+++ b/packages/mcp/src/tools/get-wireframe-assets/get-wireframe-assets.schema.ts
@@ -0,0 +1,13 @@
+import { z } from 'zod';
+
+export const getWireframeAssetsSchema = {
+ path: z.string().describe('Relative or absolute path to the .qm file'),
+ outputDir: z
+ .string()
+ .optional()
+ .describe(
+ 'Directory where PNG files will be saved. ' +
+ 'Relative paths are resolved from the workspace root. ' +
+ 'Defaults to "images/" inside the workspace root.'
+ ),
+};
diff --git a/packages/mcp/src/tools/get-wireframe-assets/get-wireframe-assets.tool.ts b/packages/mcp/src/tools/get-wireframe-assets/get-wireframe-assets.tool.ts
new file mode 100644
index 00000000..a8a372fd
--- /dev/null
+++ b/packages/mcp/src/tools/get-wireframe-assets/get-wireframe-assets.tool.ts
@@ -0,0 +1,13 @@
+import { getWireframeAssetsHandler } from './get-wireframe-assets.handler';
+import { getWireframeAssetsSchema } from './get-wireframe-assets.schema';
+
+export const getWireframeAssets = {
+ name: 'get_wireframe_assets' as const,
+ description:
+ 'Extracts all image assets (logos, content images, etc.) from a .qm wireframe file. ' +
+ 'Finds every shape of type "image" that has an imageSrc, saves each one as a PNG/JPEG/etc. ' +
+ 'to "images//" inside the workspace root (or outputDir if provided), ' +
+ 'and returns the images as inline content so they can be viewed directly.',
+ schema: getWireframeAssetsSchema,
+ execute: getWireframeAssetsHandler,
+};
diff --git a/packages/mcp/src/tools/get-wireframe-assets/index.ts b/packages/mcp/src/tools/get-wireframe-assets/index.ts
new file mode 100644
index 00000000..0d67ef00
--- /dev/null
+++ b/packages/mcp/src/tools/get-wireframe-assets/index.ts
@@ -0,0 +1 @@
+export * from './get-wireframe-assets.tool';
diff --git a/packages/mcp/src/tools/get-wireframe-json/get-wireframe-json.handler.ts b/packages/mcp/src/tools/get-wireframe-json/get-wireframe-json.handler.ts
new file mode 100644
index 00000000..257c75ab
--- /dev/null
+++ b/packages/mcp/src/tools/get-wireframe-json/get-wireframe-json.handler.ts
@@ -0,0 +1,16 @@
+import { toolError, toolText } from '#/commons/tool-response.helpers';
+import type { WireframeFileService } from '#/commons/wireframe-file.service';
+
+export async function getWireframeJsonHandler(
+ args: { path: string },
+ service: WireframeFileService
+) {
+ try {
+ const { content } = await service.readFile(args.path);
+ return toolText(content);
+ } catch (err) {
+ return toolError(
+ `Error reading wireframe at "${args.path}": ${String(err)}`
+ );
+ }
+}
diff --git a/packages/mcp/src/tools/get-wireframe-json/get-wireframe-json.schema.ts b/packages/mcp/src/tools/get-wireframe-json/get-wireframe-json.schema.ts
new file mode 100644
index 00000000..5f3dec93
--- /dev/null
+++ b/packages/mcp/src/tools/get-wireframe-json/get-wireframe-json.schema.ts
@@ -0,0 +1,5 @@
+import { z } from 'zod';
+
+export const getWireframeJsonSchema = {
+ path: z.string().describe('Relative or absolute path to the .qm file'),
+};
diff --git a/packages/mcp/src/tools/get-wireframe-json/get-wireframe-json.tool.ts b/packages/mcp/src/tools/get-wireframe-json/get-wireframe-json.tool.ts
new file mode 100644
index 00000000..ba237ba2
--- /dev/null
+++ b/packages/mcp/src/tools/get-wireframe-json/get-wireframe-json.tool.ts
@@ -0,0 +1,10 @@
+import { getWireframeJsonHandler } from './get-wireframe-json.handler';
+import { getWireframeJsonSchema } from './get-wireframe-json.schema';
+
+export const getWireframeJson = {
+ name: 'get_wireframe_json' as const,
+ description:
+ 'Returns the JSON content of a .qm wireframe file. When the file is open in the editor with unsaved changes, returns the latest in-memory state instead of the saved file.',
+ schema: getWireframeJsonSchema,
+ execute: getWireframeJsonHandler,
+};
diff --git a/packages/mcp/src/tools/get-wireframe-json/index.ts b/packages/mcp/src/tools/get-wireframe-json/index.ts
new file mode 100644
index 00000000..7e2ea9a1
--- /dev/null
+++ b/packages/mcp/src/tools/get-wireframe-json/index.ts
@@ -0,0 +1 @@
+export * from './get-wireframe-json.tool';
diff --git a/packages/mcp/src/tools/get-wireframe-pages/get-wireframe-pages.handler.ts b/packages/mcp/src/tools/get-wireframe-pages/get-wireframe-pages.handler.ts
new file mode 100644
index 00000000..ce5a473b
--- /dev/null
+++ b/packages/mcp/src/tools/get-wireframe-pages/get-wireframe-pages.handler.ts
@@ -0,0 +1,23 @@
+import { toolError, toolText } from '#/commons/tool-response.helpers';
+import type { WireframeFileService } from '#/commons/wireframe-file.service';
+import type { WireframePage } from './get-wireframe-pages.models';
+
+export async function getWireframePagesHandler(
+ args: { path: string },
+ service: WireframeFileService
+) {
+ try {
+ const { parsed } = await service.readFile(args.path);
+
+ const pages: WireframePage[] = parsed.pages.map((page, index) => ({
+ index,
+ id: page.id,
+ name: page.name,
+ shapeCount: page.shapes.length,
+ }));
+
+ return toolText(JSON.stringify(pages, null, 2));
+ } catch (err) {
+ return toolError(`Error reading pages from "${args.path}": ${String(err)}`);
+ }
+}
diff --git a/packages/mcp/src/tools/get-wireframe-pages/get-wireframe-pages.models.ts b/packages/mcp/src/tools/get-wireframe-pages/get-wireframe-pages.models.ts
new file mode 100644
index 00000000..b52537f7
--- /dev/null
+++ b/packages/mcp/src/tools/get-wireframe-pages/get-wireframe-pages.models.ts
@@ -0,0 +1,6 @@
+export interface WireframePage {
+ index: number;
+ id: string;
+ name: string;
+ shapeCount: number;
+}
diff --git a/packages/mcp/src/tools/get-wireframe-pages/get-wireframe-pages.schema.ts b/packages/mcp/src/tools/get-wireframe-pages/get-wireframe-pages.schema.ts
new file mode 100644
index 00000000..ef59c58e
--- /dev/null
+++ b/packages/mcp/src/tools/get-wireframe-pages/get-wireframe-pages.schema.ts
@@ -0,0 +1,5 @@
+import { z } from 'zod';
+
+export const getWireframePagesSchema = {
+ path: z.string().describe('Relative or absolute path to the .qm file'),
+};
diff --git a/packages/mcp/src/tools/get-wireframe-pages/get-wireframe-pages.tool.ts b/packages/mcp/src/tools/get-wireframe-pages/get-wireframe-pages.tool.ts
new file mode 100644
index 00000000..970a8163
--- /dev/null
+++ b/packages/mcp/src/tools/get-wireframe-pages/get-wireframe-pages.tool.ts
@@ -0,0 +1,11 @@
+import { getWireframePagesHandler } from './get-wireframe-pages.handler';
+import { getWireframePagesSchema } from './get-wireframe-pages.schema';
+
+export const getWireframePages = {
+ name: 'get_wireframe_pages' as const,
+ description:
+ 'Returns the list of pages in a .qm wireframe file with their index, id, name, and shape count. ' +
+ 'Use the index values with capture_wireframe to screenshot a specific page.',
+ schema: getWireframePagesSchema,
+ execute: getWireframePagesHandler,
+};
diff --git a/packages/mcp/src/tools/get-wireframe-pages/index.ts b/packages/mcp/src/tools/get-wireframe-pages/index.ts
new file mode 100644
index 00000000..9de8913e
--- /dev/null
+++ b/packages/mcp/src/tools/get-wireframe-pages/index.ts
@@ -0,0 +1 @@
+export * from './get-wireframe-pages.tool';
diff --git a/packages/mcp/src/tools/list-wireframes/index.ts b/packages/mcp/src/tools/list-wireframes/index.ts
new file mode 100644
index 00000000..5467112e
--- /dev/null
+++ b/packages/mcp/src/tools/list-wireframes/index.ts
@@ -0,0 +1 @@
+export * from './list-wireframes.tool';
diff --git a/packages/mcp/src/tools/list-wireframes/list-wireframes.handler.ts b/packages/mcp/src/tools/list-wireframes/list-wireframes.handler.ts
new file mode 100644
index 00000000..15323d30
--- /dev/null
+++ b/packages/mcp/src/tools/list-wireframes/list-wireframes.handler.ts
@@ -0,0 +1,52 @@
+import { toolError, toolText } from '#/commons/tool-response.helpers';
+import { readdir } from 'node:fs/promises';
+import { join, relative } from 'node:path';
+
+const IGNORED_DIRS = new Set([
+ 'node_modules',
+ '.git',
+ 'dist',
+ 'out',
+ '.vscode',
+]);
+
+export async function listWireframesHandler() {
+ const root = process.env.QM_WORKSPACE_ROOT ?? process.cwd();
+
+ try {
+ const files = await collectQmFiles(root, root);
+ return toolText(JSON.stringify(files, null, 2));
+ } catch (err) {
+ return toolError(`Error scanning workspace: ${String(err)}`);
+ }
+}
+
+async function collectQmFiles(dir: string, root: string): Promise {
+ let entries: import('node:fs').Dirent[];
+ try {
+ entries = (await readdir(dir, {
+ withFileTypes: true,
+ })) as unknown as import('node:fs').Dirent[];
+ } catch {
+ return [];
+ }
+
+ const results: string[] = [];
+
+ for (const entry of entries) {
+ if (IGNORED_DIRS.has(entry.name)) {
+ continue;
+ }
+
+ const fullPath = join(dir, entry.name);
+
+ if (entry.isDirectory()) {
+ const nested = await collectQmFiles(fullPath, root);
+ results.push(...nested);
+ } else if (entry.isFile() && entry.name.endsWith('.qm')) {
+ results.push(relative(root, fullPath));
+ }
+ }
+
+ return results;
+}
diff --git a/packages/mcp/src/tools/list-wireframes/list-wireframes.tool.ts b/packages/mcp/src/tools/list-wireframes/list-wireframes.tool.ts
new file mode 100644
index 00000000..9b1473d1
--- /dev/null
+++ b/packages/mcp/src/tools/list-wireframes/list-wireframes.tool.ts
@@ -0,0 +1,8 @@
+import { listWireframesHandler } from './list-wireframes.handler';
+
+export const listWireframes = {
+ name: 'list_wireframes' as const,
+ description:
+ 'Lists all .qm wireframe files in the current workspace. Returns paths relative to the workspace root.',
+ execute: listWireframesHandler,
+};
diff --git a/packages/mcp/tsconfig.json b/packages/mcp/tsconfig.json
index 38115089..e9acf234 100644
--- a/packages/mcp/tsconfig.json
+++ b/packages/mcp/tsconfig.json
@@ -3,6 +3,7 @@
"include": ["src"],
"compilerOptions": {
"rootDir": "src",
+ "lib": ["ES2024", "DOM"],
"paths": {
"#*": ["./src/*"]
}
diff --git a/packages/mcp/tsdown.config.ts b/packages/mcp/tsdown.config.ts
index 33d5ceda..0ecb8f03 100644
--- a/packages/mcp/tsdown.config.ts
+++ b/packages/mcp/tsdown.config.ts
@@ -3,4 +3,8 @@ import { baseTsdownConfig } from '@lemoncode/tsdown-config/base';
export default {
...baseTsdownConfig,
entry: ['src/index.ts'],
+ // Add a shebang so the built file can run as an npm bin (npx @lemoncode/quickmock-mcp).
+ outputOptions: {
+ banner: '#!/usr/bin/env node',
+ },
};
diff --git a/packages/registry-protocol/package.json b/packages/registry-protocol/package.json
new file mode 100644
index 00000000..59f317b9
--- /dev/null
+++ b/packages/registry-protocol/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "@lemoncode/quickmock-registry-protocol",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "exports": {
+ ".": "./src/index.ts"
+ },
+ "scripts": {
+ "check-types": "tsc --noEmit"
+ },
+ "devDependencies": {
+ "@lemoncode/typescript-config": "*"
+ }
+}
diff --git a/packages/registry-protocol/src/constant.ts b/packages/registry-protocol/src/constant.ts
new file mode 100644
index 00000000..90bb6ecb
--- /dev/null
+++ b/packages/registry-protocol/src/constant.ts
@@ -0,0 +1,5 @@
+export const TOKEN_HEADER = 'x-quickmock-token';
+export const LOOPBACK_HOST = '127.0.0.1';
+export const DOCUMENT_ROUTE = '/document';
+export const PORT_TOKEN_SEPARATOR = ' ';
+export const WORKSPACE_HASH_LENGTH = 8;
diff --git a/packages/registry-protocol/src/index.ts b/packages/registry-protocol/src/index.ts
new file mode 100644
index 00000000..a99efb16
--- /dev/null
+++ b/packages/registry-protocol/src/index.ts
@@ -0,0 +1,2 @@
+export * from './constant';
+export * from './utils';
diff --git a/packages/registry-protocol/src/utils.ts b/packages/registry-protocol/src/utils.ts
new file mode 100644
index 00000000..01ff618d
--- /dev/null
+++ b/packages/registry-protocol/src/utils.ts
@@ -0,0 +1,25 @@
+import { createHash } from 'node:crypto';
+import { tmpdir } from 'node:os';
+import { join } from 'node:path';
+import { PORT_TOKEN_SEPARATOR, WORKSPACE_HASH_LENGTH } from './constant';
+
+export function buildPortFilePath(workspaceRoot: string): string {
+ const hash = createHash('md5')
+ .update(workspaceRoot)
+ .digest('hex')
+ .slice(0, WORKSPACE_HASH_LENGTH);
+ return join(tmpdir(), `quickmock-${hash}.port`);
+}
+
+export function encodePortFile(port: number, token: string): string {
+ return `${port}${PORT_TOKEN_SEPARATOR}${token}`;
+}
+
+export function parsePortFile(
+ raw: string
+): { port: number; token: string } | null {
+ const [portStr, token] = raw.trim().split(PORT_TOKEN_SEPARATOR);
+ const port = Number.parseInt(portStr ?? '', 10);
+ if (Number.isNaN(port) || !token) return null;
+ return { port, token };
+}
diff --git a/packages/registry-protocol/tsconfig.json b/packages/registry-protocol/tsconfig.json
new file mode 100644
index 00000000..77ce3f8a
--- /dev/null
+++ b/packages/registry-protocol/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "@lemoncode/typescript-config/node",
+ "include": ["src"]
+}
diff --git a/packages/vscode-extension/.vscodeignore b/packages/vscode-extension/.vscodeignore
index 7dada2d1..a4774897 100644
--- a/packages/vscode-extension/.vscodeignore
+++ b/packages/vscode-extension/.vscodeignore
@@ -1,8 +1,11 @@
src/**
node_modules/**
+scripts/**
*.ts
tsconfig.json
tsdown.config.ts
vitest.config.ts
.turbo/**
+.env*
coverage/**
+*.vsix
diff --git a/packages/vscode-extension/CHANGELOG.md b/packages/vscode-extension/CHANGELOG.md
index 66de67b2..418af359 100644
--- a/packages/vscode-extension/CHANGELOG.md
+++ b/packages/vscode-extension/CHANGELOG.md
@@ -1,5 +1,30 @@
# @lemoncode/quickmock-vscode-extension
+## 0.1.0
+
+### Minor Changes
+
+- Bump 0.1.0
+- 2282316: First public release of the QuickMock VS Code extension and its MCP server.
+
+ **`quickmock` (VS Code extension)**
+ - Custom editor for `.qm` files backed by the QuickMock web app, served inside a webview.
+ - `quickmock.appUrl` setting (default `https://quickmock.net/editor.html`) to point the editor and the MCP renderer at any QuickMock instance. Changes refresh open editors and respawn the MCP server.
+ - Automatic MCP server registration for VS Code / GitHub Copilot, Claude Code, Cursor, Windsurf and Claude Desktop, plus a dynamic `McpServerDefinitionProvider`. Existing entries are refreshed on activation so users always end up pointing at the right MCP invocation.
+ - The MCP server is no longer bundled inside the `.vsix`. In production the extension spawns it on demand via `npx -y @lemoncode/quickmock-mcp`, so users always run the latest published MCP without waiting for an extension release. In development it resolves the local workspace build.
+
+ **`@lemoncode/quickmock-mcp` (MCP server)**
+ - MCP tools to explore and render wireframes: `list_wireframes`, `get_wireframe_json`, `get_wireframe_pages`, `get_wireframe_assets` and `capture_wireframe`.
+ - Headless screenshot pipeline via `puppeteer-core` against the QuickMock app, using a postMessage bridge.
+ - On-demand Chromium download via `@puppeteer/browsers`, cached under `~/.quickmock/browsers`, so headless rendering works without relying on the user's local browser install.
+ - Reads the target app URL from `~/.quickmock/app-url` (written by the extension) with a production fallback, so the MCP works out of the box regardless of how it is spawned.
+
+### Patch Changes
+
+- Updated dependencies
+- Updated dependencies [2282316]
+ - @lemoncode/quickmock-mcp@0.1.0
+
## 0.0.1
### Patch Changes
diff --git a/packages/vscode-extension/package.json b/packages/vscode-extension/package.json
index 63b796f1..79a738b5 100644
--- a/packages/vscode-extension/package.json
+++ b/packages/vscode-extension/package.json
@@ -2,7 +2,8 @@
"name": "quickmock",
"version": "0.0.1",
"type": "module",
- "main": "./dist/index.mjs",
+ "main": "./dist/index.cjs",
+ "module": "./dist/index.mjs",
"imports": {
"#*": "./src/*"
},
@@ -14,11 +15,16 @@
"test:watch": "vitest",
"test:coverage": "vitest run --coverage"
},
+ "dependencies": {
+ "@lemoncode/quickmock-mcp": "*"
+ },
"devDependencies": {
+ "@lemoncode/quickmock-bridge-protocol": "*",
+ "@lemoncode/quickmock-registry-protocol": "*",
"@lemoncode/tsdown-config": "*",
"@lemoncode/typescript-config": "*",
"@lemoncode/vitest-config": "*",
- "@types/vscode": "1.116.0",
+ "@types/vscode": "1.115.0",
"@vscode/vsce": "3.9.0"
},
"publisher": "Lemoncoders",
@@ -47,7 +53,7 @@
"directory": "packages/vscode-extension"
},
"engines": {
- "vscode": "^1.116.0"
+ "vscode": "^1.115.0"
},
"icon": "./assets/app-icon.webp",
"galleryBanner": {
@@ -55,13 +61,44 @@
"theme": "dark"
},
"qna": false,
- "activationEvents": [],
+ "activationEvents": [
+ "onStartupFinished"
+ ],
"contributes": {
+ "customEditors": [
+ {
+ "viewType": "quickmock.editor",
+ "displayName": "QuickMock Wireframe Editor",
+ "selector": [
+ {
+ "filenamePattern": "*.qm"
+ }
+ ],
+ "priority": "default"
+ }
+ ],
"commands": [
{
- "command": "quickmock.helloWorld",
- "title": "Quickmock: Hello World"
+ "command": "quickmock.newWireframe",
+ "title": "QuickMock: New Wireframe"
+ }
+ ],
+ "mcpServerDefinitionProviders": [
+ {
+ "id": "quickmock",
+ "label": "QuickMock Wireframe Tools"
+ }
+ ],
+ "configuration": {
+ "title": "QuickMock",
+ "properties": {
+ "quickmock.appUrl": {
+ "type": "string",
+ "default": "https://quickmock.net/editor.html",
+ "format": "uri",
+ "markdownDescription": "Base URL of the QuickMock web app used by the custom editor and the MCP renderer. The extension automatically appends `?env=vscode` for the webview and `&headless=1` for the headless screenshot MCP. Changing this refreshes open editors and respawns the MCP server."
+ }
}
- ]
+ }
}
}
diff --git a/packages/vscode-extension/src/core/config.ts b/packages/vscode-extension/src/core/config.ts
new file mode 100644
index 00000000..5eb6ff16
--- /dev/null
+++ b/packages/vscode-extension/src/core/config.ts
@@ -0,0 +1,46 @@
+import { mkdirSync, writeFileSync } from 'node:fs';
+import { dirname } from 'node:path';
+import * as vscode from 'vscode';
+import { logError } from './logger';
+import { APP_URL_FILE } from './paths';
+
+const SECTION = 'quickmock';
+const APP_URL_KEY = 'appUrl';
+const FULL_KEY = `${SECTION}.${APP_URL_KEY}`;
+const DEFAULT_APP_URL = 'https://quickmock.net/editor.html';
+
+const EDITOR_PARAMS = { env: 'vscode' } as const;
+const HEADLESS_PARAMS = { env: 'vscode', headless: '1' } as const;
+
+const readRawAppUrl = (): string => {
+ const value = vscode.workspace
+ .getConfiguration(SECTION)
+ .get(APP_URL_KEY);
+ return value?.trim() || DEFAULT_APP_URL;
+};
+
+const withParams = (url: string, params: Record): string => {
+ const parsed = new URL(url);
+ for (const [k, v] of Object.entries(params)) parsed.searchParams.set(k, v);
+ return parsed.toString();
+};
+
+export const getEditorAppUrl = (): string =>
+ withParams(readRawAppUrl(), EDITOR_PARAMS);
+
+export const getHeadlessAppUrl = (): string =>
+ withParams(readRawAppUrl(), HEADLESS_PARAMS);
+
+export const syncAppUrlFile = (): void => {
+ try {
+ mkdirSync(dirname(APP_URL_FILE), { recursive: true });
+ writeFileSync(APP_URL_FILE, getHeadlessAppUrl(), 'utf-8');
+ } catch (err) {
+ logError('Failed to write app URL file:', err);
+ }
+};
+
+export const onAppUrlChange = (listener: () => void): vscode.Disposable =>
+ vscode.workspace.onDidChangeConfiguration(e => {
+ if (e.affectsConfiguration(FULL_KEY)) listener();
+ });
diff --git a/packages/vscode-extension/src/core/document-registry.ts b/packages/vscode-extension/src/core/document-registry.ts
new file mode 100644
index 00000000..87caee02
--- /dev/null
+++ b/packages/vscode-extension/src/core/document-registry.ts
@@ -0,0 +1,17 @@
+export class DocumentRegistry {
+ private readonly map = new Map();
+
+ set(fsPath: string, content: string): void {
+ this.map.set(fsPath, content);
+ }
+
+ get(fsPath: string): string | undefined {
+ return this.map.get(fsPath);
+ }
+
+ delete(fsPath: string): void {
+ this.map.delete(fsPath);
+ }
+}
+
+export const documentRegistry = new DocumentRegistry();
diff --git a/packages/vscode-extension/src/core/logger.ts b/packages/vscode-extension/src/core/logger.ts
new file mode 100644
index 00000000..a1098647
--- /dev/null
+++ b/packages/vscode-extension/src/core/logger.ts
@@ -0,0 +1,9 @@
+const PREFIX = '[QuickMock]';
+
+export const logInfo = (message: string, ...rest: unknown[]): void => {
+ console.info(`${PREFIX} ${message}`, ...rest);
+};
+
+export const logError = (message: string, ...rest: unknown[]): void => {
+ console.error(`${PREFIX} ${message}`, ...rest);
+};
diff --git a/packages/vscode-extension/src/core/paths.ts b/packages/vscode-extension/src/core/paths.ts
new file mode 100644
index 00000000..6f2fcb72
--- /dev/null
+++ b/packages/vscode-extension/src/core/paths.ts
@@ -0,0 +1,5 @@
+import { homedir } from 'node:os';
+import { join } from 'node:path';
+
+export const QUICKMOCK_HOME = join(homedir(), '.quickmock');
+export const APP_URL_FILE = join(QUICKMOCK_HOME, 'app-url');
diff --git a/packages/vscode-extension/src/editor/document.ts b/packages/vscode-extension/src/editor/document.ts
new file mode 100644
index 00000000..3a89f1d0
--- /dev/null
+++ b/packages/vscode-extension/src/editor/document.ts
@@ -0,0 +1,29 @@
+import * as vscode from 'vscode';
+
+export type QuickMockDocument = vscode.CustomDocument & {
+ readonly uri: vscode.Uri;
+ content: string;
+};
+
+export const openDocument = async (
+ uri: vscode.Uri,
+ openContext: vscode.CustomDocumentOpenContext
+): Promise => {
+ const source = openContext.backupId
+ ? vscode.Uri.parse(openContext.backupId)
+ : uri;
+ const content = await readFile(source);
+ return { uri, content, dispose: () => {} };
+};
+
+export const readFile = async (uri: vscode.Uri): Promise => {
+ const bytes = await vscode.workspace.fs.readFile(uri);
+ return new TextDecoder().decode(bytes);
+};
+
+export const writeFile = async (
+ uri: vscode.Uri,
+ content: string
+): Promise => {
+ await vscode.workspace.fs.writeFile(uri, new TextEncoder().encode(content));
+};
diff --git a/packages/vscode-extension/src/editor/handlers.ts b/packages/vscode-extension/src/editor/handlers.ts
new file mode 100644
index 00000000..e6795936
--- /dev/null
+++ b/packages/vscode-extension/src/editor/handlers.ts
@@ -0,0 +1,45 @@
+import { basename } from 'node:path';
+import {
+ APP_MESSAGE_TYPE,
+ type AppMessage,
+ HOST_MESSAGE_TYPE,
+ type HostMessage,
+} from '@lemoncode/quickmock-bridge-protocol';
+import { type QuickMockDocument, writeFile } from './document';
+
+type PostMessageFn = (msg: HostMessage) => void;
+
+export const handleWebviewMessage = async (
+ msg: AppMessage,
+ doc: QuickMockDocument,
+ postMessage: PostMessageFn
+): Promise => {
+ switch (msg.type) {
+ case APP_MESSAGE_TYPE.READY:
+ postMessage({
+ type: HOST_MESSAGE_TYPE.LOAD,
+ payload: { content: doc.content, fileName: basename(doc.uri.fsPath) },
+ });
+ break;
+
+ case APP_MESSAGE_TYPE.WEBVIEW_READY: {
+ let data: unknown;
+ try {
+ data = JSON.parse(doc.content);
+ } catch {
+ data = doc.content;
+ }
+ postMessage({
+ type: HOST_MESSAGE_TYPE.LOAD_FILE,
+ payload: { data, fileName: basename(doc.uri.fsPath) },
+ });
+ break;
+ }
+
+ case APP_MESSAGE_TYPE.SAVE:
+ doc.content = msg.payload.content;
+ await writeFile(doc.uri, doc.content);
+ postMessage({ type: HOST_MESSAGE_TYPE.SAVED });
+ break;
+ }
+};
diff --git a/packages/vscode-extension/src/editor/panel.ts b/packages/vscode-extension/src/editor/panel.ts
new file mode 100644
index 00000000..f943115f
--- /dev/null
+++ b/packages/vscode-extension/src/editor/panel.ts
@@ -0,0 +1,33 @@
+import * as vscode from 'vscode';
+
+const escapeAttr = (value: string): string =>
+ value.replace(/&/g, '&').replace(/"/g, '"');
+
+export const getHtml = (
+ webview: vscode.Webview,
+ extensionUri: vscode.Uri,
+ appUrl: string
+): string => {
+ const scriptUri = webview.asWebviewUri(
+ vscode.Uri.joinPath(extensionUri, 'dist', 'webview.js')
+ );
+ const appOrigin = new URL(appUrl).origin;
+ const wsOrigin = appOrigin.replace(/^http/, 'ws');
+
+ return /* html */ `
+
+
+
+
+
+
+
+
+
+
+`;
+};
diff --git a/packages/vscode-extension/src/editor/provider.ts b/packages/vscode-extension/src/editor/provider.ts
new file mode 100644
index 00000000..a9b9fa4e
--- /dev/null
+++ b/packages/vscode-extension/src/editor/provider.ts
@@ -0,0 +1,141 @@
+import { basename } from 'node:path';
+import * as vscode from 'vscode';
+import { getEditorAppUrl, onAppUrlChange } from '#core/config';
+import { documentRegistry } from '#core/document-registry';
+import {
+ type AppMessage,
+ HOST_MESSAGE_TYPE,
+ type HostMessage,
+} from '@lemoncode/quickmock-bridge-protocol';
+import {
+ openDocument,
+ type QuickMockDocument,
+ readFile,
+ writeFile,
+} from './document';
+import { handleWebviewMessage } from './handlers';
+import { getHtml } from './panel';
+
+export class QuickMockEditorProvider implements vscode.CustomEditorProvider {
+ static register(context: vscode.ExtensionContext): vscode.Disposable {
+ const provider = new QuickMockEditorProvider(context.extensionUri);
+ const editorRegistration = vscode.window.registerCustomEditorProvider(
+ 'quickmock.editor',
+ provider,
+ {
+ supportsMultipleEditorsPerDocument: false,
+ webviewOptions: { retainContextWhenHidden: true },
+ }
+ );
+ const configListener = onAppUrlChange(() => provider.refreshAllPanels());
+ return vscode.Disposable.from(editorRegistration, configListener);
+ }
+
+ constructor(private readonly extensionUri: vscode.Uri) {}
+
+ private readonly _onDidChangeCustomDocument = new vscode.EventEmitter<
+ vscode.CustomDocumentContentChangeEvent
+ >();
+ readonly onDidChangeCustomDocument = this._onDidChangeCustomDocument.event;
+
+ private readonly panels = new Map();
+
+ async openCustomDocument(
+ uri: vscode.Uri,
+ openContext: vscode.CustomDocumentOpenContext
+ ): Promise {
+ const doc = await openDocument(uri, openContext);
+ documentRegistry.set(doc.uri.fsPath, doc.content);
+ return doc;
+ }
+
+ async saveCustomDocument(
+ doc: QuickMockDocument,
+ _cancel: vscode.CancellationToken
+ ): Promise {
+ await writeFile(doc.uri, doc.content);
+ }
+
+ async saveCustomDocumentAs(
+ doc: QuickMockDocument,
+ dest: vscode.Uri,
+ _cancel: vscode.CancellationToken
+ ): Promise {
+ await writeFile(dest, doc.content);
+ }
+
+ async revertCustomDocument(
+ doc: QuickMockDocument,
+ _cancel: vscode.CancellationToken
+ ): Promise {
+ doc.content = await readFile(doc.uri);
+ this.broadcast(doc, {
+ type: HOST_MESSAGE_TYPE.LOAD,
+ payload: { content: doc.content, fileName: basename(doc.uri.fsPath) },
+ });
+ }
+
+ async backupCustomDocument(
+ doc: QuickMockDocument,
+ context: vscode.CustomDocumentBackupContext,
+ _cancel: vscode.CancellationToken
+ ): Promise {
+ await writeFile(context.destination, doc.content);
+ return {
+ id: context.destination.toString(),
+ delete: () => {
+ vscode.workspace.fs
+ .delete(context.destination)
+ .then(undefined, () => {});
+ },
+ };
+ }
+
+ resolveCustomEditor(
+ doc: QuickMockDocument,
+ panel: vscode.WebviewPanel,
+ _token: vscode.CancellationToken
+ ): void {
+ const key = doc.uri.toString();
+ this.panels.set(key, [...(this.panels.get(key) ?? []), panel]);
+ panel.onDidDispose(() => {
+ const remaining = (this.panels.get(key) ?? []).filter(p => p !== panel);
+ this.panels.set(key, remaining);
+ if (remaining.length === 0) {
+ documentRegistry.delete(doc.uri.fsPath);
+ }
+ });
+
+ panel.webview.options = {
+ enableScripts: true,
+ localResourceRoots: [this.extensionUri],
+ };
+ panel.webview.html = getHtml(
+ panel.webview,
+ this.extensionUri,
+ getEditorAppUrl()
+ );
+
+ panel.webview.onDidReceiveMessage(async (msg: AppMessage) => {
+ await handleWebviewMessage(msg, doc, reply =>
+ panel.webview.postMessage(reply satisfies HostMessage)
+ );
+ documentRegistry.set(doc.uri.fsPath, doc.content);
+ });
+ }
+
+ private broadcast(doc: QuickMockDocument, msg: HostMessage): void {
+ for (const panel of this.panels.get(doc.uri.toString()) ?? []) {
+ panel.webview.postMessage(msg);
+ }
+ }
+
+ refreshAllPanels(): void {
+ const url = getEditorAppUrl();
+ for (const panels of this.panels.values()) {
+ for (const panel of panels) {
+ panel.webview.html = getHtml(panel.webview, this.extensionUri, url);
+ }
+ }
+ }
+}
diff --git a/packages/vscode-extension/src/index.ts b/packages/vscode-extension/src/index.ts
index c0a7ab1d..0916497f 100644
--- a/packages/vscode-extension/src/index.ts
+++ b/packages/vscode-extension/src/index.ts
@@ -1,14 +1,33 @@
+import { onAppUrlChange, syncAppUrlFile } from '#core/config';
+import { logError } from '#core/logger';
+import { QuickMockEditorProvider } from '#editor/provider';
+import { registerMcpServer } from '#mcp/mcp-registration';
+import { RegistryServer } from '#mcp/registry-server';
+import { registerQuickMockMcpServerProvider } from '#mcp/server-definition-provider';
import * as vscode from 'vscode';
export const activate = (context: vscode.ExtensionContext) => {
- const disposable = vscode.commands.registerCommand(
- 'quickmock.helloWorld',
- () => {
- vscode.window.showInformationMessage('Quickmock extension is running!');
- }
+ syncAppUrlFile();
+ context.subscriptions.push(onAppUrlChange(syncAppUrlFile));
+
+ context.subscriptions.push(QuickMockEditorProvider.register(context));
+
+ const registryServer = new RegistryServer();
+ registryServer
+ .start(context)
+ .catch(err => logError('Failed to start MCP registry server:', err));
+
+ context.subscriptions.push(registerQuickMockMcpServerProvider(context));
+
+ registerMcpServer(context).catch(err =>
+ logError('Failed to register MCP server:', err)
);
- context.subscriptions.push(disposable);
+ context.subscriptions.push(
+ vscode.commands.registerCommand('quickmock.newWireframe', () => {
+ vscode.window.showInformationMessage('New wireframe coming soon');
+ })
+ );
};
export const deactivate = () => {};
diff --git a/packages/vscode-extension/src/mcp/mcp-client-targets.ts b/packages/vscode-extension/src/mcp/mcp-client-targets.ts
new file mode 100644
index 00000000..00d08902
--- /dev/null
+++ b/packages/vscode-extension/src/mcp/mcp-client-targets.ts
@@ -0,0 +1,62 @@
+import { homedir, platform } from 'node:os';
+import { join } from 'node:path';
+
+export interface McpClientTarget {
+ label: string;
+ path: string;
+}
+
+const CLAUDE_CODE: McpClientTarget = {
+ label: 'Claude Code',
+ path: join(homedir(), '.claude.json'),
+};
+
+const CURSOR: McpClientTarget = {
+ label: 'Cursor',
+ path: join(homedir(), '.cursor', 'mcp.json'),
+};
+
+const WINDSURF: McpClientTarget = {
+ label: 'Windsurf',
+ path: join(homedir(), '.codeium', 'windsurf', 'mcp_config.json'),
+};
+
+const CLAUDE_DESKTOP_FILE = 'claude_desktop_config.json';
+
+const getClaudeDesktopTarget = (): McpClientTarget => {
+ const home = homedir();
+ const os = platform();
+
+ if (os === 'darwin') {
+ return {
+ label: 'Claude Desktop',
+ path: join(
+ home,
+ 'Library',
+ 'Application Support',
+ 'Claude',
+ CLAUDE_DESKTOP_FILE
+ ),
+ };
+ }
+
+ if (os === 'win32') {
+ const appData = process.env.APPDATA ?? join(home, 'AppData', 'Roaming');
+ return {
+ label: 'Claude Desktop',
+ path: join(appData, 'Claude', CLAUDE_DESKTOP_FILE),
+ };
+ }
+
+ return {
+ label: 'Claude Desktop',
+ path: join(home, '.config', 'Claude', CLAUDE_DESKTOP_FILE),
+ };
+};
+
+export const getMcpClientTargets = (): McpClientTarget[] => [
+ CLAUDE_CODE,
+ CURSOR,
+ WINDSURF,
+ getClaudeDesktopTarget(),
+];
diff --git a/packages/vscode-extension/src/mcp/mcp-config-file.ts b/packages/vscode-extension/src/mcp/mcp-config-file.ts
new file mode 100644
index 00000000..1d7f0edf
--- /dev/null
+++ b/packages/vscode-extension/src/mcp/mcp-config-file.ts
@@ -0,0 +1,26 @@
+import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
+import { dirname } from 'node:path';
+
+export interface McpFileConfig {
+ mcpServers?: Record;
+ [key: string]: unknown;
+}
+
+export const readMcpFileConfig = (filePath: string): McpFileConfig => {
+ try {
+ return JSON.parse(readFileSync(filePath, 'utf-8')) as McpFileConfig;
+ } catch {
+ return {};
+ }
+};
+
+export const writeMcpFileConfig = (
+ filePath: string,
+ data: McpFileConfig
+): void => {
+ const dir = dirname(filePath);
+ if (!existsSync(dir)) {
+ mkdirSync(dir, { recursive: true });
+ }
+ writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
+};
diff --git a/packages/vscode-extension/src/mcp/mcp-invocation.ts b/packages/vscode-extension/src/mcp/mcp-invocation.ts
new file mode 100644
index 00000000..c5b07a5d
--- /dev/null
+++ b/packages/vscode-extension/src/mcp/mcp-invocation.ts
@@ -0,0 +1,27 @@
+import { createRequire } from 'node:module';
+import { dirname, join } from 'node:path';
+import * as vscode from 'vscode';
+
+export const MCP_SERVER_ID = 'quickmock';
+
+const MCP_PKG = '@lemoncode/quickmock-mcp';
+
+export interface McpInvocation {
+ command: string;
+ args: string[];
+}
+
+// Production: spawn the MCP from the published npm package via npx.
+// Development: spawn the local workspace build so changes are picked up on rebuild.
+export const getMcpInvocation = (
+ context: vscode.ExtensionContext
+): McpInvocation =>
+ context.extensionMode === vscode.ExtensionMode.Production
+ ? { command: 'npx', args: ['-y', MCP_PKG] }
+ : { command: 'node', args: [resolveLocalMcpEntry()] };
+
+const resolveLocalMcpEntry = (): string => {
+ const require = createRequire(import.meta.url);
+ const pkgJsonPath = require.resolve(`${MCP_PKG}/package.json`);
+ return join(dirname(pkgJsonPath), 'dist', 'index.mjs');
+};
diff --git a/packages/vscode-extension/src/mcp/mcp-registration.ts b/packages/vscode-extension/src/mcp/mcp-registration.ts
new file mode 100644
index 00000000..79e4ad58
--- /dev/null
+++ b/packages/vscode-extension/src/mcp/mcp-registration.ts
@@ -0,0 +1,100 @@
+import { existsSync } from 'node:fs';
+import { dirname } from 'node:path';
+import * as vscode from 'vscode';
+import { logError, logInfo } from '#core/logger';
+import { getMcpInvocation, MCP_SERVER_ID } from '#mcp/mcp-invocation';
+import type { McpInvocation } from '#mcp/mcp-invocation';
+import {
+ getMcpClientTargets,
+ type McpClientTarget,
+} from './mcp-client-targets';
+import { readMcpFileConfig, writeMcpFileConfig } from './mcp-config-file';
+
+const VSCODE_CLIENT_LABEL = 'VS Code / GitHub Copilot';
+const MCP_CONFIG_SECTION = 'mcp';
+const MCP_SERVERS_KEY = 'servers';
+
+export type RegistrationStatus = 'registered' | 'skipped' | 'error';
+
+export interface RegistrationResult {
+ label: string;
+ status: RegistrationStatus;
+ detail?: string;
+}
+
+interface McpServerEntry {
+ type: 'stdio';
+ command: string;
+ args: string[];
+}
+
+const buildMcpServerEntry = ({
+ command,
+ args,
+}: McpInvocation): McpServerEntry => ({
+ type: 'stdio',
+ command,
+ args,
+});
+
+const registerInVSCode = async (
+ entry: McpServerEntry
+): Promise => {
+ try {
+ const config = vscode.workspace.getConfiguration(MCP_CONFIG_SECTION);
+ const servers = config.get>(MCP_SERVERS_KEY) ?? {};
+ servers[MCP_SERVER_ID] = entry;
+ await config.update(
+ MCP_SERVERS_KEY,
+ servers,
+ vscode.ConfigurationTarget.Global
+ );
+ return { label: VSCODE_CLIENT_LABEL, status: 'registered' };
+ } catch (err) {
+ return {
+ label: VSCODE_CLIENT_LABEL,
+ status: 'error',
+ detail: String(err),
+ };
+ }
+};
+
+const registerInClientTarget = (
+ target: McpClientTarget,
+ entry: McpServerEntry
+): RegistrationResult => {
+ if (!existsSync(target.path) && !existsSync(dirname(target.path))) {
+ return { label: target.label, status: 'skipped', detail: 'Not installed' };
+ }
+
+ try {
+ const config = readMcpFileConfig(target.path);
+ if (!config.mcpServers) config.mcpServers = {};
+ config.mcpServers[MCP_SERVER_ID] = entry;
+ writeMcpFileConfig(target.path, config);
+ return { label: target.label, status: 'registered' };
+ } catch (err) {
+ return { label: target.label, status: 'error', detail: String(err) };
+ }
+};
+
+export const registerMcpServer = async (
+ context: vscode.ExtensionContext
+): Promise => {
+ const entry = buildMcpServerEntry(getMcpInvocation(context));
+
+ const results: RegistrationResult[] = [
+ await registerInVSCode(entry),
+ ...getMcpClientTargets().map(t => registerInClientTarget(t, entry)),
+ ];
+
+ for (const r of results) {
+ if (r.status === 'registered') {
+ logInfo(`MCP registered — ${r.label}`);
+ } else if (r.status === 'error') {
+ logError(`MCP registration failed — ${r.label}: ${r.detail}`);
+ }
+ }
+
+ return results;
+};
diff --git a/packages/vscode-extension/src/mcp/registry-server.ts b/packages/vscode-extension/src/mcp/registry-server.ts
new file mode 100644
index 00000000..55ac1214
--- /dev/null
+++ b/packages/vscode-extension/src/mcp/registry-server.ts
@@ -0,0 +1,96 @@
+import { randomBytes } from 'node:crypto';
+import { unlinkSync, writeFileSync } from 'node:fs';
+import {
+ createServer,
+ type IncomingMessage,
+ type ServerResponse,
+} from 'node:http';
+import {
+ buildPortFilePath,
+ DOCUMENT_ROUTE,
+ encodePortFile,
+ LOOPBACK_HOST,
+ TOKEN_HEADER,
+} from '@lemoncode/quickmock-registry-protocol';
+import * as vscode from 'vscode';
+import { documentRegistry } from '#core/document-registry';
+
+const TOKEN_BYTE_LENGTH = 32;
+const PORT_FILE_MODE = 0o600;
+
+export class RegistryServer {
+ private portFile: string | null = null;
+ private token = '';
+
+ async start(context: vscode.ExtensionContext): Promise {
+ const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
+ if (!workspaceRoot) {
+ return;
+ }
+
+ this.portFile = buildPortFilePath(workspaceRoot);
+ this.token = randomBytes(TOKEN_BYTE_LENGTH).toString('hex');
+
+ const server = createServer((req, res) => this.handleRequest(req, res));
+
+ await new Promise((resolve, reject) => {
+ server.on('error', reject);
+ server.listen(0, LOOPBACK_HOST, () => {
+ const { port } = server.address() as { port: number };
+ try {
+ writeFileSync(this.portFile!, encodePortFile(port, this.token), {
+ mode: PORT_FILE_MODE,
+ });
+ } catch (err) {
+ reject(err);
+ return;
+ }
+ resolve();
+ });
+ });
+
+ context.subscriptions.push({
+ dispose: () => {
+ server.close();
+ if (this.portFile) {
+ try {
+ unlinkSync(this.portFile);
+ } catch {}
+ }
+ },
+ });
+ }
+
+ private handleRequest(req: IncomingMessage, res: ServerResponse): void {
+ if (req.headers[TOKEN_HEADER] !== this.token) {
+ res.writeHead(401);
+ res.end();
+ return;
+ }
+
+ const url = new URL(req.url ?? '/', 'http://localhost');
+
+ if (url.pathname !== DOCUMENT_ROUTE) {
+ res.writeHead(404);
+ res.end();
+ return;
+ }
+
+ const path = url.searchParams.get('path');
+ if (!path) {
+ res.writeHead(400);
+ res.end('Missing path parameter');
+ return;
+ }
+
+ const content = documentRegistry.get(path);
+ if (content === undefined) {
+ res.writeHead(404);
+ res.end('Document not open in editor');
+ return;
+ }
+
+ res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
+ res.end(content);
+ }
+}
diff --git a/packages/vscode-extension/src/mcp/server-definition-provider.ts b/packages/vscode-extension/src/mcp/server-definition-provider.ts
new file mode 100644
index 00000000..0dfcff0a
--- /dev/null
+++ b/packages/vscode-extension/src/mcp/server-definition-provider.ts
@@ -0,0 +1,91 @@
+import { createHash } from 'node:crypto';
+import * as vscode from 'vscode';
+import { getHeadlessAppUrl, onAppUrlChange } from '#core/config';
+import { logInfo } from '#core/logger';
+import { getMcpInvocation, MCP_SERVER_ID } from '#mcp/mcp-invocation';
+import { version as EXTENSION_VERSION } from '../../package.json';
+
+const SERVER_LABEL = 'QuickMock Wireframe Tools';
+const VERSION_HASH_ALGO = 'sha1';
+const VERSION_HASH_LENGTH = 8;
+
+const buildDefinition = (
+ context: vscode.ExtensionContext,
+ workspaceFolder: vscode.WorkspaceFolder
+): vscode.McpStdioServerDefinition => {
+ const versionSuffix = createHash(VERSION_HASH_ALGO)
+ .update(getHeadlessAppUrl())
+ .digest('hex')
+ .slice(0, VERSION_HASH_LENGTH);
+
+ const { command, args } = getMcpInvocation(context);
+
+ return new vscode.McpStdioServerDefinition(
+ SERVER_LABEL,
+ command,
+ args,
+ { QM_WORKSPACE_ROOT: workspaceFolder.uri.fsPath },
+ `${EXTENSION_VERSION}+${versionSuffix}`
+ );
+};
+
+export const registerQuickMockMcpServerProvider = (
+ context: vscode.ExtensionContext
+): vscode.Disposable => {
+ const didChangeDefinitions = new vscode.EventEmitter();
+ logInfo('Registering MCP server definition provider');
+
+ let providerRegistration: vscode.Disposable | undefined;
+
+ const register = () => {
+ providerRegistration?.dispose();
+ providerRegistration = vscode.lm.registerMcpServerDefinitionProvider(
+ MCP_SERVER_ID,
+ {
+ onDidChangeMcpServerDefinitions: didChangeDefinitions.event,
+ provideMcpServerDefinitions: async _token => {
+ logInfo('Providing MCP server definitions');
+ const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
+ if (!workspaceFolder) {
+ logInfo('No workspace folder available for MCP server');
+ return [];
+ }
+ return [buildDefinition(context, workspaceFolder)];
+ },
+ resolveMcpServerDefinition: async (server, _token) => {
+ logInfo('Resolving MCP server definition');
+ const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
+ if (
+ !workspaceFolder ||
+ !(server instanceof vscode.McpStdioServerDefinition)
+ ) {
+ return server;
+ }
+ const fresh = buildDefinition(context, workspaceFolder);
+ server.command = fresh.command;
+ server.args = fresh.args;
+ server.env = fresh.env;
+ server.version = fresh.version;
+ return server;
+ },
+ }
+ );
+ };
+
+ register();
+
+ const subscriptions: vscode.Disposable[] = [
+ didChangeDefinitions,
+ vscode.workspace.onDidChangeWorkspaceFolders(() =>
+ didChangeDefinitions.fire()
+ ),
+ onAppUrlChange(() => {
+ logInfo('appUrl changed, re-registering MCP provider');
+ register();
+ didChangeDefinitions.fire();
+ }),
+ { dispose: () => providerRegistration?.dispose() },
+ ];
+
+ return vscode.Disposable.from(...subscriptions);
+};
diff --git a/packages/vscode-extension/src/webview/bridge.ts b/packages/vscode-extension/src/webview/bridge.ts
new file mode 100644
index 00000000..513d71f6
--- /dev/null
+++ b/packages/vscode-extension/src/webview/bridge.ts
@@ -0,0 +1,32 @@
+import {
+ type AppMessage,
+ HOST_MESSAGE_TYPE,
+ type HostMessage,
+} from '@lemoncode/quickmock-bridge-protocol';
+
+// Reference: https://code.visualstudio.com/api/extension-guides/webview#loading-local-content
+declare function acquireVsCodeApi(): { postMessage(msg: AppMessage): void };
+
+const vscode = acquireVsCodeApi();
+
+const FORWARDED_TO_IFRAME: ReadonlySet = new Set([
+ HOST_MESSAGE_TYPE.LOAD,
+ HOST_MESSAGE_TYPE.SAVED,
+ HOST_MESSAGE_TYPE.LOAD_FILE,
+]);
+
+export const setupBridge = (
+ iframe: HTMLIFrameElement,
+ appOrigin: string
+): void => {
+ window.addEventListener('message', (event: MessageEvent) => {
+ if (event.origin === appOrigin) {
+ vscode.postMessage(event.data as AppMessage);
+ } else {
+ const msg = event.data as HostMessage;
+ if (FORWARDED_TO_IFRAME.has(msg.type)) {
+ iframe.contentWindow?.postMessage(msg, appOrigin);
+ }
+ }
+ });
+};
diff --git a/packages/vscode-extension/src/webview/main.ts b/packages/vscode-extension/src/webview/main.ts
new file mode 100644
index 00000000..63e4057d
--- /dev/null
+++ b/packages/vscode-extension/src/webview/main.ts
@@ -0,0 +1,20 @@
+import { setupBridge } from './bridge';
+
+const appUrl = document.body.dataset.appUrl;
+if (!appUrl) {
+ throw new Error('[QuickMock] Missing data-app-url attribute on ');
+}
+
+const appOrigin = new URL(appUrl).origin;
+
+const iframe = document.createElement('iframe');
+iframe.src = appUrl;
+iframe.setAttribute(
+ 'sandbox',
+ 'allow-scripts allow-same-origin allow-downloads'
+);
+iframe.allow = 'clipboard-read; clipboard-write';
+iframe.title = 'QuickMock Application';
+document.body.appendChild(iframe);
+
+setupBridge(iframe, appOrigin);
diff --git a/packages/vscode-extension/tsconfig.json b/packages/vscode-extension/tsconfig.json
index 38115089..e9acf234 100644
--- a/packages/vscode-extension/tsconfig.json
+++ b/packages/vscode-extension/tsconfig.json
@@ -3,6 +3,7 @@
"include": ["src"],
"compilerOptions": {
"rootDir": "src",
+ "lib": ["ES2024", "DOM"],
"paths": {
"#*": ["./src/*"]
}
diff --git a/packages/vscode-extension/tsdown.config.ts b/packages/vscode-extension/tsdown.config.ts
index 6462135d..a2514af7 100644
--- a/packages/vscode-extension/tsdown.config.ts
+++ b/packages/vscode-extension/tsdown.config.ts
@@ -1,7 +1,24 @@
import { baseTsdownConfig } from '@lemoncode/tsdown-config/base';
+import { defineConfig } from 'tsdown';
-export default {
- ...baseTsdownConfig,
- entry: ['src/index.ts'],
- external: ['vscode'],
-};
+export default defineConfig([
+ {
+ ...baseTsdownConfig,
+ entry: ['src/index.ts'],
+ format: ['esm', 'cjs'],
+ dts: false,
+ deps: {
+ neverBundle: ['vscode'],
+ // alwaysBundle: ['@lemoncode/quickmock-mcp'],
+ },
+ },
+ {
+ ...baseTsdownConfig,
+ entry: { webview: 'src/webview/main.ts' },
+ format: 'iife',
+ platform: 'browser',
+ outputOptions: {
+ entryFileNames: '[name].js',
+ },
+ },
+]);
diff --git a/tooling/typescript/node.json b/tooling/typescript/node.json
index 14b2f8e8..9f21fc22 100644
--- a/tooling/typescript/node.json
+++ b/tooling/typescript/node.json
@@ -3,6 +3,7 @@
"compilerOptions": {
"target": "ES2024",
"lib": ["ES2024"],
+ "types": ["node"],
"noEmit": true
}
}