diff --git a/ls/editors/code/package-lock.json b/ls/editors/code/package-lock.json
index 213ae7d02..073223e33 100644
--- a/ls/editors/code/package-lock.json
+++ b/ls/editors/code/package-lock.json
@@ -2044,9 +2044,9 @@
}
},
"node_modules/flatted": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
- "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
+ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
"dev": true,
"license": "ISC"
},
diff --git a/playground/.gitignore b/playground/.gitignore
new file mode 100644
index 000000000..a547bf36d
--- /dev/null
+++ b/playground/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/playground/.nvmrc b/playground/.nvmrc
new file mode 100644
index 000000000..4555c44c2
--- /dev/null
+++ b/playground/.nvmrc
@@ -0,0 +1 @@
+22.22.1
\ No newline at end of file
diff --git a/playground/README.MD b/playground/README.MD
new file mode 100644
index 000000000..85bfb8356
--- /dev/null
+++ b/playground/README.MD
@@ -0,0 +1,64 @@
+# YARA-X Playground
+
+This folder contains the browser playground for YARA-X. The goal of the
+playground is to provide a lightweight web interface for writing, formatting,
+and eventually testing YARA rules directly in the browser.
+
+The current version is `0.0.0`. At this stage, the focus is on establishing the
+front-end foundation of the playground:
+
+- TypeScript for the application code
+- Lit for the UI
+- Vite for development and builds
+- Monaco-based editor integration for the rule and sample panes
+
+This version is intentionally limited. It is expected to compile and provide
+the basic UI structure, but it is not expected to be fully functional end to
+end yet.
+
+## Requirements
+
+- Node `22.22.1`
+- `pnpm`
+
+The Node version is pinned in [`.nvmrc`](./.nvmrc).
+
+## Getting started
+
+```bash
+nvm use
+pnpm install
+pnpm dev
+```
+
+This starts the Vite development server. Open the local URL printed in the
+terminal.
+
+## Scripts
+
+```bash
+pnpm dev
+pnpm build
+pnpm preview
+pnpm format
+```
+
+## Project status
+
+At the moment, this folder contains:
+
+- the application shell and layout
+- the editor setup
+- the styling and interaction base
+- the first browser-facing service contracts for the engine and the language
+ server
+
+What is still missing:
+
+- a working browser engine integration
+- a working browser language server integration
+- the final packaging strategy for browser artifacts
+- deployment workflows
+
+The intention is to connect those pieces once the browser packages are ready
+and their public interfaces are stable.
diff --git a/playground/index.html b/playground/index.html
new file mode 100644
index 000000000..597bd1a5f
--- /dev/null
+++ b/playground/index.html
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ YARA Playground
+
+
+
+
+
+
diff --git a/playground/package.json b/playground/package.json
new file mode 100644
index 000000000..1c3a79bf2
--- /dev/null
+++ b/playground/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "yara-x-playground",
+ "private": true,
+ "contributors": [
+ "kevinmuoz"
+ ],
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview",
+ "prod": "vite build && vite preview",
+ "format": "prettier --write ."
+ },
+ "dependencies": {
+ "@codingame/monaco-vscode-editor-api": "^25.1.2",
+ "lit": "^3.3.2",
+ "monaco-languageclient": "^10.7.0",
+ "vscode-languageclient": "^9.0.1",
+ "vscode-languageserver-protocol": "^3.17.5"
+ },
+ "devDependencies": {
+ "prettier": "^3.8.1",
+ "rollup-plugin-visualizer": "^7.0.1",
+ "typescript": "~5.9.3",
+ "vite": "^7.1.0"
+ }
+}
diff --git a/playground/pnpm-lock.yaml b/playground/pnpm-lock.yaml
new file mode 100644
index 000000000..0737c63b7
--- /dev/null
+++ b/playground/pnpm-lock.yaml
@@ -0,0 +1,2349 @@
+lockfileVersion: "9.0"
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+ .:
+ dependencies:
+ "@codingame/monaco-vscode-editor-api":
+ specifier: ^25.1.2
+ version: 25.1.2
+ lit:
+ specifier: ^3.3.2
+ version: 3.3.2
+ monaco-languageclient:
+ specifier: ^10.7.0
+ version: 10.7.0
+ vscode-languageclient:
+ specifier: ^9.0.1
+ version: 9.0.1
+ vscode-languageserver-protocol:
+ specifier: ^3.17.5
+ version: 3.17.5
+ devDependencies:
+ prettier:
+ specifier: ^3.8.1
+ version: 3.8.1
+ rollup-plugin-visualizer:
+ specifier: ^7.0.1
+ version: 7.0.1(rolldown@1.0.0-rc.10)(rollup@4.60.0)
+ typescript:
+ specifier: ~5.9.3
+ version: 5.9.3
+ vite:
+ specifier: ^7.1.0
+ version: 7.3.1(lightningcss@1.32.0)
+
+packages:
+ "@codingame/monaco-vscode-api@25.1.2":
+ resolution:
+ {
+ integrity: sha512-K04QcQA+Zb0KXucBAK/BGCT5dldiwIqdUbBQq7yuLvBLbof3cP1WSUuxasMHGYwM0MWyzIAsDtyAYMS7is8ZuA==,
+ }
+
+ "@codingame/monaco-vscode-base-service-override@25.1.2":
+ resolution:
+ {
+ integrity: sha512-OwYs6h1ATUAeMmX+Q1c8esTG7GLMqniBs+fLEr1/9b/ciY485ArKo5UvrUxVPDtRNy/7F06vRW9IUCq9iKP14w==,
+ }
+
+ "@codingame/monaco-vscode-bulk-edit-service-override@25.1.2":
+ resolution:
+ {
+ integrity: sha512-+EfSzjiFakCf0IIJKPZrHVGioq5N8GBsp51bXuKBR5J/B58cUaJY0Dc12PNTSpgAusAGOppUIOSBqUk4F/7IaQ==,
+ }
+
+ "@codingame/monaco-vscode-configuration-service-override@25.1.2":
+ resolution:
+ {
+ integrity: sha512-oeoZ3WtM42zHA1IWHrx9UGEfE+TixE+G8Bl9M9bjgFj1EROnkB5yOfELwRYPo4WOEtcK1C5nvIvWIj/hL9MaLg==,
+ }
+
+ "@codingame/monaco-vscode-editor-api@25.1.2":
+ resolution:
+ {
+ integrity: sha512-dVXoBLRN8vyFHsLY6iYISaNetZ3ispXLut0qL+jvN0e0CEFkUv1F/3EAE7myptrJSS/N1AptrRIxATT3lwFP+Q==,
+ }
+
+ "@codingame/monaco-vscode-editor-service-override@25.1.2":
+ resolution:
+ {
+ integrity: sha512-EadvDCyWdgxOPmaIvbcVVDNjTUYuKdjYWwKbPbbcTs9t4z1/DjdE7mV3ZdT6aGh5m6zkEEUOi143l27Y5eRt+Q==,
+ }
+
+ "@codingame/monaco-vscode-environment-service-override@25.1.2":
+ resolution:
+ {
+ integrity: sha512-8GoD3lk0CN0dIMZOrZNS/i8RCaF1YSQ6nmrf+rqneOSHG9S382EnsZZD69d4+i7JnoeyttO7Kr9KH8WOhRV6OA==,
+ }
+
+ "@codingame/monaco-vscode-extension-api@25.1.2":
+ resolution:
+ {
+ integrity: sha512-SJW/YOhjo+9MXEyzMwQMUWdJVR3Llc6pTq5JQqs6Y30v73gTrpLqtzbd9FNdCuQR8S6bUk5ScH8GL4QrVuL5FA==,
+ }
+
+ "@codingame/monaco-vscode-extensions-service-override@25.1.2":
+ resolution:
+ {
+ integrity: sha512-rTTZW2biPxcg+JumhVf2L+38C5ptvNNxiJlwz39VfXFEh6qOHtAsIMy7vIXa0uGg5/y8DNp0SnOQJP/RKhLYZA==,
+ }
+
+ "@codingame/monaco-vscode-files-service-override@25.1.2":
+ resolution:
+ {
+ integrity: sha512-TenLLAFIwY7keZFF8e3beUn7OVfnNINR5Noi4PVrjeeTcy6FuNH6Jghdul2JwpRAkvyJLdFMvomE2jlT6F03jQ==,
+ }
+
+ "@codingame/monaco-vscode-host-service-override@25.1.2":
+ resolution:
+ {
+ integrity: sha512-lgaalpA9CUQW7i0bBwgBOK0DQNDvOo3QO3p6Rz6yVsHpgA4iMqq2d11dBDUKvuQSwIHPRu8CMHCqhQk/BQN/YA==,
+ }
+
+ "@codingame/monaco-vscode-keybindings-service-override@25.1.2":
+ resolution:
+ {
+ integrity: sha512-cp/gGyTvCTAzCYnQm0HJykXJRB0Huz8Lvq60lj5LutgWcb8S3w6dOB2Houm8dHoeUm/jOko8SQNIP8hzWN92Zw==,
+ }
+
+ "@codingame/monaco-vscode-language-pack-cs@25.1.2":
+ resolution:
+ {
+ integrity: sha512-v0cB2uAOCwj135aGIf0arGV+DNW32lbWh04bv8ctTxcWRt1Pr2kTQ1pjfE8ynKgxabPfAk8E25/CerKSYOmZ+A==,
+ }
+
+ "@codingame/monaco-vscode-language-pack-de@25.1.2":
+ resolution:
+ {
+ integrity: sha512-xA3WOt1w5jlAOnyx4PBwx+qV3vx8C8/zie29qjYbgJMxGKDkb0HfpuKUwywDA2uUMI2wJZS+PnNG00zPDoLIrw==,
+ }
+
+ "@codingame/monaco-vscode-language-pack-es@25.1.2":
+ resolution:
+ {
+ integrity: sha512-1/upuO9lRJilZ3sRr0QLTpz55KYRaBWDe8wtPvghOFYOHyWgW8A4VhUQxa6L9SJgY1JkypUAm0U8WcMX2G4LnQ==,
+ }
+
+ "@codingame/monaco-vscode-language-pack-fr@25.1.2":
+ resolution:
+ {
+ integrity: sha512-iq+xx+tv1QIMmFD0eBhFRMF4xMAsVf/HyA1WogqBofteCWeAvRE9HUjZ5JzHz7jXBPe3dLP1LOM0r0GrJZs4fQ==,
+ }
+
+ "@codingame/monaco-vscode-language-pack-it@25.1.2":
+ resolution:
+ {
+ integrity: sha512-FajWCML9OR8ppLnJ0mcg+sFHEhYJl8zhb3/DHnd+pNysw8dLfetXoSWjaPnwPPpwiQgkNN1UsToZHOU9czVifQ==,
+ }
+
+ "@codingame/monaco-vscode-language-pack-ja@25.1.2":
+ resolution:
+ {
+ integrity: sha512-NwKh0BnPgUrJkxsm0X6vY4ftnd9DjxkcnQqK+bohta6UOzm09J1EjZ6QD42fjWngxrp/xiegtrYQ9NA2q6VpoA==,
+ }
+
+ "@codingame/monaco-vscode-language-pack-ko@25.1.2":
+ resolution:
+ {
+ integrity: sha512-fvaisgfcg8YaAwnyPcGmQDLwkwqzamLQUyx9HmnwDpXw0YANzd058Kwn6bz+Vfn9MjwuMNT0nllD0qQMnpdyew==,
+ }
+
+ "@codingame/monaco-vscode-language-pack-pl@25.1.2":
+ resolution:
+ {
+ integrity: sha512-9hDRyzFJkDia5rO9QE262JgxwP/cnalFisLFo7FQcw57ZhqzqXIdQIuwcKaHuAgzeQ6W2+A3KOLfTr3m7VZrXw==,
+ }
+
+ "@codingame/monaco-vscode-language-pack-pt-br@25.1.2":
+ resolution:
+ {
+ integrity: sha512-7fFnqOTAJGb5RuJ4uwh9sh0JmXALuHPGOl7iL9rZkcgIuVP5y6wVDUDXq5qjiRTNSFDs7Bzh463Ir5m5D6mJbA==,
+ }
+
+ "@codingame/monaco-vscode-language-pack-qps-ploc@25.1.2":
+ resolution:
+ {
+ integrity: sha512-IFjoqrSuPtIFWb+KlPT6PFWKszzNX+TCD9drgCV6AigvBO/xfGL3QwHB68l/DLbmDbohOz4Xdkutv20wuENAeA==,
+ }
+
+ "@codingame/monaco-vscode-language-pack-ru@25.1.2":
+ resolution:
+ {
+ integrity: sha512-0uDAeXO+GllKUPhJzP893rlDhlFV1IwCu/515rBdcyegt48iGm/xAgj26V90hNz8hmB6EuM/7d8MFeklbiIpYA==,
+ }
+
+ "@codingame/monaco-vscode-language-pack-tr@25.1.2":
+ resolution:
+ {
+ integrity: sha512-MJhHxDyJEiuVLQ9+jb8MnnN9lsbJOjJjMswVCeJ7v/Q/msAhq25QYUfn0DbOIzESJE1f7crffRb5e38XP8sYWA==,
+ }
+
+ "@codingame/monaco-vscode-language-pack-zh-hans@25.1.2":
+ resolution:
+ {
+ integrity: sha512-c7MMrhnSLb59NxpAa8nVy9aIbxy4gVYrCpDMq8W380LOaXTYb7nueTrw8QJ5QbJBNi2P2KZoGkn2BlONuBtJJg==,
+ }
+
+ "@codingame/monaco-vscode-language-pack-zh-hant@25.1.2":
+ resolution:
+ {
+ integrity: sha512-ARedFTM6JCluoPLJqkBcTJaQFdJNcN86OX6B8/NMApIPrnSIAfanMndpyilt8XjzUG6IH22cypR+DAlEjf48cA==,
+ }
+
+ "@codingame/monaco-vscode-languages-service-override@25.1.2":
+ resolution:
+ {
+ integrity: sha512-ipuS1V3NgXDkNrj0vBcgMBFnqo+19HVsZjjFGfPFH3x0uptP9aiWWK42wtDK3Qbu4teSjHL7WnSLrmw94rplWw==,
+ }
+
+ "@codingame/monaco-vscode-layout-service-override@25.1.2":
+ resolution:
+ {
+ integrity: sha512-SxBGcMK3RgkGtUn7ZDl7dCoyNW0CWFQ/bfSRYUY06A0IA4JNS5jq1lhof57d0WXewm+5l8w1Spr/vMsfx1c9ig==,
+ }
+
+ "@codingame/monaco-vscode-localization-service-override@25.1.2":
+ resolution:
+ {
+ integrity: sha512-QLj62A8XDOIQW3KjsZlNxs+sfsNNHYxWMjQMwZu/y2Vw3IIHGly2Lpn4t4SFbeaBHJQJy4i5s7NpzlbF9MbEzQ==,
+ }
+
+ "@codingame/monaco-vscode-log-service-override@25.1.2":
+ resolution:
+ {
+ integrity: sha512-OoileAUtPAJ0j3RW31DFSxtOipy0EcFq+iIXEdGvoRlsQPZJ3o9ayjf1JvCXpxUjJ3QkmvQVhXsWNUFREjEFLg==,
+ }
+
+ "@codingame/monaco-vscode-model-service-override@25.1.2":
+ resolution:
+ {
+ integrity: sha512-MGz/eV1CxibLvnl6WzK6idUHJCXJOVepJvKM6Trkv5050vRe+f/o1TjCiG8PaznAypYqZvnwkTG0B7/OTizCpQ==,
+ }
+
+ "@codingame/monaco-vscode-monarch-service-override@25.1.2":
+ resolution:
+ {
+ integrity: sha512-akyNHOJQRS7YHyk6kf0Encnkt+shlR+bIB84UJRUHFgSeF8s5gkDkQuFJph0YeUDWJWat+yBLUSZx2nHomdbHQ==,
+ }
+
+ "@codingame/monaco-vscode-quickaccess-service-override@25.1.2":
+ resolution:
+ {
+ integrity: sha512-7IIrXnwHiF3w9d9p9kspEUz/LCibMLUztmRpGdZQfFtWBJw043q7rk8V1O42KdXr1hVg9IR5vfffwjy9nbiiUg==,
+ }
+
+ "@codingame/monaco-vscode-textmate-service-override@25.1.2":
+ resolution:
+ {
+ integrity: sha512-AL0FtSQBW+1vtoXYQvUqB2hfWojpK73Kq/n6KuNXxjLF/XBJ5FpeeZDfrBfwhWPPoHuBTsaFUCQy4L8xQgbVlA==,
+ }
+
+ "@codingame/monaco-vscode-theme-defaults-default-extension@25.1.2":
+ resolution:
+ {
+ integrity: sha512-0vTMFiC89YSDSmjFckuQBUKwRuFNtsILNO3k0PBiSLN/MW+VDItjJpiVLXC42+rUWlGgY2lYxOneGVa5slCV1w==,
+ }
+
+ "@codingame/monaco-vscode-theme-service-override@25.1.2":
+ resolution:
+ {
+ integrity: sha512-hsTwl6YYTiheFuQMmCmiEGLIdIdgYaf8Z85XWyxe6YgPtDaYGnp0fGSOXKA9/bf0JtuynzoLKtUUfDupK/A7Tw==,
+ }
+
+ "@codingame/monaco-vscode-view-banner-service-override@25.1.2":
+ resolution:
+ {
+ integrity: sha512-zhujHd1PQ6rRXsC2OQGrx/282G2v3lpPFl9heDFGKzpdj5119SgcW+B9p/MwJ1qF3LJpuRRgefNiQtqC/KT1eA==,
+ }
+
+ "@codingame/monaco-vscode-view-common-service-override@25.1.2":
+ resolution:
+ {
+ integrity: sha512-4Po/YaHUvVf4VmhVCZmM2lc/flOptiWSM140bIRNpMcfH0VwihYg15CcDeu1Oc+6DaauzsG3u59GtEvlMmJ9Zw==,
+ }
+
+ "@codingame/monaco-vscode-view-status-bar-service-override@25.1.2":
+ resolution:
+ {
+ integrity: sha512-Jp9ytLaWZ6evabTPtG3Mu3dFx+7WTIPz69BsGpl9PnU0kiSWUqQhPSob0Jz7E2qmMj0ZcNv2Wqvm6bMBu5OyrA==,
+ }
+
+ "@codingame/monaco-vscode-view-title-bar-service-override@25.1.2":
+ resolution:
+ {
+ integrity: sha512-NVYtTAFR35NV/Fx7tSlbASicvpAjK5A14fmxF7/LJJN8ZmzhA/P3Y+UzhqOQl6/VcPV4pAMU0Z7Sicgwbn37dw==,
+ }
+
+ "@codingame/monaco-vscode-views-service-override@25.1.2":
+ resolution:
+ {
+ integrity: sha512-LfzlztsvobdP5L5EvJ/rqSEgy5fEVmrkMqRteuhEtNGd4hnmdBoX8W7BNMBPff6d4NfCK74pGHJF57RyT4Iixg==,
+ }
+
+ "@codingame/monaco-vscode-workbench-service-override@25.1.2":
+ resolution:
+ {
+ integrity: sha512-2LMHr+na03FhOAaXpIGmamq9hf7e4wt2kULn8NqNZRd3i+0v1tx/TSSjGhsA5EkrNrFD7CMSoXayBq8tgpCq/A==,
+ }
+
+ "@emnapi/core@1.9.1":
+ resolution:
+ {
+ integrity: sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==,
+ }
+
+ "@emnapi/runtime@1.9.1":
+ resolution:
+ {
+ integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==,
+ }
+
+ "@emnapi/wasi-threads@1.2.0":
+ resolution:
+ {
+ integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==,
+ }
+
+ "@esbuild/aix-ppc64@0.27.4":
+ resolution:
+ {
+ integrity: sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==,
+ }
+ engines: { node: ">=18" }
+ cpu: [ppc64]
+ os: [aix]
+
+ "@esbuild/android-arm64@0.27.4":
+ resolution:
+ {
+ integrity: sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==,
+ }
+ engines: { node: ">=18" }
+ cpu: [arm64]
+ os: [android]
+
+ "@esbuild/android-arm@0.27.4":
+ resolution:
+ {
+ integrity: sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==,
+ }
+ engines: { node: ">=18" }
+ cpu: [arm]
+ os: [android]
+
+ "@esbuild/android-x64@0.27.4":
+ resolution:
+ {
+ integrity: sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==,
+ }
+ engines: { node: ">=18" }
+ cpu: [x64]
+ os: [android]
+
+ "@esbuild/darwin-arm64@0.27.4":
+ resolution:
+ {
+ integrity: sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==,
+ }
+ engines: { node: ">=18" }
+ cpu: [arm64]
+ os: [darwin]
+
+ "@esbuild/darwin-x64@0.27.4":
+ resolution:
+ {
+ integrity: sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==,
+ }
+ engines: { node: ">=18" }
+ cpu: [x64]
+ os: [darwin]
+
+ "@esbuild/freebsd-arm64@0.27.4":
+ resolution:
+ {
+ integrity: sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==,
+ }
+ engines: { node: ">=18" }
+ cpu: [arm64]
+ os: [freebsd]
+
+ "@esbuild/freebsd-x64@0.27.4":
+ resolution:
+ {
+ integrity: sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==,
+ }
+ engines: { node: ">=18" }
+ cpu: [x64]
+ os: [freebsd]
+
+ "@esbuild/linux-arm64@0.27.4":
+ resolution:
+ {
+ integrity: sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==,
+ }
+ engines: { node: ">=18" }
+ cpu: [arm64]
+ os: [linux]
+
+ "@esbuild/linux-arm@0.27.4":
+ resolution:
+ {
+ integrity: sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==,
+ }
+ engines: { node: ">=18" }
+ cpu: [arm]
+ os: [linux]
+
+ "@esbuild/linux-ia32@0.27.4":
+ resolution:
+ {
+ integrity: sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==,
+ }
+ engines: { node: ">=18" }
+ cpu: [ia32]
+ os: [linux]
+
+ "@esbuild/linux-loong64@0.27.4":
+ resolution:
+ {
+ integrity: sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==,
+ }
+ engines: { node: ">=18" }
+ cpu: [loong64]
+ os: [linux]
+
+ "@esbuild/linux-mips64el@0.27.4":
+ resolution:
+ {
+ integrity: sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==,
+ }
+ engines: { node: ">=18" }
+ cpu: [mips64el]
+ os: [linux]
+
+ "@esbuild/linux-ppc64@0.27.4":
+ resolution:
+ {
+ integrity: sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==,
+ }
+ engines: { node: ">=18" }
+ cpu: [ppc64]
+ os: [linux]
+
+ "@esbuild/linux-riscv64@0.27.4":
+ resolution:
+ {
+ integrity: sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==,
+ }
+ engines: { node: ">=18" }
+ cpu: [riscv64]
+ os: [linux]
+
+ "@esbuild/linux-s390x@0.27.4":
+ resolution:
+ {
+ integrity: sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==,
+ }
+ engines: { node: ">=18" }
+ cpu: [s390x]
+ os: [linux]
+
+ "@esbuild/linux-x64@0.27.4":
+ resolution:
+ {
+ integrity: sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==,
+ }
+ engines: { node: ">=18" }
+ cpu: [x64]
+ os: [linux]
+
+ "@esbuild/netbsd-arm64@0.27.4":
+ resolution:
+ {
+ integrity: sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==,
+ }
+ engines: { node: ">=18" }
+ cpu: [arm64]
+ os: [netbsd]
+
+ "@esbuild/netbsd-x64@0.27.4":
+ resolution:
+ {
+ integrity: sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==,
+ }
+ engines: { node: ">=18" }
+ cpu: [x64]
+ os: [netbsd]
+
+ "@esbuild/openbsd-arm64@0.27.4":
+ resolution:
+ {
+ integrity: sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==,
+ }
+ engines: { node: ">=18" }
+ cpu: [arm64]
+ os: [openbsd]
+
+ "@esbuild/openbsd-x64@0.27.4":
+ resolution:
+ {
+ integrity: sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==,
+ }
+ engines: { node: ">=18" }
+ cpu: [x64]
+ os: [openbsd]
+
+ "@esbuild/openharmony-arm64@0.27.4":
+ resolution:
+ {
+ integrity: sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==,
+ }
+ engines: { node: ">=18" }
+ cpu: [arm64]
+ os: [openharmony]
+
+ "@esbuild/sunos-x64@0.27.4":
+ resolution:
+ {
+ integrity: sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==,
+ }
+ engines: { node: ">=18" }
+ cpu: [x64]
+ os: [sunos]
+
+ "@esbuild/win32-arm64@0.27.4":
+ resolution:
+ {
+ integrity: sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==,
+ }
+ engines: { node: ">=18" }
+ cpu: [arm64]
+ os: [win32]
+
+ "@esbuild/win32-ia32@0.27.4":
+ resolution:
+ {
+ integrity: sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==,
+ }
+ engines: { node: ">=18" }
+ cpu: [ia32]
+ os: [win32]
+
+ "@esbuild/win32-x64@0.27.4":
+ resolution:
+ {
+ integrity: sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==,
+ }
+ engines: { node: ">=18" }
+ cpu: [x64]
+ os: [win32]
+
+ "@lit-labs/ssr-dom-shim@1.5.1":
+ resolution:
+ {
+ integrity: sha512-Aou5UdlSpr5whQe8AA/bZG0jMj96CoJIWbGfZ91qieWu5AWUMKw8VR/pAkQkJYvBNhmCcWnZlyyk5oze8JIqYA==,
+ }
+
+ "@lit/reactive-element@2.1.2":
+ resolution:
+ {
+ integrity: sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==,
+ }
+
+ "@napi-rs/wasm-runtime@1.1.1":
+ resolution:
+ {
+ integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==,
+ }
+
+ "@oxc-project/types@0.120.0":
+ resolution:
+ {
+ integrity: sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg==,
+ }
+
+ "@rolldown/binding-android-arm64@1.0.0-rc.10":
+ resolution:
+ {
+ integrity: sha512-jOHxwXhxmFKuXztiu1ORieJeTbx5vrTkcOkkkn2d35726+iwhrY1w/+nYY/AGgF12thg33qC3R1LMBF5tHTZHg==,
+ }
+ engines: { node: ^20.19.0 || >=22.12.0 }
+ cpu: [arm64]
+ os: [android]
+
+ "@rolldown/binding-darwin-arm64@1.0.0-rc.10":
+ resolution:
+ {
+ integrity: sha512-gED05Teg/vtTZbIJBc4VNMAxAFDUPkuO/rAIyyxZjTj1a1/s6z5TII/5yMGZ0uLRCifEtwUQn8OlYzuYc0m70w==,
+ }
+ engines: { node: ^20.19.0 || >=22.12.0 }
+ cpu: [arm64]
+ os: [darwin]
+
+ "@rolldown/binding-darwin-x64@1.0.0-rc.10":
+ resolution:
+ {
+ integrity: sha512-rI15NcM1mA48lqrIxVkHfAqcyFLcQwyXWThy+BQ5+mkKKPvSO26ir+ZDp36AgYoYVkqvMcdS8zOE6SeBsR9e8A==,
+ }
+ engines: { node: ^20.19.0 || >=22.12.0 }
+ cpu: [x64]
+ os: [darwin]
+
+ "@rolldown/binding-freebsd-x64@1.0.0-rc.10":
+ resolution:
+ {
+ integrity: sha512-XZRXHdTa+4ME1MuDVp021+doQ+z6Ei4CCFmNc5/sKbqb8YmkiJdj8QKlV3rCI0AJtAeSB5n0WGPuJWNL9p/L2w==,
+ }
+ engines: { node: ^20.19.0 || >=22.12.0 }
+ cpu: [x64]
+ os: [freebsd]
+
+ "@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.10":
+ resolution:
+ {
+ integrity: sha512-R0SQMRluISSLzFE20sPWYHVmJdDQnRyc/FzSCN72BqQmh2SOZUFG+N3/vBZpR4C6WpEUVYJLrYUXaj43sJsNLA==,
+ }
+ engines: { node: ^20.19.0 || >=22.12.0 }
+ cpu: [arm]
+ os: [linux]
+
+ "@rolldown/binding-linux-arm64-gnu@1.0.0-rc.10":
+ resolution:
+ {
+ integrity: sha512-Y1reMrV/o+cwpduYhJuOE3OMKx32RMYCidf14y+HssARRmhDuWXJ4yVguDg2R/8SyyGNo+auzz64LnPK9Hq6jg==,
+ }
+ engines: { node: ^20.19.0 || >=22.12.0 }
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ "@rolldown/binding-linux-arm64-musl@1.0.0-rc.10":
+ resolution:
+ {
+ integrity: sha512-vELN+HNb2IzuzSBUOD4NHmP9yrGwl1DVM29wlQvx1OLSclL0NgVWnVDKl/8tEks79EFek/kebQKnNJkIAA4W2g==,
+ }
+ engines: { node: ^20.19.0 || >=22.12.0 }
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ "@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.10":
+ resolution:
+ {
+ integrity: sha512-ZqrufYTgzxbHwpqOjzSsb0UV/aV2TFIY5rP8HdsiPTv/CuAgCRjM6s9cYFwQ4CNH+hf9Y4erHW1GjZuZ7WoI7w==,
+ }
+ engines: { node: ^20.19.0 || >=22.12.0 }
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
+ "@rolldown/binding-linux-s390x-gnu@1.0.0-rc.10":
+ resolution:
+ {
+ integrity: sha512-gSlmVS1FZJSRicA6IyjoRoKAFK7IIHBs7xJuHRSmjImqk3mPPWbR7RhbnfH2G6bcmMEllCt2vQ/7u9e6bBnByg==,
+ }
+ engines: { node: ^20.19.0 || >=22.12.0 }
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
+ "@rolldown/binding-linux-x64-gnu@1.0.0-rc.10":
+ resolution:
+ {
+ integrity: sha512-eOCKUpluKgfObT2pHjztnaWEIbUabWzk3qPZ5PuacuPmr4+JtQG4k2vGTY0H15edaTnicgU428XW/IH6AimcQw==,
+ }
+ engines: { node: ^20.19.0 || >=22.12.0 }
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ "@rolldown/binding-linux-x64-musl@1.0.0-rc.10":
+ resolution:
+ {
+ integrity: sha512-Xdf2jQbfQowJnLcgYfD/m0Uu0Qj5OdxKallD78/IPPfzaiaI4KRAwZzHcKQ4ig1gtg1SuzC7jovNiM2TzQsBXA==,
+ }
+ engines: { node: ^20.19.0 || >=22.12.0 }
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ "@rolldown/binding-openharmony-arm64@1.0.0-rc.10":
+ resolution:
+ {
+ integrity: sha512-o1hYe8hLi1EY6jgPFyxQgQ1wcycX+qz8eEbVmot2hFkgUzPxy9+kF0u0NIQBeDq+Mko47AkaFFaChcvZa9UX9Q==,
+ }
+ engines: { node: ^20.19.0 || >=22.12.0 }
+ cpu: [arm64]
+ os: [openharmony]
+
+ "@rolldown/binding-wasm32-wasi@1.0.0-rc.10":
+ resolution:
+ {
+ integrity: sha512-Ugv9o7qYJudqQO5Y5y2N2SOo6S4WiqiNOpuQyoPInnhVzCY+wi/GHltcLHypG9DEUYMB0iTB/huJrpadiAcNcA==,
+ }
+ engines: { node: ">=14.0.0" }
+ cpu: [wasm32]
+
+ "@rolldown/binding-win32-arm64-msvc@1.0.0-rc.10":
+ resolution:
+ {
+ integrity: sha512-7UODQb4fQUNT/vmgDZBl3XOBAIOutP5R3O/rkxg0aLfEGQ4opbCgU5vOw/scPe4xOqBwL9fw7/RP1vAMZ6QlAQ==,
+ }
+ engines: { node: ^20.19.0 || >=22.12.0 }
+ cpu: [arm64]
+ os: [win32]
+
+ "@rolldown/binding-win32-x64-msvc@1.0.0-rc.10":
+ resolution:
+ {
+ integrity: sha512-PYxKHMVHOb5NJuDL53vBUl1VwUjymDcYI6rzpIni0C9+9mTiJedvUxSk7/RPp7OOAm3v+EjgMu9bIy3N6b408w==,
+ }
+ engines: { node: ^20.19.0 || >=22.12.0 }
+ cpu: [x64]
+ os: [win32]
+
+ "@rolldown/pluginutils@1.0.0-rc.10":
+ resolution:
+ {
+ integrity: sha512-UkVDEFk1w3mveXeKgaTuYfKWtPbvgck1dT8TUG3bnccrH0XtLTuAyfCoks4Q/M5ZGToSVJTIQYCzy2g/atAOeg==,
+ }
+
+ "@rollup/rollup-android-arm-eabi@4.60.0":
+ resolution:
+ {
+ integrity: sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==,
+ }
+ cpu: [arm]
+ os: [android]
+
+ "@rollup/rollup-android-arm64@4.60.0":
+ resolution:
+ {
+ integrity: sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==,
+ }
+ cpu: [arm64]
+ os: [android]
+
+ "@rollup/rollup-darwin-arm64@4.60.0":
+ resolution:
+ {
+ integrity: sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==,
+ }
+ cpu: [arm64]
+ os: [darwin]
+
+ "@rollup/rollup-darwin-x64@4.60.0":
+ resolution:
+ {
+ integrity: sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==,
+ }
+ cpu: [x64]
+ os: [darwin]
+
+ "@rollup/rollup-freebsd-arm64@4.60.0":
+ resolution:
+ {
+ integrity: sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==,
+ }
+ cpu: [arm64]
+ os: [freebsd]
+
+ "@rollup/rollup-freebsd-x64@4.60.0":
+ resolution:
+ {
+ integrity: sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==,
+ }
+ cpu: [x64]
+ os: [freebsd]
+
+ "@rollup/rollup-linux-arm-gnueabihf@4.60.0":
+ resolution:
+ {
+ integrity: sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==,
+ }
+ cpu: [arm]
+ os: [linux]
+ libc: [glibc]
+
+ "@rollup/rollup-linux-arm-musleabihf@4.60.0":
+ resolution:
+ {
+ integrity: sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==,
+ }
+ cpu: [arm]
+ os: [linux]
+ libc: [musl]
+
+ "@rollup/rollup-linux-arm64-gnu@4.60.0":
+ resolution:
+ {
+ integrity: sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==,
+ }
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ "@rollup/rollup-linux-arm64-musl@4.60.0":
+ resolution:
+ {
+ integrity: sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==,
+ }
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ "@rollup/rollup-linux-loong64-gnu@4.60.0":
+ resolution:
+ {
+ integrity: sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==,
+ }
+ cpu: [loong64]
+ os: [linux]
+ libc: [glibc]
+
+ "@rollup/rollup-linux-loong64-musl@4.60.0":
+ resolution:
+ {
+ integrity: sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==,
+ }
+ cpu: [loong64]
+ os: [linux]
+ libc: [musl]
+
+ "@rollup/rollup-linux-ppc64-gnu@4.60.0":
+ resolution:
+ {
+ integrity: sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==,
+ }
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
+ "@rollup/rollup-linux-ppc64-musl@4.60.0":
+ resolution:
+ {
+ integrity: sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==,
+ }
+ cpu: [ppc64]
+ os: [linux]
+ libc: [musl]
+
+ "@rollup/rollup-linux-riscv64-gnu@4.60.0":
+ resolution:
+ {
+ integrity: sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==,
+ }
+ cpu: [riscv64]
+ os: [linux]
+ libc: [glibc]
+
+ "@rollup/rollup-linux-riscv64-musl@4.60.0":
+ resolution:
+ {
+ integrity: sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==,
+ }
+ cpu: [riscv64]
+ os: [linux]
+ libc: [musl]
+
+ "@rollup/rollup-linux-s390x-gnu@4.60.0":
+ resolution:
+ {
+ integrity: sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==,
+ }
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
+ "@rollup/rollup-linux-x64-gnu@4.60.0":
+ resolution:
+ {
+ integrity: sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==,
+ }
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ "@rollup/rollup-linux-x64-musl@4.60.0":
+ resolution:
+ {
+ integrity: sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==,
+ }
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ "@rollup/rollup-openbsd-x64@4.60.0":
+ resolution:
+ {
+ integrity: sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==,
+ }
+ cpu: [x64]
+ os: [openbsd]
+
+ "@rollup/rollup-openharmony-arm64@4.60.0":
+ resolution:
+ {
+ integrity: sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==,
+ }
+ cpu: [arm64]
+ os: [openharmony]
+
+ "@rollup/rollup-win32-arm64-msvc@4.60.0":
+ resolution:
+ {
+ integrity: sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==,
+ }
+ cpu: [arm64]
+ os: [win32]
+
+ "@rollup/rollup-win32-ia32-msvc@4.60.0":
+ resolution:
+ {
+ integrity: sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==,
+ }
+ cpu: [ia32]
+ os: [win32]
+
+ "@rollup/rollup-win32-x64-gnu@4.60.0":
+ resolution:
+ {
+ integrity: sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==,
+ }
+ cpu: [x64]
+ os: [win32]
+
+ "@rollup/rollup-win32-x64-msvc@4.60.0":
+ resolution:
+ {
+ integrity: sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==,
+ }
+ cpu: [x64]
+ os: [win32]
+
+ "@tybys/wasm-util@0.10.1":
+ resolution:
+ {
+ integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==,
+ }
+
+ "@types/estree@1.0.8":
+ resolution:
+ {
+ integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==,
+ }
+
+ "@types/trusted-types@2.0.7":
+ resolution:
+ {
+ integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==,
+ }
+
+ "@vscode/iconv-lite-umd@0.7.1":
+ resolution:
+ {
+ integrity: sha512-tK6k0DXFHW7q5+GGuGZO+phpAqpxO4WXl+BLc/8/uOk3RsM2ssAL3CQUQDb1TGfwltjsauhN6S4ghYZzs4sPFw==,
+ }
+
+ ansi-regex@6.2.2:
+ resolution:
+ {
+ integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==,
+ }
+ engines: { node: ">=12" }
+
+ ansi-styles@6.2.3:
+ resolution:
+ {
+ integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==,
+ }
+ engines: { node: ">=12" }
+
+ balanced-match@1.0.2:
+ resolution:
+ {
+ integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==,
+ }
+
+ brace-expansion@2.0.2:
+ resolution:
+ {
+ integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==,
+ }
+
+ bundle-name@4.1.0:
+ resolution:
+ {
+ integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==,
+ }
+ engines: { node: ">=18" }
+
+ cliui@9.0.1:
+ resolution:
+ {
+ integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==,
+ }
+ engines: { node: ">=20" }
+
+ default-browser-id@5.0.1:
+ resolution:
+ {
+ integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==,
+ }
+ engines: { node: ">=18" }
+
+ default-browser@5.5.0:
+ resolution:
+ {
+ integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==,
+ }
+ engines: { node: ">=18" }
+
+ define-lazy-prop@3.0.0:
+ resolution:
+ {
+ integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==,
+ }
+ engines: { node: ">=12" }
+
+ detect-libc@2.1.2:
+ resolution:
+ {
+ integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==,
+ }
+ engines: { node: ">=8" }
+
+ dompurify@3.3.1:
+ resolution:
+ {
+ integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==,
+ }
+
+ emoji-regex@10.6.0:
+ resolution:
+ {
+ integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==,
+ }
+
+ esbuild@0.27.4:
+ resolution:
+ {
+ integrity: sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==,
+ }
+ engines: { node: ">=18" }
+ hasBin: true
+
+ escalade@3.2.0:
+ resolution:
+ {
+ integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==,
+ }
+ engines: { node: ">=6" }
+
+ fdir@6.5.0:
+ resolution:
+ {
+ integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==,
+ }
+ engines: { node: ">=12.0.0" }
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ fsevents@2.3.3:
+ resolution:
+ {
+ integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==,
+ }
+ engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 }
+ os: [darwin]
+
+ get-caller-file@2.0.5:
+ resolution:
+ {
+ integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==,
+ }
+ engines: { node: 6.* || 8.* || >= 10.* }
+
+ get-east-asian-width@1.5.0:
+ resolution:
+ {
+ integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==,
+ }
+ engines: { node: ">=18" }
+
+ is-docker@3.0.0:
+ resolution:
+ {
+ integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==,
+ }
+ engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 }
+ hasBin: true
+
+ is-in-ssh@1.0.0:
+ resolution:
+ {
+ integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==,
+ }
+ engines: { node: ">=20" }
+
+ is-inside-container@1.0.0:
+ resolution:
+ {
+ integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==,
+ }
+ engines: { node: ">=14.16" }
+ hasBin: true
+
+ is-wsl@3.1.1:
+ resolution:
+ {
+ integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==,
+ }
+ engines: { node: ">=16" }
+
+ jschardet@3.1.4:
+ resolution:
+ {
+ integrity: sha512-/kmVISmrwVwtyYU40iQUOp3SUPk2dhNCMsZBQX0R1/jZ8maaXJ/oZIzUOiyOqcgtLnETFKYChbJ5iDC/eWmFHg==,
+ }
+ engines: { node: ">=0.1.90" }
+
+ lightningcss-android-arm64@1.32.0:
+ resolution:
+ {
+ integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==,
+ }
+ engines: { node: ">= 12.0.0" }
+ cpu: [arm64]
+ os: [android]
+
+ lightningcss-darwin-arm64@1.32.0:
+ resolution:
+ {
+ integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==,
+ }
+ engines: { node: ">= 12.0.0" }
+ cpu: [arm64]
+ os: [darwin]
+
+ lightningcss-darwin-x64@1.32.0:
+ resolution:
+ {
+ integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==,
+ }
+ engines: { node: ">= 12.0.0" }
+ cpu: [x64]
+ os: [darwin]
+
+ lightningcss-freebsd-x64@1.32.0:
+ resolution:
+ {
+ integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==,
+ }
+ engines: { node: ">= 12.0.0" }
+ cpu: [x64]
+ os: [freebsd]
+
+ lightningcss-linux-arm-gnueabihf@1.32.0:
+ resolution:
+ {
+ integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==,
+ }
+ engines: { node: ">= 12.0.0" }
+ cpu: [arm]
+ os: [linux]
+
+ lightningcss-linux-arm64-gnu@1.32.0:
+ resolution:
+ {
+ integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==,
+ }
+ engines: { node: ">= 12.0.0" }
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ lightningcss-linux-arm64-musl@1.32.0:
+ resolution:
+ {
+ integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==,
+ }
+ engines: { node: ">= 12.0.0" }
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ lightningcss-linux-x64-gnu@1.32.0:
+ resolution:
+ {
+ integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==,
+ }
+ engines: { node: ">= 12.0.0" }
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ lightningcss-linux-x64-musl@1.32.0:
+ resolution:
+ {
+ integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==,
+ }
+ engines: { node: ">= 12.0.0" }
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ lightningcss-win32-arm64-msvc@1.32.0:
+ resolution:
+ {
+ integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==,
+ }
+ engines: { node: ">= 12.0.0" }
+ cpu: [arm64]
+ os: [win32]
+
+ lightningcss-win32-x64-msvc@1.32.0:
+ resolution:
+ {
+ integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==,
+ }
+ engines: { node: ">= 12.0.0" }
+ cpu: [x64]
+ os: [win32]
+
+ lightningcss@1.32.0:
+ resolution:
+ {
+ integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==,
+ }
+ engines: { node: ">= 12.0.0" }
+
+ lit-element@4.2.2:
+ resolution:
+ {
+ integrity: sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==,
+ }
+
+ lit-html@3.3.2:
+ resolution:
+ {
+ integrity: sha512-Qy9hU88zcmaxBXcc10ZpdK7cOLXvXpRoBxERdtqV9QOrfpMZZ6pSYP91LhpPtap3sFMUiL7Tw2RImbe0Al2/kw==,
+ }
+
+ lit@3.3.2:
+ resolution:
+ {
+ integrity: sha512-NF9zbsP79l4ao2SNrH3NkfmFgN/hBYSQo90saIVI1o5GpjAdCPVstVzO1MrLOakHoEhYkrtRjPK6Ob521aoYWQ==,
+ }
+
+ marked@14.0.0:
+ resolution:
+ {
+ integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==,
+ }
+ engines: { node: ">= 18" }
+ hasBin: true
+
+ minimatch@5.1.9:
+ resolution:
+ {
+ integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==,
+ }
+ engines: { node: ">=10" }
+
+ monaco-languageclient@10.7.0:
+ resolution:
+ {
+ integrity: sha512-oA5cOFixkF4bspVL2zMSn48LvlNR/Cu3vJ8MCVam3PdjobSULGgHtOASuZIi3FgWK42X1z8/6hrG0LCjvNu1Hw==,
+ }
+ engines: { node: ">=20.10.0", npm: ">=10.2.3" }
+
+ nanoid@3.3.11:
+ resolution:
+ {
+ integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==,
+ }
+ engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 }
+ hasBin: true
+
+ open@11.0.0:
+ resolution:
+ {
+ integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==,
+ }
+ engines: { node: ">=20" }
+
+ picocolors@1.1.1:
+ resolution:
+ {
+ integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==,
+ }
+
+ picomatch@4.0.3:
+ resolution:
+ {
+ integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==,
+ }
+ engines: { node: ">=12" }
+
+ postcss@8.5.8:
+ resolution:
+ {
+ integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==,
+ }
+ engines: { node: ^10 || ^12 || >=14 }
+
+ powershell-utils@0.1.0:
+ resolution:
+ {
+ integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==,
+ }
+ engines: { node: ">=20" }
+
+ prettier@3.8.1:
+ resolution:
+ {
+ integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==,
+ }
+ engines: { node: ">=14" }
+ hasBin: true
+
+ rolldown@1.0.0-rc.10:
+ resolution:
+ {
+ integrity: sha512-q7j6vvarRFmKpgJUT8HCAUljkgzEp4LAhPlJUvQhA5LA1SUL36s5QCysMutErzL3EbNOZOkoziSx9iZC4FddKA==,
+ }
+ engines: { node: ^20.19.0 || >=22.12.0 }
+ hasBin: true
+
+ rollup-plugin-visualizer@7.0.1:
+ resolution:
+ {
+ integrity: sha512-UJUT4+1Ho4OcWmPYU3sYXgUqI8B8Ayfe06MX7y0qCJ1K8aGoKtR/NDd/2nZqM7ADkrzny+I99Ul7GgyoiVNAgg==,
+ }
+ engines: { node: ">=22" }
+ hasBin: true
+ peerDependencies:
+ rolldown: 1.x || ^1.0.0-beta || ^1.0.0-rc
+ rollup: 2.x || 3.x || 4.x
+ peerDependenciesMeta:
+ rolldown:
+ optional: true
+ rollup:
+ optional: true
+
+ rollup@4.60.0:
+ resolution:
+ {
+ integrity: sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==,
+ }
+ engines: { node: ">=18.0.0", npm: ">=8.0.0" }
+ hasBin: true
+
+ run-applescript@7.1.0:
+ resolution:
+ {
+ integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==,
+ }
+ engines: { node: ">=18" }
+
+ semver@7.7.4:
+ resolution:
+ {
+ integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==,
+ }
+ engines: { node: ">=10" }
+ hasBin: true
+
+ source-map-js@1.2.1:
+ resolution:
+ {
+ integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==,
+ }
+ engines: { node: ">=0.10.0" }
+
+ source-map@0.7.6:
+ resolution:
+ {
+ integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==,
+ }
+ engines: { node: ">= 12" }
+
+ string-width@7.2.0:
+ resolution:
+ {
+ integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==,
+ }
+ engines: { node: ">=18" }
+
+ strip-ansi@7.2.0:
+ resolution:
+ {
+ integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==,
+ }
+ engines: { node: ">=12" }
+
+ tinyglobby@0.2.15:
+ resolution:
+ {
+ integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==,
+ }
+ engines: { node: ">=12.0.0" }
+
+ tslib@2.8.1:
+ resolution:
+ {
+ integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==,
+ }
+
+ typescript@5.9.3:
+ resolution:
+ {
+ integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==,
+ }
+ engines: { node: ">=14.17" }
+ hasBin: true
+
+ vite@7.3.1:
+ resolution:
+ {
+ integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==,
+ }
+ engines: { node: ^20.19.0 || >=22.12.0 }
+ hasBin: true
+ peerDependencies:
+ "@types/node": ^20.19.0 || >=22.12.0
+ jiti: ">=1.21.0"
+ less: ^4.0.0
+ lightningcss: ^1.21.0
+ sass: ^1.70.0
+ sass-embedded: ^1.70.0
+ stylus: ">=0.54.8"
+ sugarss: ^5.0.0
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ "@types/node":
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
+ vscode-jsonrpc@8.2.0:
+ resolution:
+ {
+ integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==,
+ }
+ engines: { node: ">=14.0.0" }
+
+ vscode-jsonrpc@8.2.1:
+ resolution:
+ {
+ integrity: sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==,
+ }
+ engines: { node: ">=14.0.0" }
+
+ vscode-languageclient@9.0.1:
+ resolution:
+ {
+ integrity: sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA==,
+ }
+ engines: { vscode: ^1.82.0 }
+
+ vscode-languageserver-protocol@3.17.5:
+ resolution:
+ {
+ integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==,
+ }
+
+ vscode-languageserver-types@3.17.5:
+ resolution:
+ {
+ integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==,
+ }
+
+ vscode-ws-jsonrpc@3.5.0:
+ resolution:
+ {
+ integrity: sha512-13ZDy7Od4AfEPK2HIfY3DtyRi4FVsvFql1yobVJrpIoHOKGGJpIjVvIJpMxkrHzCZzWlYlg+WEu2hrYkCTvM0Q==,
+ }
+ engines: { node: ">=20.10.0", npm: ">=10.2.3" }
+
+ wrap-ansi@9.0.2:
+ resolution:
+ {
+ integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==,
+ }
+ engines: { node: ">=18" }
+
+ wsl-utils@0.3.1:
+ resolution:
+ {
+ integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==,
+ }
+ engines: { node: ">=20" }
+
+ y18n@5.0.8:
+ resolution:
+ {
+ integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==,
+ }
+ engines: { node: ">=10" }
+
+ yargs-parser@22.0.0:
+ resolution:
+ {
+ integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==,
+ }
+ engines: { node: ^20.19.0 || ^22.12.0 || >=23 }
+
+ yargs@18.0.0:
+ resolution:
+ {
+ integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==,
+ }
+ engines: { node: ^20.19.0 || ^22.12.0 || >=23 }
+
+snapshots:
+ "@codingame/monaco-vscode-api@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-base-service-override": 25.1.2
+ "@codingame/monaco-vscode-environment-service-override": 25.1.2
+ "@codingame/monaco-vscode-extensions-service-override": 25.1.2
+ "@codingame/monaco-vscode-files-service-override": 25.1.2
+ "@codingame/monaco-vscode-host-service-override": 25.1.2
+ "@codingame/monaco-vscode-layout-service-override": 25.1.2
+ "@codingame/monaco-vscode-quickaccess-service-override": 25.1.2
+ "@vscode/iconv-lite-umd": 0.7.1
+ dompurify: 3.3.1
+ jschardet: 3.1.4
+ marked: 14.0.0
+
+ "@codingame/monaco-vscode-base-service-override@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-bulk-edit-service-override@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-configuration-service-override@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+ "@codingame/monaco-vscode-files-service-override": 25.1.2
+
+ "@codingame/monaco-vscode-editor-api@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-editor-service-override@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-environment-service-override@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-extension-api@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+ "@codingame/monaco-vscode-extensions-service-override": 25.1.2
+
+ "@codingame/monaco-vscode-extensions-service-override@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+ "@codingame/monaco-vscode-files-service-override": 25.1.2
+
+ "@codingame/monaco-vscode-files-service-override@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-host-service-override@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-keybindings-service-override@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+ "@codingame/monaco-vscode-files-service-override": 25.1.2
+
+ "@codingame/monaco-vscode-language-pack-cs@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-language-pack-de@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-language-pack-es@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-language-pack-fr@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-language-pack-it@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-language-pack-ja@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-language-pack-ko@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-language-pack-pl@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-language-pack-pt-br@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-language-pack-qps-ploc@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-language-pack-ru@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-language-pack-tr@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-language-pack-zh-hans@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-language-pack-zh-hant@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-languages-service-override@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+ "@codingame/monaco-vscode-files-service-override": 25.1.2
+
+ "@codingame/monaco-vscode-layout-service-override@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-localization-service-override@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-log-service-override@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+ "@codingame/monaco-vscode-environment-service-override": 25.1.2
+
+ "@codingame/monaco-vscode-model-service-override@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-monarch-service-override@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-quickaccess-service-override@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-textmate-service-override@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+ "@codingame/monaco-vscode-files-service-override": 25.1.2
+
+ "@codingame/monaco-vscode-theme-defaults-default-extension@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-theme-service-override@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+ "@codingame/monaco-vscode-files-service-override": 25.1.2
+
+ "@codingame/monaco-vscode-view-banner-service-override@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-view-common-service-override@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+ "@codingame/monaco-vscode-bulk-edit-service-override": 25.1.2
+
+ "@codingame/monaco-vscode-view-status-bar-service-override@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-view-title-bar-service-override@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+
+ "@codingame/monaco-vscode-views-service-override@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+ "@codingame/monaco-vscode-keybindings-service-override": 25.1.2
+ "@codingame/monaco-vscode-layout-service-override": 25.1.2
+ "@codingame/monaco-vscode-quickaccess-service-override": 25.1.2
+ "@codingame/monaco-vscode-view-common-service-override": 25.1.2
+
+ "@codingame/monaco-vscode-workbench-service-override@25.1.2":
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+ "@codingame/monaco-vscode-keybindings-service-override": 25.1.2
+ "@codingame/monaco-vscode-quickaccess-service-override": 25.1.2
+ "@codingame/monaco-vscode-view-banner-service-override": 25.1.2
+ "@codingame/monaco-vscode-view-common-service-override": 25.1.2
+ "@codingame/monaco-vscode-view-status-bar-service-override": 25.1.2
+ "@codingame/monaco-vscode-view-title-bar-service-override": 25.1.2
+
+ "@emnapi/core@1.9.1":
+ dependencies:
+ "@emnapi/wasi-threads": 1.2.0
+ tslib: 2.8.1
+ optional: true
+
+ "@emnapi/runtime@1.9.1":
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ "@emnapi/wasi-threads@1.2.0":
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ "@esbuild/aix-ppc64@0.27.4":
+ optional: true
+
+ "@esbuild/android-arm64@0.27.4":
+ optional: true
+
+ "@esbuild/android-arm@0.27.4":
+ optional: true
+
+ "@esbuild/android-x64@0.27.4":
+ optional: true
+
+ "@esbuild/darwin-arm64@0.27.4":
+ optional: true
+
+ "@esbuild/darwin-x64@0.27.4":
+ optional: true
+
+ "@esbuild/freebsd-arm64@0.27.4":
+ optional: true
+
+ "@esbuild/freebsd-x64@0.27.4":
+ optional: true
+
+ "@esbuild/linux-arm64@0.27.4":
+ optional: true
+
+ "@esbuild/linux-arm@0.27.4":
+ optional: true
+
+ "@esbuild/linux-ia32@0.27.4":
+ optional: true
+
+ "@esbuild/linux-loong64@0.27.4":
+ optional: true
+
+ "@esbuild/linux-mips64el@0.27.4":
+ optional: true
+
+ "@esbuild/linux-ppc64@0.27.4":
+ optional: true
+
+ "@esbuild/linux-riscv64@0.27.4":
+ optional: true
+
+ "@esbuild/linux-s390x@0.27.4":
+ optional: true
+
+ "@esbuild/linux-x64@0.27.4":
+ optional: true
+
+ "@esbuild/netbsd-arm64@0.27.4":
+ optional: true
+
+ "@esbuild/netbsd-x64@0.27.4":
+ optional: true
+
+ "@esbuild/openbsd-arm64@0.27.4":
+ optional: true
+
+ "@esbuild/openbsd-x64@0.27.4":
+ optional: true
+
+ "@esbuild/openharmony-arm64@0.27.4":
+ optional: true
+
+ "@esbuild/sunos-x64@0.27.4":
+ optional: true
+
+ "@esbuild/win32-arm64@0.27.4":
+ optional: true
+
+ "@esbuild/win32-ia32@0.27.4":
+ optional: true
+
+ "@esbuild/win32-x64@0.27.4":
+ optional: true
+
+ "@lit-labs/ssr-dom-shim@1.5.1": {}
+
+ "@lit/reactive-element@2.1.2":
+ dependencies:
+ "@lit-labs/ssr-dom-shim": 1.5.1
+
+ "@napi-rs/wasm-runtime@1.1.1":
+ dependencies:
+ "@emnapi/core": 1.9.1
+ "@emnapi/runtime": 1.9.1
+ "@tybys/wasm-util": 0.10.1
+ optional: true
+
+ "@oxc-project/types@0.120.0":
+ optional: true
+
+ "@rolldown/binding-android-arm64@1.0.0-rc.10":
+ optional: true
+
+ "@rolldown/binding-darwin-arm64@1.0.0-rc.10":
+ optional: true
+
+ "@rolldown/binding-darwin-x64@1.0.0-rc.10":
+ optional: true
+
+ "@rolldown/binding-freebsd-x64@1.0.0-rc.10":
+ optional: true
+
+ "@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.10":
+ optional: true
+
+ "@rolldown/binding-linux-arm64-gnu@1.0.0-rc.10":
+ optional: true
+
+ "@rolldown/binding-linux-arm64-musl@1.0.0-rc.10":
+ optional: true
+
+ "@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.10":
+ optional: true
+
+ "@rolldown/binding-linux-s390x-gnu@1.0.0-rc.10":
+ optional: true
+
+ "@rolldown/binding-linux-x64-gnu@1.0.0-rc.10":
+ optional: true
+
+ "@rolldown/binding-linux-x64-musl@1.0.0-rc.10":
+ optional: true
+
+ "@rolldown/binding-openharmony-arm64@1.0.0-rc.10":
+ optional: true
+
+ "@rolldown/binding-wasm32-wasi@1.0.0-rc.10":
+ dependencies:
+ "@napi-rs/wasm-runtime": 1.1.1
+ optional: true
+
+ "@rolldown/binding-win32-arm64-msvc@1.0.0-rc.10":
+ optional: true
+
+ "@rolldown/binding-win32-x64-msvc@1.0.0-rc.10":
+ optional: true
+
+ "@rolldown/pluginutils@1.0.0-rc.10":
+ optional: true
+
+ "@rollup/rollup-android-arm-eabi@4.60.0":
+ optional: true
+
+ "@rollup/rollup-android-arm64@4.60.0":
+ optional: true
+
+ "@rollup/rollup-darwin-arm64@4.60.0":
+ optional: true
+
+ "@rollup/rollup-darwin-x64@4.60.0":
+ optional: true
+
+ "@rollup/rollup-freebsd-arm64@4.60.0":
+ optional: true
+
+ "@rollup/rollup-freebsd-x64@4.60.0":
+ optional: true
+
+ "@rollup/rollup-linux-arm-gnueabihf@4.60.0":
+ optional: true
+
+ "@rollup/rollup-linux-arm-musleabihf@4.60.0":
+ optional: true
+
+ "@rollup/rollup-linux-arm64-gnu@4.60.0":
+ optional: true
+
+ "@rollup/rollup-linux-arm64-musl@4.60.0":
+ optional: true
+
+ "@rollup/rollup-linux-loong64-gnu@4.60.0":
+ optional: true
+
+ "@rollup/rollup-linux-loong64-musl@4.60.0":
+ optional: true
+
+ "@rollup/rollup-linux-ppc64-gnu@4.60.0":
+ optional: true
+
+ "@rollup/rollup-linux-ppc64-musl@4.60.0":
+ optional: true
+
+ "@rollup/rollup-linux-riscv64-gnu@4.60.0":
+ optional: true
+
+ "@rollup/rollup-linux-riscv64-musl@4.60.0":
+ optional: true
+
+ "@rollup/rollup-linux-s390x-gnu@4.60.0":
+ optional: true
+
+ "@rollup/rollup-linux-x64-gnu@4.60.0":
+ optional: true
+
+ "@rollup/rollup-linux-x64-musl@4.60.0":
+ optional: true
+
+ "@rollup/rollup-openbsd-x64@4.60.0":
+ optional: true
+
+ "@rollup/rollup-openharmony-arm64@4.60.0":
+ optional: true
+
+ "@rollup/rollup-win32-arm64-msvc@4.60.0":
+ optional: true
+
+ "@rollup/rollup-win32-ia32-msvc@4.60.0":
+ optional: true
+
+ "@rollup/rollup-win32-x64-gnu@4.60.0":
+ optional: true
+
+ "@rollup/rollup-win32-x64-msvc@4.60.0":
+ optional: true
+
+ "@tybys/wasm-util@0.10.1":
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ "@types/estree@1.0.8": {}
+
+ "@types/trusted-types@2.0.7": {}
+
+ "@vscode/iconv-lite-umd@0.7.1": {}
+
+ ansi-regex@6.2.2: {}
+
+ ansi-styles@6.2.3: {}
+
+ balanced-match@1.0.2: {}
+
+ brace-expansion@2.0.2:
+ dependencies:
+ balanced-match: 1.0.2
+
+ bundle-name@4.1.0:
+ dependencies:
+ run-applescript: 7.1.0
+
+ cliui@9.0.1:
+ dependencies:
+ string-width: 7.2.0
+ strip-ansi: 7.2.0
+ wrap-ansi: 9.0.2
+
+ default-browser-id@5.0.1: {}
+
+ default-browser@5.5.0:
+ dependencies:
+ bundle-name: 4.1.0
+ default-browser-id: 5.0.1
+
+ define-lazy-prop@3.0.0: {}
+
+ detect-libc@2.1.2:
+ optional: true
+
+ dompurify@3.3.1:
+ optionalDependencies:
+ "@types/trusted-types": 2.0.7
+
+ emoji-regex@10.6.0: {}
+
+ esbuild@0.27.4:
+ optionalDependencies:
+ "@esbuild/aix-ppc64": 0.27.4
+ "@esbuild/android-arm": 0.27.4
+ "@esbuild/android-arm64": 0.27.4
+ "@esbuild/android-x64": 0.27.4
+ "@esbuild/darwin-arm64": 0.27.4
+ "@esbuild/darwin-x64": 0.27.4
+ "@esbuild/freebsd-arm64": 0.27.4
+ "@esbuild/freebsd-x64": 0.27.4
+ "@esbuild/linux-arm": 0.27.4
+ "@esbuild/linux-arm64": 0.27.4
+ "@esbuild/linux-ia32": 0.27.4
+ "@esbuild/linux-loong64": 0.27.4
+ "@esbuild/linux-mips64el": 0.27.4
+ "@esbuild/linux-ppc64": 0.27.4
+ "@esbuild/linux-riscv64": 0.27.4
+ "@esbuild/linux-s390x": 0.27.4
+ "@esbuild/linux-x64": 0.27.4
+ "@esbuild/netbsd-arm64": 0.27.4
+ "@esbuild/netbsd-x64": 0.27.4
+ "@esbuild/openbsd-arm64": 0.27.4
+ "@esbuild/openbsd-x64": 0.27.4
+ "@esbuild/openharmony-arm64": 0.27.4
+ "@esbuild/sunos-x64": 0.27.4
+ "@esbuild/win32-arm64": 0.27.4
+ "@esbuild/win32-ia32": 0.27.4
+ "@esbuild/win32-x64": 0.27.4
+
+ escalade@3.2.0: {}
+
+ fdir@6.5.0(picomatch@4.0.3):
+ optionalDependencies:
+ picomatch: 4.0.3
+
+ fsevents@2.3.3:
+ optional: true
+
+ get-caller-file@2.0.5: {}
+
+ get-east-asian-width@1.5.0: {}
+
+ is-docker@3.0.0: {}
+
+ is-in-ssh@1.0.0: {}
+
+ is-inside-container@1.0.0:
+ dependencies:
+ is-docker: 3.0.0
+
+ is-wsl@3.1.1:
+ dependencies:
+ is-inside-container: 1.0.0
+
+ jschardet@3.1.4: {}
+
+ lightningcss-android-arm64@1.32.0:
+ optional: true
+
+ lightningcss-darwin-arm64@1.32.0:
+ optional: true
+
+ lightningcss-darwin-x64@1.32.0:
+ optional: true
+
+ lightningcss-freebsd-x64@1.32.0:
+ optional: true
+
+ lightningcss-linux-arm-gnueabihf@1.32.0:
+ optional: true
+
+ lightningcss-linux-arm64-gnu@1.32.0:
+ optional: true
+
+ lightningcss-linux-arm64-musl@1.32.0:
+ optional: true
+
+ lightningcss-linux-x64-gnu@1.32.0:
+ optional: true
+
+ lightningcss-linux-x64-musl@1.32.0:
+ optional: true
+
+ lightningcss-win32-arm64-msvc@1.32.0:
+ optional: true
+
+ lightningcss-win32-x64-msvc@1.32.0:
+ optional: true
+
+ lightningcss@1.32.0:
+ dependencies:
+ detect-libc: 2.1.2
+ optionalDependencies:
+ lightningcss-android-arm64: 1.32.0
+ lightningcss-darwin-arm64: 1.32.0
+ lightningcss-darwin-x64: 1.32.0
+ lightningcss-freebsd-x64: 1.32.0
+ lightningcss-linux-arm-gnueabihf: 1.32.0
+ lightningcss-linux-arm64-gnu: 1.32.0
+ lightningcss-linux-arm64-musl: 1.32.0
+ lightningcss-linux-x64-gnu: 1.32.0
+ lightningcss-linux-x64-musl: 1.32.0
+ lightningcss-win32-arm64-msvc: 1.32.0
+ lightningcss-win32-x64-msvc: 1.32.0
+ optional: true
+
+ lit-element@4.2.2:
+ dependencies:
+ "@lit-labs/ssr-dom-shim": 1.5.1
+ "@lit/reactive-element": 2.1.2
+ lit-html: 3.3.2
+
+ lit-html@3.3.2:
+ dependencies:
+ "@types/trusted-types": 2.0.7
+
+ lit@3.3.2:
+ dependencies:
+ "@lit/reactive-element": 2.1.2
+ lit-element: 4.2.2
+ lit-html: 3.3.2
+
+ marked@14.0.0: {}
+
+ minimatch@5.1.9:
+ dependencies:
+ brace-expansion: 2.0.2
+
+ monaco-languageclient@10.7.0:
+ dependencies:
+ "@codingame/monaco-vscode-api": 25.1.2
+ "@codingame/monaco-vscode-configuration-service-override": 25.1.2
+ "@codingame/monaco-vscode-editor-api": 25.1.2
+ "@codingame/monaco-vscode-editor-service-override": 25.1.2
+ "@codingame/monaco-vscode-extension-api": 25.1.2
+ "@codingame/monaco-vscode-extensions-service-override": 25.1.2
+ "@codingame/monaco-vscode-language-pack-cs": 25.1.2
+ "@codingame/monaco-vscode-language-pack-de": 25.1.2
+ "@codingame/monaco-vscode-language-pack-es": 25.1.2
+ "@codingame/monaco-vscode-language-pack-fr": 25.1.2
+ "@codingame/monaco-vscode-language-pack-it": 25.1.2
+ "@codingame/monaco-vscode-language-pack-ja": 25.1.2
+ "@codingame/monaco-vscode-language-pack-ko": 25.1.2
+ "@codingame/monaco-vscode-language-pack-pl": 25.1.2
+ "@codingame/monaco-vscode-language-pack-pt-br": 25.1.2
+ "@codingame/monaco-vscode-language-pack-qps-ploc": 25.1.2
+ "@codingame/monaco-vscode-language-pack-ru": 25.1.2
+ "@codingame/monaco-vscode-language-pack-tr": 25.1.2
+ "@codingame/monaco-vscode-language-pack-zh-hans": 25.1.2
+ "@codingame/monaco-vscode-language-pack-zh-hant": 25.1.2
+ "@codingame/monaco-vscode-languages-service-override": 25.1.2
+ "@codingame/monaco-vscode-localization-service-override": 25.1.2
+ "@codingame/monaco-vscode-log-service-override": 25.1.2
+ "@codingame/monaco-vscode-model-service-override": 25.1.2
+ "@codingame/monaco-vscode-monarch-service-override": 25.1.2
+ "@codingame/monaco-vscode-textmate-service-override": 25.1.2
+ "@codingame/monaco-vscode-theme-defaults-default-extension": 25.1.2
+ "@codingame/monaco-vscode-theme-service-override": 25.1.2
+ "@codingame/monaco-vscode-views-service-override": 25.1.2
+ "@codingame/monaco-vscode-workbench-service-override": 25.1.2
+ vscode: "@codingame/monaco-vscode-extension-api@25.1.2"
+ vscode-languageclient: 9.0.1
+ vscode-languageserver-protocol: 3.17.5
+ vscode-ws-jsonrpc: 3.5.0
+
+ nanoid@3.3.11: {}
+
+ open@11.0.0:
+ dependencies:
+ default-browser: 5.5.0
+ define-lazy-prop: 3.0.0
+ is-in-ssh: 1.0.0
+ is-inside-container: 1.0.0
+ powershell-utils: 0.1.0
+ wsl-utils: 0.3.1
+
+ picocolors@1.1.1: {}
+
+ picomatch@4.0.3: {}
+
+ postcss@8.5.8:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ powershell-utils@0.1.0: {}
+
+ prettier@3.8.1: {}
+
+ rolldown@1.0.0-rc.10:
+ dependencies:
+ "@oxc-project/types": 0.120.0
+ "@rolldown/pluginutils": 1.0.0-rc.10
+ optionalDependencies:
+ "@rolldown/binding-android-arm64": 1.0.0-rc.10
+ "@rolldown/binding-darwin-arm64": 1.0.0-rc.10
+ "@rolldown/binding-darwin-x64": 1.0.0-rc.10
+ "@rolldown/binding-freebsd-x64": 1.0.0-rc.10
+ "@rolldown/binding-linux-arm-gnueabihf": 1.0.0-rc.10
+ "@rolldown/binding-linux-arm64-gnu": 1.0.0-rc.10
+ "@rolldown/binding-linux-arm64-musl": 1.0.0-rc.10
+ "@rolldown/binding-linux-ppc64-gnu": 1.0.0-rc.10
+ "@rolldown/binding-linux-s390x-gnu": 1.0.0-rc.10
+ "@rolldown/binding-linux-x64-gnu": 1.0.0-rc.10
+ "@rolldown/binding-linux-x64-musl": 1.0.0-rc.10
+ "@rolldown/binding-openharmony-arm64": 1.0.0-rc.10
+ "@rolldown/binding-wasm32-wasi": 1.0.0-rc.10
+ "@rolldown/binding-win32-arm64-msvc": 1.0.0-rc.10
+ "@rolldown/binding-win32-x64-msvc": 1.0.0-rc.10
+ optional: true
+
+ rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-rc.10)(rollup@4.60.0):
+ dependencies:
+ open: 11.0.0
+ picomatch: 4.0.3
+ source-map: 0.7.6
+ yargs: 18.0.0
+ optionalDependencies:
+ rolldown: 1.0.0-rc.10
+ rollup: 4.60.0
+
+ rollup@4.60.0:
+ dependencies:
+ "@types/estree": 1.0.8
+ optionalDependencies:
+ "@rollup/rollup-android-arm-eabi": 4.60.0
+ "@rollup/rollup-android-arm64": 4.60.0
+ "@rollup/rollup-darwin-arm64": 4.60.0
+ "@rollup/rollup-darwin-x64": 4.60.0
+ "@rollup/rollup-freebsd-arm64": 4.60.0
+ "@rollup/rollup-freebsd-x64": 4.60.0
+ "@rollup/rollup-linux-arm-gnueabihf": 4.60.0
+ "@rollup/rollup-linux-arm-musleabihf": 4.60.0
+ "@rollup/rollup-linux-arm64-gnu": 4.60.0
+ "@rollup/rollup-linux-arm64-musl": 4.60.0
+ "@rollup/rollup-linux-loong64-gnu": 4.60.0
+ "@rollup/rollup-linux-loong64-musl": 4.60.0
+ "@rollup/rollup-linux-ppc64-gnu": 4.60.0
+ "@rollup/rollup-linux-ppc64-musl": 4.60.0
+ "@rollup/rollup-linux-riscv64-gnu": 4.60.0
+ "@rollup/rollup-linux-riscv64-musl": 4.60.0
+ "@rollup/rollup-linux-s390x-gnu": 4.60.0
+ "@rollup/rollup-linux-x64-gnu": 4.60.0
+ "@rollup/rollup-linux-x64-musl": 4.60.0
+ "@rollup/rollup-openbsd-x64": 4.60.0
+ "@rollup/rollup-openharmony-arm64": 4.60.0
+ "@rollup/rollup-win32-arm64-msvc": 4.60.0
+ "@rollup/rollup-win32-ia32-msvc": 4.60.0
+ "@rollup/rollup-win32-x64-gnu": 4.60.0
+ "@rollup/rollup-win32-x64-msvc": 4.60.0
+ fsevents: 2.3.3
+
+ run-applescript@7.1.0: {}
+
+ semver@7.7.4: {}
+
+ source-map-js@1.2.1: {}
+
+ source-map@0.7.6: {}
+
+ string-width@7.2.0:
+ dependencies:
+ emoji-regex: 10.6.0
+ get-east-asian-width: 1.5.0
+ strip-ansi: 7.2.0
+
+ strip-ansi@7.2.0:
+ dependencies:
+ ansi-regex: 6.2.2
+
+ tinyglobby@0.2.15:
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+
+ tslib@2.8.1:
+ optional: true
+
+ typescript@5.9.3: {}
+
+ vite@7.3.1(lightningcss@1.32.0):
+ dependencies:
+ esbuild: 0.27.4
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+ postcss: 8.5.8
+ rollup: 4.60.0
+ tinyglobby: 0.2.15
+ optionalDependencies:
+ fsevents: 2.3.3
+ lightningcss: 1.32.0
+
+ vscode-jsonrpc@8.2.0: {}
+
+ vscode-jsonrpc@8.2.1: {}
+
+ vscode-languageclient@9.0.1:
+ dependencies:
+ minimatch: 5.1.9
+ semver: 7.7.4
+ vscode-languageserver-protocol: 3.17.5
+
+ vscode-languageserver-protocol@3.17.5:
+ dependencies:
+ vscode-jsonrpc: 8.2.0
+ vscode-languageserver-types: 3.17.5
+
+ vscode-languageserver-types@3.17.5: {}
+
+ vscode-ws-jsonrpc@3.5.0:
+ dependencies:
+ vscode-jsonrpc: 8.2.1
+
+ wrap-ansi@9.0.2:
+ dependencies:
+ ansi-styles: 6.2.3
+ string-width: 7.2.0
+ strip-ansi: 7.2.0
+
+ wsl-utils@0.3.1:
+ dependencies:
+ is-wsl: 3.1.1
+ powershell-utils: 0.1.0
+
+ y18n@5.0.8: {}
+
+ yargs-parser@22.0.0: {}
+
+ yargs@18.0.0:
+ dependencies:
+ cliui: 9.0.1
+ escalade: 3.2.0
+ get-caller-file: 2.0.5
+ string-width: 7.2.0
+ y18n: 5.0.8
+ yargs-parser: 22.0.0
diff --git a/playground/public/apple-touch-icon.png b/playground/public/apple-touch-icon.png
new file mode 100644
index 000000000..add381359
Binary files /dev/null and b/playground/public/apple-touch-icon.png differ
diff --git a/playground/public/favicon-96x96.png b/playground/public/favicon-96x96.png
new file mode 100644
index 000000000..b8ab064e6
Binary files /dev/null and b/playground/public/favicon-96x96.png differ
diff --git a/playground/public/favicon.ico b/playground/public/favicon.ico
new file mode 100644
index 000000000..c8fc7fbf9
Binary files /dev/null and b/playground/public/favicon.ico differ
diff --git a/playground/public/favicon.svg b/playground/public/favicon.svg
new file mode 100644
index 000000000..cd1d1fcf2
--- /dev/null
+++ b/playground/public/favicon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/playground/public/web-app-manifest-192x192.png b/playground/public/web-app-manifest-192x192.png
new file mode 100644
index 000000000..bba1f3c4d
Binary files /dev/null and b/playground/public/web-app-manifest-192x192.png differ
diff --git a/playground/public/web-app-manifest-512x512.png b/playground/public/web-app-manifest-512x512.png
new file mode 100644
index 000000000..6f3b359a5
Binary files /dev/null and b/playground/public/web-app-manifest-512x512.png differ
diff --git a/playground/public/yara-mark.svg b/playground/public/yara-mark.svg
new file mode 100644
index 000000000..7a99ccb30
--- /dev/null
+++ b/playground/public/yara-mark.svg
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/playground/src/app/yara-playground-app.ts b/playground/src/app/yara-playground-app.ts
new file mode 100644
index 000000000..7c8805db3
--- /dev/null
+++ b/playground/src/app/yara-playground-app.ts
@@ -0,0 +1,361 @@
+import { DEFAULT_SAMPLE_INPUT, DEFAULT_SAMPLE_RULE } from "../data/sample";
+import type {
+ ExecutionState,
+ LoadedSampleFile,
+ ResultMode,
+ SampleMode,
+} from "../types/execution";
+import { LitElement, html } from "lit";
+import { customElement, query, state } from "lit/decorators.js";
+
+import { YaraFile } from "../components/yara-file";
+import "../components/yara-result-panel";
+import "../components/yara-status-bar";
+import { SplitResizeController } from "../controllers/split-resize-controller";
+import {
+ createPlainTextEditor,
+ createYaraEditor,
+ type EditorHandle,
+} from "../editor/yara-monaco";
+import { summarizeResult } from "../results/summarize-result";
+import { getWasmYaraEngine } from "../services/wasm-yara-engine";
+import type { ServiceStatus } from "../types/service-status";
+import type { YaraEngine } from "../services/yara-engine";
+
+const INITIAL_EXECUTION: ExecutionState = {
+ raw: {
+ message: "Local YARA-X playground ready",
+ hint: "Edit the rule, tweak the sample text, then run the scanner.",
+ },
+ durationMs: null,
+ summary: summarizeResult({}, "scan"),
+};
+
+@customElement("yara-playground-app")
+export class YaraPlaygroundApp extends LitElement {
+ @query(".rule-editor")
+ private ruleEditorHost!: HTMLDivElement;
+
+ @query(".sample-editor")
+ private sampleEditorHost!: HTMLDivElement;
+
+ @query(".editor-region")
+ private editorRegion!: HTMLDivElement;
+
+ @query(".workspace")
+ private workspace!: HTMLDivElement;
+
+ @query("yara-file")
+ private filePane!: YaraFile;
+
+ @state()
+ private resultMode: ResultMode = "summary";
+
+ @state()
+ private sampleMode: SampleMode = "text";
+
+ @state()
+ private isBusy = false;
+
+ @state()
+ private lspStatus: ServiceStatus = "idle";
+
+ @state()
+ private coreStatus: ServiceStatus = "idle";
+
+ @state()
+ private editorSplit = 49.6;
+
+ @state()
+ private workspaceSplit = 50;
+
+ @state()
+ private execution = INITIAL_EXECUTION;
+
+ @state()
+ private loadedSampleFile: LoadedSampleFile | null = null;
+
+ private ruleEditor?: EditorHandle;
+ private sampleEditor?: EditorHandle;
+ private engine?: YaraEngine;
+
+ private readonly editorResizeController = new SplitResizeController(this, {
+ getElement: () => this.editorRegion ?? null,
+ getAxis: () => (this.isCompactLayout ? "y" : "x"),
+ min: 28,
+ max: 72,
+ onChange: (value) => {
+ this.editorSplit = value;
+ },
+ });
+
+ private readonly workspaceResizeController = new SplitResizeController(this, {
+ getElement: () => this.workspace ?? null,
+ getAxis: () => "y",
+ min: 32,
+ max: 78,
+ onChange: (value) => {
+ this.workspaceSplit = value;
+ },
+ });
+
+ protected createRenderRoot() {
+ // Disable Shadow DOM so Monaco and its floating widgets can render correctly.
+ return this;
+ }
+
+ private readonly handleKeydown = (event: KeyboardEvent) => {
+ if (
+ (event.metaKey || event.ctrlKey) &&
+ !event.shiftKey &&
+ event.key.toLowerCase() === "s"
+ ) {
+ event.preventDefault();
+ void this.formatRule();
+ return;
+ }
+
+ if (
+ (event.metaKey || event.ctrlKey) &&
+ event.shiftKey &&
+ event.key.toLowerCase() === "f"
+ ) {
+ event.preventDefault();
+ void this.formatRule();
+ return;
+ }
+
+ if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
+ event.preventDefault();
+ void this.runScan();
+ }
+ };
+
+ connectedCallback() {
+ super.connectedCallback();
+ window.addEventListener("keydown", this.handleKeydown);
+ }
+
+ protected async firstUpdated() {
+ await this.filePane.updateComplete;
+
+ try {
+ const [ruleEditor, sampleEditor] = await Promise.all([
+ createYaraEditor(this.ruleEditorHost, DEFAULT_SAMPLE_RULE),
+ createPlainTextEditor(this.sampleEditorHost, DEFAULT_SAMPLE_INPUT),
+ ]);
+
+ this.ruleEditor = ruleEditor;
+ this.sampleEditor = sampleEditor;
+ this.lspStatus = "idle";
+ } catch (error) {
+ console.error("failed to initialize editors", error);
+ this.lspStatus = "error";
+ this.execution = {
+ raw: {
+ errors: [error instanceof Error ? error.message : String(error)],
+ warnings: [],
+ matching_rules: [],
+ non_matching_rules: [],
+ },
+ durationMs: null,
+ summary: summarizeResult(
+ {
+ errors: [error instanceof Error ? error.message : String(error)],
+ },
+ "scan",
+ ),
+ };
+ }
+ }
+
+ disconnectedCallback() {
+ window.removeEventListener("keydown", this.handleKeydown);
+ this.ruleEditor?.dispose();
+ this.sampleEditor?.dispose();
+ super.disconnectedCallback();
+ }
+
+ private get editorsReady() {
+ return Boolean(
+ this.ruleEditor && this.sampleEditor && this.lspStatus === "ready",
+ );
+ }
+
+ private get canRun() {
+ return (
+ this.editorsReady &&
+ !this.isBusy &&
+ (this.sampleMode === "text" || this.loadedSampleFile !== null)
+ );
+ }
+
+ private get isCompactLayout() {
+ return window.innerWidth <= 900;
+ }
+
+ private async ensureEngine() {
+ this.coreStatus = "loading";
+
+ try {
+ if (!this.engine) {
+ this.engine = getWasmYaraEngine();
+ }
+
+ this.coreStatus = "ready";
+ return this.engine;
+ } catch (error) {
+ this.coreStatus = "error";
+ throw error;
+ }
+ }
+
+ private async runScan() {
+ if (!this.canRun) return;
+
+ this.isBusy = true;
+ const startedAt = performance.now();
+
+ try {
+ const engine = await this.ensureEngine();
+ let sampleBytes: Uint8Array;
+
+ if (this.sampleMode === "file") {
+ sampleBytes = this.loadedSampleFile?.bytes ?? new Uint8Array();
+ } else {
+ sampleBytes = new TextEncoder().encode(
+ this.sampleEditor?.getValue() ?? "",
+ );
+ }
+
+ const compiler = await engine.createCompiler();
+ compiler.addSource(this.ruleEditor?.getValue() ?? "");
+
+ const rules = compiler.build();
+ const raw = rules.scan(sampleBytes);
+
+ this.execution = {
+ raw,
+ durationMs: Math.round(performance.now() - startedAt),
+ summary: summarizeResult(raw, "scan"),
+ };
+ } catch (error) {
+ const message = error instanceof Error ? error.message : String(error);
+ const raw = {
+ errors: [message],
+ warnings: [],
+ matching_rules: [],
+ non_matching_rules: [],
+ };
+
+ this.execution = {
+ raw,
+ durationMs: Math.round(performance.now() - startedAt),
+ summary: summarizeResult(raw, "scan"),
+ };
+ } finally {
+ this.isBusy = false;
+ }
+ }
+
+ private async formatRule() {
+ if (!this.ruleEditor || this.lspStatus !== "ready") return;
+
+ const formatted = await this.ruleEditor.format();
+ if (!formatted) {
+ console.warn("format action is not available for the YARA editor");
+ }
+ }
+
+ private handleRunRequest = () => {
+ void this.runScan();
+ };
+
+ private handleSampleModeChange = (event: CustomEvent) => {
+ this.sampleMode = event.detail;
+ };
+
+ private handleSampleFileLoad = (event: CustomEvent) => {
+ this.loadedSampleFile = event.detail;
+ this.sampleMode = "file";
+ };
+
+ private handleSampleFileClear = () => {
+ this.loadedSampleFile = null;
+ this.sampleMode = "text";
+ };
+
+ private handleResultModeChange = (event: CustomEvent) => {
+ this.resultMode = event.detail;
+ };
+
+ render() {
+ return html`
+
+
+
+
+
+
+
+
+
Rule editor
+
+ main.yar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `;
+ }
+}
diff --git a/playground/src/components/yara-file.ts b/playground/src/components/yara-file.ts
new file mode 100644
index 000000000..7cf92d2b5
--- /dev/null
+++ b/playground/src/components/yara-file.ts
@@ -0,0 +1,246 @@
+import { LitElement, html } from "lit";
+import type { LoadedSampleFile, SampleMode } from "../types/execution";
+import { customElement, property, query, state } from "lit/decorators.js";
+
+import { classMap } from "lit/directives/class-map.js";
+
+@customElement("yara-file")
+export class YaraFile extends LitElement {
+ @property({ type: String })
+ sampleMode: SampleMode = "text";
+
+ @property({ attribute: false })
+ loadedSampleFile: LoadedSampleFile | null = null;
+
+ @query("#sample-file-input")
+ private sampleFileInput!: HTMLInputElement;
+
+ @state()
+ private isDragActive = false;
+
+ protected createRenderRoot() {
+ return this;
+ }
+
+ private dispatchSampleModeChange(mode: SampleMode) {
+ this.dispatchEvent(
+ new CustomEvent("sample-mode-change", {
+ detail: mode,
+ bubbles: true,
+ composed: true,
+ }),
+ );
+ }
+
+ private async createLoadedSampleFile(file: File): Promise {
+ return {
+ name: file.name,
+ size: file.size,
+ bytes: new Uint8Array(await file.arrayBuffer()),
+ };
+ }
+
+ private async emitLoadedSampleFile(file: File) {
+ const loadedSampleFile = await this.createLoadedSampleFile(file);
+
+ this.dispatchEvent(
+ new CustomEvent("sample-file-load", {
+ detail: loadedSampleFile,
+ bubbles: true,
+ composed: true,
+ }),
+ );
+ }
+
+ private openSampleFilePicker = () => {
+ this.sampleFileInput?.click();
+ };
+
+ private async handleFileChange(event: Event) {
+ const input = event.target as HTMLInputElement;
+ const file = input.files?.[0];
+
+ if (!file) return;
+
+ await this.emitLoadedSampleFile(file);
+ input.value = "";
+ }
+
+ private handleDragEnter = (event: DragEvent) => {
+ if (this.sampleMode !== "file") return;
+ if (!event.dataTransfer?.types.includes("Files")) return;
+
+ event.preventDefault();
+ this.isDragActive = true;
+ };
+
+ private handleDragOver = (event: DragEvent) => {
+ if (this.sampleMode !== "file") return;
+ if (!event.dataTransfer?.types.includes("Files")) return;
+
+ event.preventDefault();
+ event.dataTransfer.dropEffect = "copy";
+ this.isDragActive = true;
+ };
+
+ private handleDragLeave = (event: DragEvent) => {
+ if (this.sampleMode !== "file") return;
+
+ const nextTarget = event.relatedTarget as Node | null;
+
+ if (nextTarget && event.currentTarget instanceof Node) {
+ if (event.currentTarget.contains(nextTarget)) return;
+ }
+
+ this.isDragActive = false;
+ };
+
+ private handleDrop = async (event: DragEvent) => {
+ if (this.sampleMode !== "file") return;
+
+ event.preventDefault();
+ this.isDragActive = false;
+
+ const file = event.dataTransfer?.files?.[0];
+
+ if (!file) return;
+
+ await this.emitLoadedSampleFile(file);
+ };
+
+ private clearLoadedFile = () => {
+ this.dispatchEvent(
+ new CustomEvent("sample-file-clear", {
+ bubbles: true,
+ composed: true,
+ }),
+ );
+ };
+
+ private formatBytes(size: number) {
+ if (size < 1024) return `${size} B`;
+ if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`;
+ return `${(size / (1024 * 1024)).toFixed(1)} MB`;
+ }
+
+ private renderModeChip(mode: SampleMode, label: string) {
+ return html`
+
+ {
+ this.dispatchSampleModeChange(mode);
+ }}
+ />
+ ${label}
+
+ `;
+ }
+
+ private renderFileState() {
+ if (!this.loadedSampleFile) {
+ return html`
+
+
+
File mode
+
No file selected yet
+
+ Choose a file or drag one here to keep everything local in your
+ browser. Nothing leaves your device.
+
+
+
+ Choose file
+
+
+ `;
+ }
+
+ return html`
+
+
+
File ready
+
${this.loadedSampleFile.name}
+
+ ${this.formatBytes(this.loadedSampleFile.size)} loaded locally in
+ memory for this session. Drop another file here to replace it.
+
+
+
+
+ Replace
+
+
+ Cancel
+
+
+
+ `;
+ }
+
+ render() {
+ return html`
+
+
+
+
+
+ ${this.renderModeChip("text", "Text")}
+ ${this.renderModeChip("file", "File")}
+
+
+
+
+
+
+
+
${this.renderFileState()}
+
+
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "yara-file": YaraFile;
+ }
+}
diff --git a/playground/src/components/yara-result-panel.ts b/playground/src/components/yara-result-panel.ts
new file mode 100644
index 000000000..2cc6c195c
--- /dev/null
+++ b/playground/src/components/yara-result-panel.ts
@@ -0,0 +1,236 @@
+import type { ExecutionState, ResultMode } from "../types/execution";
+import { LitElement, html, nothing } from "lit";
+import { customElement, property } from "lit/decorators.js";
+
+import { classMap } from "lit/directives/class-map.js";
+import { summarizeResult } from "../results/summarize-result";
+
+const EMPTY_EXECUTION: ExecutionState = {
+ raw: {},
+ durationMs: null,
+ summary: summarizeResult({}, "scan"),
+};
+
+@customElement("yara-result-panel")
+export class YaraResultPanel extends LitElement {
+ @property({ type: String })
+ resultMode: ResultMode = "summary";
+
+ @property({ attribute: false })
+ execution: ExecutionState = EMPTY_EXECUTION;
+
+ protected createRenderRoot() {
+ return this;
+ }
+
+ private get resultLabel() {
+ switch (this.execution.summary.tone) {
+ case "match":
+ return "Match";
+ case "clean":
+ return "Clean";
+ case "issues":
+ return "Issues";
+ default:
+ return "Ready";
+ }
+ }
+
+ private dispatchResultModeChange(mode: ResultMode) {
+ this.dispatchEvent(
+ new CustomEvent("result-mode-change", {
+ detail: mode,
+ bubbles: true,
+ composed: true,
+ }),
+ );
+ }
+
+ private renderStat(label: string, value: number) {
+ return html`
+
+ ${label}
+ ${value}
+
+ `;
+ }
+
+ private renderIssues(title: string, issues: string[]) {
+ if (issues.length === 0) return nothing;
+
+ return html`
+
+ ${title}
+
+ ${issues.map((issue) => html`${issue} `)}
+
+
+ `;
+ }
+
+ private renderMatches() {
+ const { matchingRules } = this.execution.summary;
+
+ return html`
+
+
+
Matching rules
+
${matchingRules.length}
+
+
+ ${matchingRules.length === 0
+ ? html`
+
+ No matching rules for the current sample.
+
+ `
+ : html`
+
+ ${matchingRules.map(
+ (rule) => html`
+
+
+
+
${rule.identifier}
+
${rule.namespace}
+
+
${rule.hits} hits
+
+
+ ${rule.patterns.map(
+ (pattern) => html`
+
+ ${pattern.identifier}
+ ${pattern.ranges.join(", ") ||
+ "No ranges"}
+
+ `,
+ )}
+
+
+ `,
+ )}
+
+ `}
+
+ `;
+ }
+
+ private renderNonMatches() {
+ const { nonMatchingRules } = this.execution.summary;
+
+ if (nonMatchingRules.length === 0) return nothing;
+
+ return html`
+
+ Non-matching rules
+
+ ${nonMatchingRules.map(
+ (rule) => html`${rule} `,
+ )}
+
+
+ `;
+ }
+
+ private renderSummaryResults() {
+ const { summary } = this.execution;
+
+ return html`
+
+ ${this.renderStat("Matches", summary.matches)}
+ ${this.renderStat("Non-matches", summary.nonMatches)}
+ ${this.renderStat("Warnings", summary.warnings)}
+ ${this.renderStat("Errors", summary.errors)}
+
+
+
+
+
${summary.headline}
+
+ ${this.resultLabel}
+
+
+
+
+ ${this.renderIssues("Warnings", summary.warningsList)}
+ ${this.renderIssues("Errors", summary.errorsList)} ${this.renderMatches()}
+ ${this.renderNonMatches()}
+ `;
+ }
+
+ private renderRawResults() {
+ return html`
+${JSON.stringify(this.execution.raw, null, 2)} `;
+ }
+
+ private renderModeChip(mode: ResultMode, label: string) {
+ return html`
+
+ {
+ this.dispatchResultModeChange(mode);
+ }}
+ />
+ ${label}
+
+ `;
+ }
+
+ render() {
+ return html`
+
+
+
+
+ ${this.resultMode === "summary"
+ ? this.renderSummaryResults()
+ : this.renderRawResults()}
+
+
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "yara-result-panel": YaraResultPanel;
+ }
+}
diff --git a/playground/src/components/yara-status-bar.ts b/playground/src/components/yara-status-bar.ts
new file mode 100644
index 000000000..467867815
--- /dev/null
+++ b/playground/src/components/yara-status-bar.ts
@@ -0,0 +1,138 @@
+import { LitElement, html } from "lit";
+import { customElement, property } from "lit/decorators.js";
+
+import type { ServiceStatus } from "../types/service-status";
+import { classMap } from "lit/directives/class-map.js";
+
+const yaraMarkUrl = "/yara-mark.svg";
+
+function getServiceLabel(kind: "core" | "lsp", status: ServiceStatus) {
+ if (kind === "core") {
+ switch (status) {
+ case "loading":
+ return "Core loading";
+ case "ready":
+ return "Core ready";
+ case "error":
+ return "Core error";
+ default:
+ return "Core idle";
+ }
+ }
+
+ switch (status) {
+ case "loading":
+ return "LSP starting";
+ case "ready":
+ return "LSP ready";
+ case "error":
+ return "LSP error";
+ default:
+ return "LSP idle";
+ }
+}
+
+@customElement("yara-status-bar")
+export class YaraStatusBar extends LitElement {
+ @property({ type: String })
+ coreStatus: ServiceStatus = "idle";
+
+ @property({ type: String })
+ lspStatus: ServiceStatus = "idle";
+
+ @property({ type: Boolean })
+ isBusy = false;
+
+ @property({ type: Boolean })
+ canRun = false;
+
+ protected createRenderRoot() {
+ return this;
+ }
+
+ private dispatchRunRequest = () => {
+ this.dispatchEvent(
+ new CustomEvent("run-request", {
+ bubbles: true,
+ composed: true,
+ }),
+ );
+ };
+
+ private renderServiceChip(label: string, status: ServiceStatus) {
+ return html`
+
+ ${label}
+
+ `;
+ }
+
+ render() {
+ return html`
+
+
+
+
+
+
+ YARA-X Playground
+ Local-first playground
+
+
+
+
+
+ ${this.renderServiceChip(
+ getServiceLabel("core", this.coreStatus),
+ this.coreStatus,
+ )}
+ ${this.renderServiceChip(
+ getServiceLabel("lsp", this.lspStatus),
+ this.lspStatus,
+ )}
+
+
+
+
+ ${this.isBusy
+ ? html`
+
+
+
+ `
+ : html`
+
+
+
+ `}
+
+ ${this.isBusy ? "Running..." : "Run"}
+ Cmd/Ctrl + Enter
+
+
+
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "yara-status-bar": YaraStatusBar;
+ }
+}
diff --git a/playground/src/controllers/split-resize-controller.ts b/playground/src/controllers/split-resize-controller.ts
new file mode 100644
index 000000000..8ac867de7
--- /dev/null
+++ b/playground/src/controllers/split-resize-controller.ts
@@ -0,0 +1,76 @@
+import type { ReactiveController, ReactiveControllerHost } from "lit";
+
+type SplitAxis = "x" | "y";
+
+type SplitResizeConfig = {
+ getElement: () => HTMLElement | null;
+ getAxis: () => SplitAxis;
+ min: number;
+ max: number;
+ onChange: (value: number) => void;
+};
+
+export class SplitResizeController implements ReactiveController {
+ private cleanup?: () => void;
+ private readonly config: SplitResizeConfig;
+
+ constructor(host: ReactiveControllerHost, config: SplitResizeConfig) {
+ this.config = config;
+ host.addController(this);
+ }
+
+ hostDisconnected() {
+ this.stop();
+ }
+
+ readonly start = (event: PointerEvent) => {
+ event.preventDefault();
+ this.updateFromPointer(event);
+ document.body.style.userSelect = "none";
+ document.body.style.cursor =
+ this.config.getAxis() === "y" ? "row-resize" : "col-resize";
+
+ const onMove = (moveEvent: PointerEvent) => {
+ this.updateFromPointer(moveEvent);
+ };
+
+ const onUp = () => {
+ this.stop();
+ };
+
+ window.addEventListener("pointermove", onMove);
+ window.addEventListener("pointerup", onUp);
+ this.cleanup = () => {
+ window.removeEventListener("pointermove", onMove);
+ window.removeEventListener("pointerup", onUp);
+ document.body.style.userSelect = "";
+ document.body.style.cursor = "";
+ this.cleanup = undefined;
+ };
+ };
+
+ private updateFromPointer(event: PointerEvent) {
+ const element = this.config.getElement();
+
+ if (!element) return;
+
+ const rect = element.getBoundingClientRect();
+ const size = this.config.getAxis() === "y" ? rect.height : rect.width;
+
+ if (size <= 0) return;
+
+ const offset =
+ this.config.getAxis() === "y"
+ ? event.clientY - rect.top
+ : event.clientX - rect.left;
+ const next = (offset / size) * 100;
+
+ this.config.onChange(
+ Math.min(this.config.max, Math.max(this.config.min, next)),
+ );
+ }
+
+ private stop() {
+ this.cleanup?.();
+ }
+}
diff --git a/playground/src/data/sample.ts b/playground/src/data/sample.ts
new file mode 100644
index 000000000..8e04ef42d
--- /dev/null
+++ b/playground/src/data/sample.ts
@@ -0,0 +1,14 @@
+// This content is shown by default when the playground first opens in the browser
+export const DEFAULT_SAMPLE_RULE = `rule suspicious_download_example {
+ meta:
+ author = "YARA-X Playground"
+ description = "Example rule used in the playground"
+ strings:
+ $a = "powershell" nocase
+ $b = "download" nocase
+ $c = "http://" nocase
+ condition:
+ 2 of them
+}`;
+
+export const DEFAULT_SAMPLE_INPUT = `powershell -command download http://example.com/payload`;
diff --git a/playground/src/editor/yara-monaco.ts b/playground/src/editor/yara-monaco.ts
new file mode 100644
index 000000000..67a20042b
--- /dev/null
+++ b/playground/src/editor/yara-monaco.ts
@@ -0,0 +1,337 @@
+import "@codingame/monaco-vscode-editor-api/esm/vs/editor/contrib/format/browser/formatActions.js";
+
+import * as monaco from "@codingame/monaco-vscode-editor-api";
+
+import { MonacoVscodeApiWrapper } from "monaco-languageclient/vscodeApiWrapper";
+import { configureDefaultWorkerFactory } from "monaco-languageclient/workerFactory";
+import { createModelReference } from "@codingame/monaco-vscode-api/monaco";
+
+const RULE_URI = monaco.Uri.file("/workspace/main.yar");
+const SAMPLE_URI = monaco.Uri.file("/workspace/sample.txt");
+const THEME_NAME = "yara-studio";
+
+export const YARA_CONFIG = {
+ codeFormatting: {
+ alignMetadata: true,
+ alignPatterns: true,
+ indentSectionHeaders: true,
+ indentSectionContents: true,
+ newlineBeforeCurlyBrace: false,
+ emptyLineBeforeSectionHeader: false,
+ emptyLineAfterSectionHeader: false,
+ },
+ metadataValidation: [],
+ ruleNameValidation: null,
+ cacheWorkspace: false,
+};
+
+const YARA_KEYWORDS = [
+ "rule",
+ "meta",
+ "strings",
+ "condition",
+ "private",
+ "global",
+ "import",
+];
+
+const YARA_OPERATORS = [
+ "all",
+ "and",
+ "any",
+ "ascii",
+ "at",
+ "base64",
+ "base64wide",
+ "contains",
+ "entrypoint",
+ "false",
+ "filesize",
+ "for",
+ "fullword",
+ "in",
+ "matches",
+ "nocase",
+ "none",
+ "not",
+ "of",
+ "or",
+ "them",
+ "true",
+ "wide",
+ "xor",
+];
+
+export type EditorHandle = {
+ editor: monaco.editor.IStandaloneCodeEditor;
+ getValue: () => string;
+ setValue: (value: string) => void;
+ format: () => Promise;
+ dispose: () => void;
+};
+
+let vscodeApiInitPromise: Promise | undefined;
+let themeRegistered = false;
+
+function registerStudioTheme() {
+ if (themeRegistered) return;
+
+ monaco.editor.defineTheme(THEME_NAME, {
+ base: "vs-dark",
+ inherit: true,
+ rules: [
+ { token: "keyword", foreground: "e9c46a" },
+ { token: "variable", foreground: "5ce1e6" },
+ { token: "identifier", foreground: "d6e6f2" },
+ { token: "string", foreground: "7ce8a2" },
+ { token: "number", foreground: "ffa770" },
+ { token: "comment", foreground: "547282" },
+ ],
+ colors: {
+ "editor.background": "#04111b",
+ "editor.lineHighlightBackground": "#0b2230",
+ "editor.foreground": "#d8e5ef",
+ "editorCursor.foreground": "#1ee3cf",
+ "editorLineNumber.foreground": "#527084",
+ "editorLineNumber.activeForeground": "#8fb7c8",
+ "editor.selectionBackground": "#12374b",
+ "editor.inactiveSelectionBackground": "#0d2c3d",
+ "editorIndentGuide.background1": "#0b2330",
+ "editorIndentGuide.activeBackground1": "#1f5665",
+ "editorWidget.background": "#071825",
+ "editorWidget.border": "#133141",
+ },
+ });
+
+ themeRegistered = true;
+}
+
+async function ensureVscodeApi() {
+ vscodeApiInitPromise ??= (async () => {
+ const apiWrapper = new MonacoVscodeApiWrapper({
+ $type: "classic",
+ viewsConfig: { $type: "EditorService" },
+ userConfiguration: {
+ json: JSON.stringify({
+ "editor.colorDecorators": false,
+ }),
+ },
+ advanced: {
+ loadThemes: false,
+ loadExtensionServices: false,
+ },
+ monacoWorkerFactory: configureDefaultWorkerFactory,
+ });
+
+ await apiWrapper.start();
+ registerStudioTheme();
+ monaco.editor.setTheme(THEME_NAME);
+ })();
+
+ await vscodeApiInitPromise;
+}
+
+function registerYaraLanguage() {
+ if (monaco.languages.getLanguages().some((lang) => lang.id === "yara"))
+ return;
+
+ monaco.languages.register({
+ id: "yara",
+ extensions: [".yar", ".yara"],
+ aliases: ["YARA", "yara"],
+ });
+
+ monaco.languages.setLanguageConfiguration("yara", {
+ comments: {
+ lineComment: "//",
+ blockComment: ["/*", "*/"],
+ },
+ brackets: [
+ ["{", "}"],
+ ["[", "]"],
+ ["(", ")"],
+ ],
+ autoClosingPairs: [
+ { open: "{", close: "}" },
+ { open: "[", close: "]" },
+ { open: "(", close: ")" },
+ { open: '"', close: '"', notIn: ["string"] },
+ { open: "/*", close: " */", notIn: ["string"] },
+ ],
+ surroundingPairs: [
+ { open: "{", close: "}" },
+ { open: "[", close: "]" },
+ { open: "(", close: ")" },
+ { open: '"', close: '"' },
+ ],
+ });
+
+ monaco.languages.setMonarchTokensProvider("yara", {
+ keywords: YARA_KEYWORDS,
+ operators: YARA_OPERATORS,
+ tokenizer: {
+ root: [
+ [/\$[a-zA-Z_]\w*/, "variable"],
+ [
+ /[a-zA-Z_][\w]*/,
+ {
+ cases: {
+ "@keywords": "keyword",
+ "@operators": "keyword",
+ "@default": "identifier",
+ },
+ },
+ ],
+ [/\/\*/, "comment", "@comment"],
+ [/\/\/.*$/, "comment"],
+ [/"([^"\\]|\\.)*$/, "string.invalid"],
+ [/"/, { token: "string.quote", bracket: "@open", next: "@string" }],
+ [/\b\d+(?:\.\d+)?\b/, "number"],
+ [/[{}()[\]]/, "@brackets"],
+ [/[=> {
+ const action = editor.getAction("editor.action.formatDocument");
+ await action?.run();
+ },
+ });
+}
+
+function toHandle(
+ editor: monaco.editor.IStandaloneCodeEditor,
+ modelRef: { dispose: () => void },
+ extraDispose?: () => void,
+): EditorHandle {
+ return {
+ editor,
+ getValue: () => editor.getValue(),
+ setValue: (value) => editor.setValue(value),
+ format: async () => {
+ const action = editor.getAction("editor.action.formatDocument");
+ if (!action) return false;
+ await action.run();
+ return true;
+ },
+ dispose: () => {
+ extraDispose?.();
+ editor.dispose();
+ modelRef.dispose();
+ },
+ };
+}
+
+export async function createYaraEditor(
+ element: HTMLElement,
+ initialValue: string,
+): Promise {
+ await ensureVscodeApi();
+ registerYaraLanguage();
+
+ const { modelRef, model } = await createEditorModel(
+ RULE_URI,
+ initialValue,
+ "yara",
+ );
+ const editor = buildEditor(element, model, {
+ quickSuggestions: true,
+ suggestOnTriggerCharacters: true,
+ tabSize: 2,
+ insertSpaces: true,
+ });
+ const editorAction = registerYaraEditorActions(editor);
+
+ // TODO(@kevinmuoz): Wire the browser language server package here once the
+ // upstream worker entrypoint and distribution strategy are settled.
+
+ return toHandle(editor, modelRef, () => {
+ editorAction.dispose();
+ });
+}
+
+export async function createPlainTextEditor(
+ element: HTMLElement,
+ initialValue: string,
+): Promise {
+ await ensureVscodeApi();
+
+ const { modelRef, model } = await createEditorModel(
+ SAMPLE_URI,
+ initialValue,
+ "plaintext",
+ );
+ const editor = buildEditor(element, model, {
+ lineNumbers: "off",
+ glyphMargin: false,
+ folding: false,
+ wordWrap: "on",
+ tabSize: 2,
+ insertSpaces: true,
+ quickSuggestions: false,
+ suggestOnTriggerCharacters: false,
+ });
+
+ return toHandle(editor, modelRef);
+}
diff --git a/playground/src/main.ts b/playground/src/main.ts
new file mode 100644
index 000000000..f4207864e
--- /dev/null
+++ b/playground/src/main.ts
@@ -0,0 +1,3 @@
+import "./styles/base.css";
+import "./styles/playground.css";
+import "./app/yara-playground-app";
diff --git a/playground/src/results/summarize-result.ts b/playground/src/results/summarize-result.ts
new file mode 100644
index 000000000..d994c427b
--- /dev/null
+++ b/playground/src/results/summarize-result.ts
@@ -0,0 +1,144 @@
+import type { ResultSummary, RuleSummary } from "../types/result-summary";
+
+type MaybeObject = Record;
+
+type PatternMatch = {
+ range?: {
+ start?: number;
+ end?: number;
+ };
+};
+
+function asObject(value: unknown): MaybeObject {
+ return value && typeof value === "object" ? (value as MaybeObject) : {};
+}
+
+function asArray(value: unknown): T[] {
+ return Array.isArray(value) ? (value as T[]) : [];
+}
+
+function formatIssue(value: unknown): string {
+ if (typeof value === "string") return value;
+ if (value instanceof Error) return value?.message;
+
+ const object = asObject(value);
+ if (typeof object.text === "string") return object?.text;
+ if (typeof object.title === "string") return object?.title;
+
+ try {
+ return JSON.stringify(value);
+ } catch {
+ return String(value);
+ }
+}
+
+function formatRange(match: unknown): string | null {
+ const matchObject = asObject(match);
+ const range = asObject(matchObject.range);
+ const start = range?.start;
+ const end = range?.end;
+
+ if (typeof start === "number" && typeof end === "number") {
+ return `${start}-${end}`;
+ }
+
+ const offset = matchObject.offset;
+ const length = matchObject.length;
+
+ if (typeof offset !== "number" || typeof length !== "number") {
+ return null;
+ }
+
+ return `${offset}-${offset + length}`;
+}
+
+function summarizeRule(rule: unknown): RuleSummary {
+ const object = asObject(rule);
+ const patterns = asArray(object.patterns).map((pattern) => {
+ const patternObject = asObject(pattern);
+ const matches = asArray(patternObject.matches);
+ const ranges = matches
+ .map(formatRange)
+ .filter((value): value is string => Boolean(value));
+
+ return {
+ identifier:
+ typeof patternObject.identifier === "string"
+ ? patternObject.identifier
+ : "unknown",
+ hits: matches.length,
+ ranges,
+ };
+ });
+
+ return {
+ identifier:
+ typeof object.identifier === "string"
+ ? object.identifier
+ : "unknown_rule",
+ namespace:
+ typeof object.namespace === "string" ? object.namespace : "default",
+ hits: patterns.reduce((sum, pattern) => sum + pattern.hits, 0),
+ patterns,
+ };
+}
+
+export function summarizeResult(
+ raw: unknown,
+ mode: "validate" | "scan",
+): ResultSummary {
+ const object = asObject(raw);
+ const errorsList = asArray(object.errors).map(formatIssue);
+ const warningsList = asArray(object.warnings).map(formatIssue);
+ const matchingRules = asArray(object.matching_rules ?? object.matches).map(
+ summarizeRule,
+ );
+ const nonMatchingRules = asArray(object.non_matching_rules).map(
+ (rule) => {
+ const ruleObject = asObject(rule);
+ return typeof ruleObject.identifier === "string"
+ ? ruleObject.identifier
+ : "unknown_rule";
+ },
+ );
+ const hitCount = matchingRules.reduce((sum, rule) => sum + rule.hits, 0);
+
+ let headline = "Ready to run a rule.";
+ let tone: ResultSummary["tone"] = "idle";
+
+ if (mode === "validate") {
+ if (errorsList.length > 0) {
+ headline = `Validation found ${errorsList.length} error(s).`;
+ tone = "issues";
+ } else if (warningsList.length > 0) {
+ headline = `Validation completed with ${warningsList.length} warning(s).`;
+ tone = "issues";
+ } else {
+ headline = "Rule validation passed without issues.";
+ tone = "clean";
+ }
+ } else if (errorsList.length > 0) {
+ headline = `Scan finished with ${errorsList.length} error(s).`;
+ tone = "issues";
+ } else if (matchingRules.length > 0) {
+ headline = `Matched ${matchingRules.length} rule(s) with ${hitCount} pattern hit(s).`;
+ tone = "match";
+ } else {
+ headline = "Scan completed with no matching rules.";
+ tone = warningsList.length > 0 ? "issues" : "clean";
+ }
+
+ return {
+ errors: errorsList.length,
+ warnings: warningsList.length,
+ matches: matchingRules.length,
+ nonMatches: nonMatchingRules.length,
+ hitCount,
+ headline,
+ tone,
+ errorsList,
+ warningsList,
+ matchingRules,
+ nonMatchingRules,
+ };
+}
diff --git a/playground/src/services/wasm-yara-engine.ts b/playground/src/services/wasm-yara-engine.ts
new file mode 100644
index 000000000..486cfb813
--- /dev/null
+++ b/playground/src/services/wasm-yara-engine.ts
@@ -0,0 +1,60 @@
+import type { YaraCompiler, YaraEngine } from "./yara-engine";
+
+function notImplemented(message: string): never {
+ throw new Error(message);
+}
+
+function compilerTodo(): YaraCompiler {
+ return {
+ addSource: (_source) => {
+ notImplemented(
+ `TODO(@kevinmuoz): wire "Compiler.addSource" to the official "yara-x-wasm" npm package once PR #598 is merged and published.`,
+ );
+ },
+ newNamespace: (_namespace) => {
+ notImplemented(
+ `TODO(@kevinmuoz): wire "Compiler.newNamespace" to the official "yara-x-wasm" npm package once PR #598 is merged and published.`,
+ );
+ },
+ defineGlobal: (_identifier, _value) => {
+ notImplemented(
+ `TODO(@kevinmuoz): wire "Compiler.defineGlobal" to the official "yara-x-wasm" npm package once PR #598 is merged and published.`,
+ );
+ },
+ errors: () => {
+ notImplemented(
+ `TODO(@kevinmuoz): expose "Compiler.errors()" from the official "yara-x-wasm" npm package once PR #598 is merged and published.`,
+ );
+ },
+ warnings: () => {
+ notImplemented(
+ `TODO(@kevinmuoz): expose "Compiler.warnings()" from the official "yara-x-wasm" npm package once PR #598 is merged and published.`,
+ );
+ },
+ build: () => {
+ notImplemented(
+ `TODO(@kevinmuoz): wire "Compiler.build()" to the official "yara-x-wasm" npm package once PR #598 is merged and published.`,
+ );
+ },
+ };
+}
+
+export function getWasmYaraEngine(): YaraEngine {
+ return {
+ async createCompiler() {
+ /**
+ * TODO(@kevinmuoz): I'll Replace this local placeholder with an adapter around
+ * the upstream `yara-x-wasm` npm package once PR #598 lands
+ *
+ * Possible future config:
+ *
+ * import initYara, { Compiler } from "yara-x-wasm";
+ *
+ * await initYara();
+ * const compiler = new Compiler();
+ *
+ */
+ return compilerTodo();
+ },
+ };
+}
diff --git a/playground/src/services/wasm-yara-language-server.ts b/playground/src/services/wasm-yara-language-server.ts
new file mode 100644
index 000000000..f7f9d9afb
--- /dev/null
+++ b/playground/src/services/wasm-yara-language-server.ts
@@ -0,0 +1,23 @@
+import type { YaraLanguageServer } from "./yara-language-server";
+
+export function getWasmYaraLanguageServer(): YaraLanguageServer {
+ return {
+ async createWorker() {
+ /**
+ * TODO(@kevinmuoz): I'll Replace this placeholder once the browser
+ * worker entrypoint for "yara-x-ls" is upstream and we agree on how it
+ * should be distributed to web consumers. "createWorker()"" must keep
+ * resolving only once the worker is ready to accept LSP traffic
+ *
+ * Possible future config:
+ *
+ * import { createWorker } from "@virustotal/yara-x-ls-web";
+ *
+ * const worker = await createWorker();
+ */
+ throw new Error(
+ "TODO(@kevinmuoz): wire the browser language-server package once the upstream worker entrypoint is published.",
+ );
+ },
+ };
+}
diff --git a/playground/src/services/yara-engine.ts b/playground/src/services/yara-engine.ts
new file mode 100644
index 000000000..0c09fad73
--- /dev/null
+++ b/playground/src/services/yara-engine.ts
@@ -0,0 +1,48 @@
+/**
+ * Playground-facing contract for the browser engine integration.
+ *
+ * TODO(@kevinmuoz): Replace the local adapter with the official `yara-x-wasm`
+ * npm package once PR #598 is merged and published.
+ *
+ * Expected upstream usage:
+ *
+ * import initYara, { Compiler, Scanner } from "yara-x-wasm";
+ *
+ * await initYara();
+ *
+ * const compiler = new Compiler();
+ * compiler.addSource(rule);
+ *
+ * const rules = compiler.build();
+ * const scanner = new Scanner(rules);
+ * const result = scanner.scan(bytes);
+ *
+ */
+
+export type YaraEngineScanResult = unknown;
+
+export interface YaraEngine {
+ createCompiler(): Promise;
+}
+
+export interface YaraCompiler {
+ addSource(source: string): void;
+ newNamespace(namespace: string): void;
+ defineGlobal(identifier: string, value: unknown): void;
+ errors(): string[];
+ warnings(): string[];
+ build(): YaraRules;
+}
+
+export interface YaraRules {
+ scan(payload: Uint8Array): YaraEngineScanResult;
+ scanner(): YaraScanner;
+ warnings(): string[];
+}
+
+export interface YaraScanner {
+ setTimeoutMs(timeoutMs: number): void;
+ setMaxMatchesPerPattern(limit: number): void;
+ setGlobal(identifier: string, value: unknown): void;
+ scan(payload: Uint8Array): YaraEngineScanResult;
+}
diff --git a/playground/src/services/yara-language-server.ts b/playground/src/services/yara-language-server.ts
new file mode 100644
index 000000000..0c3822f28
--- /dev/null
+++ b/playground/src/services/yara-language-server.ts
@@ -0,0 +1,21 @@
+/**
+ * Playground-facing contract for the browser language server integration.
+ *
+ * TODO(@kevinmuoz): Confirm with Victor whether the browser language server
+ * should be published as a separate npm package or bundled only as part of the
+ * official playground. Keep this interface Monaco-agnostic either way.
+ *
+ * Possible future config:
+ *
+ * const languageServer = getYaraLanguageServer();
+ * const worker = await languageServer.createWorker();
+ *
+ */
+export interface YaraLanguageServer {
+ /**
+ * Returns a ready-to-use worker that can be connected directly to an LSP
+ * client. Any package-specific bootstrap or readiness handshake should stay
+ * hidden behind this contract.
+ */
+ createWorker(): Promise;
+}
diff --git a/playground/src/styles/base.css b/playground/src/styles/base.css
new file mode 100644
index 000000000..679eb37a3
--- /dev/null
+++ b/playground/src/styles/base.css
@@ -0,0 +1,33 @@
+:root {
+ color-scheme: light;
+ background: #161625;
+ font-family: "Jost", "Avenir Next", "IBM Plex Sans", "Segoe UI", sans-serif;
+ font-optical-sizing: auto;
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+html,
+body {
+ margin: 0;
+ min-width: 320px;
+ min-height: 100svh;
+}
+
+body {
+ overflow: hidden;
+ color: #f5f7ff;
+ background: #161625;
+}
+
+@media (max-width: 900px) {
+ body {
+ overflow: auto;
+ }
+}
diff --git a/playground/src/styles/playground.css b/playground/src/styles/playground.css
new file mode 100644
index 000000000..cb6fd6e21
--- /dev/null
+++ b/playground/src/styles/playground.css
@@ -0,0 +1,918 @@
+yara-playground-app {
+ display: block;
+ min-height: 100svh;
+ color: #f5f7ff;
+}
+
+yara-status-bar {
+ display: block;
+}
+
+yara-file,
+yara-result-panel {
+ display: block;
+ min-width: 0;
+ min-height: 0;
+}
+
+yara-file > .pane,
+yara-result-panel > .results-region {
+ height: 100%;
+}
+
+yara-playground-app button,
+yara-playground-app input {
+ font: inherit;
+}
+
+yara-playground-app h2,
+yara-playground-app h3,
+yara-playground-app p {
+ margin: 0;
+}
+
+.studio {
+ width: 100%;
+ height: 100svh;
+ display: grid;
+ grid-template-rows: auto minmax(0, 1fr);
+ background: #161625;
+ overflow: hidden;
+}
+
+.topbar,
+.results-head {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+}
+
+.pane-head {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ min-width: 0;
+}
+
+.topbar {
+ min-height: 52px;
+ position: relative;
+ z-index: 10;
+ padding: 9px 14px;
+ border-bottom: 1px solid rgba(134, 170, 249, 0.18);
+ background: rgba(22, 22, 37, 0.98);
+}
+
+.brand,
+.topbar-actions,
+.status-row,
+.sample-head-actions,
+.results-controls,
+.result-row,
+.pattern-row,
+.token-row {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.brand {
+ min-width: 0;
+}
+
+.topbar-actions {
+ margin-left: auto;
+ justify-content: flex-end;
+ flex-wrap: wrap;
+}
+
+.brand-mark {
+ width: 34px;
+ height: 34px;
+ display: grid;
+ place-items: center;
+ flex: 0 0 auto;
+ border-radius: 10px;
+ background: linear-gradient(
+ 135deg,
+ rgba(134, 170, 249, 0.22),
+ rgba(77, 222, 214, 0.22)
+ );
+ color: #eef4ff;
+ border: 1px solid rgba(134, 170, 249, 0.22);
+}
+
+.brand-mark-icon {
+ width: 24px;
+ height: auto;
+ display: block;
+}
+
+.brand-copy,
+.pane-head-copy {
+ display: grid;
+ gap: 2px;
+ min-width: 0;
+}
+
+.pane-head-copy {
+ flex: 1 1 auto;
+}
+
+.brand-copy strong,
+yara-playground-app h2,
+yara-playground-app h3 {
+ color: #eef2ff;
+ font-weight: 650;
+ letter-spacing: -0.02em;
+}
+
+.brand-copy strong {
+ font-size: 0.88rem;
+ line-height: 1.1;
+}
+
+.brand-copy span {
+ font-size: 0.74rem;
+ line-height: 1.1;
+}
+
+yara-playground-app h2 {
+ font-size: 0.9rem;
+ line-height: 1.15;
+}
+
+.brand-copy span,
+.eyebrow,
+.muted,
+.stat span,
+.section-heading,
+yara-playground-app code {
+ color: #86aaf9;
+}
+
+.eyebrow,
+.section-heading {
+ font-size: 0.76rem;
+ text-transform: uppercase;
+ letter-spacing: 0.12em;
+ font-weight: 700;
+}
+
+yara-playground-app code {
+ font-family: "IBM Plex Mono", ui-monospace, monospace;
+ font-size: 0.84rem;
+}
+
+.service-chip,
+.duration-chip,
+.hit-pill,
+.token {
+ display: inline-flex;
+ align-items: center;
+ border-radius: 999px;
+ border: 1px solid rgba(134, 170, 249, 0.18);
+ background: rgba(255, 255, 255, 0.03);
+ color: #cad7ff;
+ padding: 0.32rem 0.65rem;
+ line-height: 1;
+ font-size: 0.75rem;
+ white-space: nowrap;
+}
+
+.service-chip.ready,
+.hit-pill.match {
+ color: #55e6b0;
+ border-color: rgba(85, 230, 176, 0.22);
+ background: rgba(85, 230, 176, 0.09);
+}
+
+.service-chip.loading {
+ color: #ffcf70;
+ border-color: rgba(255, 207, 112, 0.22);
+ background: rgba(255, 207, 112, 0.09);
+}
+
+.service-chip.error,
+.hit-pill.issues {
+ color: #ff8b9b;
+ border-color: rgba(255, 139, 155, 0.22);
+ background: rgba(255, 139, 155, 0.08);
+}
+
+.hit-pill.clean {
+ color: #86aaf9;
+ background: rgba(134, 170, 249, 0.1);
+}
+
+.run-button {
+ position: relative;
+ z-index: 1;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.62rem;
+ appearance: none;
+ border: 1px solid rgba(134, 170, 249, 0.16);
+ border-radius: 999px;
+ padding: 0.42rem 0.54rem 0.42rem 0.48rem;
+ font-weight: 700;
+ color: #eef4ff;
+ background: rgba(255, 255, 255, 0.04);
+ box-shadow: 0 10px 24px rgba(8, 12, 24, 0.18);
+ cursor: pointer;
+ transition:
+ transform 180ms ease,
+ opacity 180ms ease,
+ box-shadow 180ms ease,
+ background 180ms ease,
+ border-color 180ms ease;
+}
+
+.run-button:hover:not(:disabled) {
+ transform: translateY(-1px);
+ background: rgba(255, 255, 255, 0.06);
+ border-color: rgba(134, 170, 249, 0.28);
+ box-shadow: 0 16px 34px rgba(8, 12, 24, 0.24);
+}
+
+.run-button:disabled {
+ opacity: 0.45;
+ cursor: not-allowed;
+ box-shadow: none;
+}
+
+.run-icon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 1.85rem;
+ height: 1.85rem;
+ flex: 0 0 auto;
+ border-radius: 999px;
+ background: linear-gradient(135deg, #86aaf9, #92f2df);
+ color: #0d1320;
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18);
+}
+
+.run-icon svg {
+ width: 0.95rem;
+ height: 0.95rem;
+}
+
+.spinner {
+ animation: spin 0.9s linear infinite;
+}
+
+.run-tooltip {
+ position: absolute;
+ left: 50%;
+ top: calc(100% + 10px);
+ transform: translateX(-50%) translateY(-4px);
+ border-radius: 999px;
+ border: 1px solid rgba(134, 170, 249, 0.16);
+ background: rgba(22, 22, 37, 0.98);
+ color: #dbe5ff;
+ padding: 0.38rem 0.6rem;
+ font-size: 0.74rem;
+ line-height: 1;
+ white-space: nowrap;
+ box-shadow: 0 12px 32px rgba(8, 12, 24, 0.26);
+ opacity: 0;
+ pointer-events: none;
+ z-index: 12;
+ transition:
+ opacity 160ms ease,
+ transform 160ms ease;
+}
+
+.run-button:hover .run-tooltip,
+.run-button:focus-visible .run-tooltip {
+ opacity: 1;
+ transform: translateX(-50%) translateY(0);
+}
+
+.workspace {
+ min-height: 0;
+ display: grid;
+ grid-template-rows:
+ minmax(260px, var(--workspace-split))
+ 12px
+ minmax(180px, calc(100% - var(--workspace-split) - 12px));
+ background: #161625;
+}
+
+.editor-region {
+ min-height: 0;
+ display: grid;
+ grid-template-columns:
+ minmax(0, var(--editor-split))
+ 12px
+ minmax(0, calc(100% - var(--editor-split) - 12px));
+}
+
+.pane,
+.results-region {
+ min-width: 0;
+ min-height: 0;
+ display: grid;
+ grid-template-rows: auto minmax(0, 1fr);
+ background: #161625;
+ overflow: hidden;
+}
+
+.sample-pane {
+ position: relative;
+}
+
+.pane-head,
+.results-head {
+ min-height: 50px;
+ width: 100%;
+ max-width: 100%;
+ min-width: 0;
+ box-sizing: border-box;
+ padding: 8px 14px;
+ border-bottom: 1px solid rgba(134, 170, 249, 0.14);
+ background: rgba(255, 255, 255, 0.01);
+}
+
+.pane-head h2 {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.editor-shell {
+ min-height: 0;
+ overflow: hidden;
+ background: #04111b;
+}
+
+.sample-pane-controls {
+ position: absolute;
+ top: 0;
+ right: 16px;
+ height: 50px;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ z-index: 4;
+ max-width: calc(100% - 32px);
+}
+
+.sample-pane-shell {
+ min-height: 0;
+ position: relative;
+ background: #04111b;
+}
+
+.sample-pane-shell.is-drag-active .file-state-shell {
+ background:
+ radial-gradient(
+ circle at top left,
+ rgba(146, 242, 223, 0.16),
+ transparent 34%
+ ),
+ rgba(4, 17, 27, 0.96);
+}
+
+.sample-pane-shell.is-drag-active .file-state {
+ border-color: rgba(146, 242, 223, 0.42);
+ background: rgba(146, 242, 223, 0.06);
+ transform: scale(1.01);
+}
+
+.sample-pane-shell .editor-shell {
+ height: 100%;
+}
+
+.sample-pane-shell.is-file-mode .editor-shell {
+ visibility: hidden;
+ pointer-events: none;
+}
+
+.file-state-shell {
+ position: absolute;
+ inset: 0;
+ display: none;
+ padding: 18px;
+ overflow: auto;
+ background:
+ radial-gradient(
+ circle at top left,
+ rgba(134, 170, 249, 0.07),
+ transparent 30%
+ ),
+ #04111b;
+}
+
+.sample-pane-shell.is-file-mode .file-state-shell {
+ display: block;
+}
+
+.file-state {
+ width: min(520px, 100%);
+ min-height: 100%;
+ min-width: 0;
+ max-width: 100%;
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ gap: 18px;
+ color: #e6edff;
+ margin: 0 auto;
+ padding: 28px 22px;
+ border: 1px dashed rgba(134, 170, 249, 0.18);
+ border-radius: 24px;
+ background: rgba(255, 255, 255, 0.015);
+ text-align: center;
+ transition:
+ border-color 160ms ease,
+ background 160ms ease,
+ transform 160ms ease;
+}
+
+.file-state-copy {
+ display: grid;
+ gap: 8px;
+ min-width: 0;
+ justify-items: center;
+}
+
+.file-state-copy h3,
+.file-state-copy p {
+ overflow-wrap: anywhere;
+}
+
+.file-state-copy p {
+ max-width: 42ch;
+ color: #86aaf9;
+ line-height: 1.55;
+}
+
+.file-state-actions {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ flex-wrap: wrap;
+}
+
+.secondary-button,
+.ghost-button {
+ appearance: none;
+ border-radius: 999px;
+ padding: 0.7rem 1rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition:
+ background 160ms ease,
+ border-color 160ms ease,
+ transform 160ms ease;
+}
+
+.secondary-button {
+ border: 1px solid rgba(134, 170, 249, 0.2);
+ background: rgba(134, 170, 249, 0.12);
+ color: #f5f7ff;
+}
+
+.ghost-button {
+ border: 1px solid rgba(134, 170, 249, 0.12);
+ background: rgba(255, 255, 255, 0.02);
+ color: #86aaf9;
+}
+
+.secondary-button:hover,
+.ghost-button:hover {
+ transform: translateY(-1px);
+}
+
+.editor-shell .monaco-editor textarea.inputarea,
+.editor-shell .monaco-editor .inputarea {
+ opacity: 0 !important;
+ background: transparent !important;
+ border: 0 !important;
+ box-shadow: none !important;
+}
+
+.editor-shell .monaco-editor .suggest-widget,
+.editor-shell .monaco-editor .parameter-hints-widget,
+.editor-shell .monaco-editor .monaco-hover,
+.editor-shell .monaco-editor .monaco-editor-hover,
+.editor-shell .monaco-editor .rename-box,
+.editor-shell .monaco-editor .zone-widget,
+.editor-shell .monaco-editor .peekview-widget,
+.overflowingContentWidgets,
+.overflowingOverlayWidgets,
+.context-view,
+.monaco-menu-container {
+ z-index: 40 !important;
+}
+
+.editor-divider,
+.workspace-divider {
+ position: relative;
+ background: rgba(255, 255, 255, 0.02);
+ touch-action: none;
+ z-index: 5;
+}
+
+.editor-divider {
+ cursor: col-resize;
+ border-inline: 1px solid rgba(134, 170, 249, 0.1);
+}
+
+.workspace-divider {
+ cursor: row-resize;
+ border-block: 1px solid rgba(134, 170, 249, 0.1);
+}
+
+.divider-handle {
+ position: absolute;
+ inset: 50% auto auto 50%;
+ width: 4px;
+ height: 64px;
+ transform: translate(-50%, -50%);
+ border-radius: 999px;
+ background: linear-gradient(
+ 180deg,
+ rgba(134, 170, 249, 0.2),
+ rgba(134, 170, 249, 0.7),
+ rgba(134, 170, 249, 0.2)
+ );
+}
+
+.divider-handle.horizontal {
+ width: 84px;
+ height: 4px;
+ background: linear-gradient(
+ 90deg,
+ rgba(134, 170, 249, 0.2),
+ rgba(134, 170, 249, 0.7),
+ rgba(134, 170, 249, 0.2)
+ );
+}
+
+.results-region {
+ overflow: hidden;
+}
+
+.results-body {
+ min-height: 0;
+ overflow: auto;
+ padding: 14px 16px 18px;
+ display: grid;
+ gap: 14px;
+}
+
+.headline-strip,
+.stats-strip,
+.result-section,
+.raw-output {
+ border: 1px solid rgba(134, 170, 249, 0.12);
+ background: rgba(255, 255, 255, 0.03);
+}
+
+.headline-strip,
+.result-section,
+.raw-output {
+ border-radius: 20px;
+}
+
+.headline-strip {
+ padding: 16px 18px;
+}
+
+.headline-strip.clean {
+ background: rgba(134, 170, 249, 0.08);
+}
+
+.headline-strip.match {
+ background: rgba(85, 230, 176, 0.08);
+ border-color: rgba(85, 230, 176, 0.14);
+}
+
+.headline-strip.issues {
+ background: rgba(255, 139, 155, 0.07);
+ border-color: rgba(255, 139, 155, 0.14);
+}
+
+.headline-strip p {
+ color: #f4f7ff;
+}
+
+.metrics-grid {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+}
+
+.stat {
+ min-width: 120px;
+ flex: 1 1 calc(25% - 10px);
+ display: grid;
+ gap: 8px;
+ padding: 10px 12px;
+ border-radius: 16px;
+ background: rgba(255, 255, 255, 0.03);
+ border: 1px solid rgba(134, 170, 249, 0.08);
+}
+
+.stat strong {
+ font-size: 1.28rem;
+ line-height: 1;
+ color: #f4f7ff;
+}
+
+.result-section {
+ padding: 18px;
+}
+
+.issue-list {
+ margin: 12px 0 0;
+ padding-left: 18px;
+ color: #ffb8c1;
+ display: grid;
+ gap: 8px;
+}
+
+.match-list {
+ display: grid;
+ gap: 14px;
+ margin-top: 14px;
+}
+
+.match-item {
+ display: grid;
+ gap: 12px;
+ padding-top: 14px;
+ border-top: 1px solid rgba(134, 170, 249, 0.1);
+}
+
+.match-item:first-child {
+ padding-top: 0;
+ border-top: 0;
+}
+
+.pattern-list {
+ display: grid;
+ gap: 10px;
+}
+
+.pattern-row {
+ justify-content: space-between;
+ padding: 10px 12px;
+ border-radius: 16px;
+ background: rgba(255, 255, 255, 0.025);
+ border: 1px solid rgba(134, 170, 249, 0.08);
+}
+
+.token-row {
+ flex-wrap: wrap;
+ margin-top: 12px;
+}
+
+.empty-copy {
+ margin-top: 12px;
+ color: #86aaf9;
+}
+
+.mode-chip {
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+ border-radius: 999px;
+ border: 0;
+ background: transparent;
+ color: #86aaf9;
+ cursor: pointer;
+ overflow: hidden;
+}
+
+.mode-chip input {
+ position: absolute;
+ inset: 0;
+ opacity: 0;
+ pointer-events: none;
+}
+
+.mode-chip span {
+ display: block;
+ padding: 0.48rem 0.82rem;
+ line-height: 1;
+ position: relative;
+ z-index: 1;
+}
+
+.mode-chip.active {
+ background: rgba(134, 170, 249, 0.14);
+ color: #f4f7ff;
+}
+
+.mode-switch {
+ display: inline-flex;
+ align-items: center;
+ gap: 2px;
+ flex: 0 0 auto;
+ width: max-content;
+ padding: 3px;
+ border-radius: 999px;
+ border: 1px solid rgba(134, 170, 249, 0.16);
+ background: rgba(255, 255, 255, 0.03);
+}
+
+.sample-head-actions {
+ flex: 0 0 auto;
+ width: max-content;
+ min-width: max-content;
+ overflow: visible;
+}
+
+.pane-meta {
+ flex: 0 0 auto;
+ margin-left: auto;
+}
+
+.pane-meta {
+ color: #86aaf9;
+ font-family: "IBM Plex Mono", ui-monospace, monospace;
+ font-size: 0.8rem;
+ line-height: 1;
+}
+
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+.raw-output {
+ margin: 0;
+ padding: 18px;
+ overflow: auto;
+ font-family: "IBM Plex Mono", ui-monospace, monospace;
+ font-size: 0.87rem;
+ line-height: 1.65;
+ color: #e8edff;
+}
+
+@media (max-width: 900px) {
+ .studio {
+ min-height: 100svh;
+ height: auto;
+ overflow: auto;
+ }
+
+ .topbar {
+ flex-wrap: wrap;
+ padding: 14px;
+ }
+
+ .topbar-actions {
+ width: 100%;
+ justify-content: space-between;
+ }
+
+ .workspace {
+ grid-template-rows: auto auto;
+ align-content: start;
+ gap: 12px;
+ padding-bottom: 14px;
+ }
+
+ .editor-region {
+ grid-template-columns: 1fr;
+ grid-template-rows: auto auto;
+ gap: 12px;
+ }
+
+ .pane,
+ .results-region {
+ grid-template-rows: auto minmax(0, auto);
+ }
+
+ .editor-shell,
+ .sample-pane-shell,
+ .results-body {
+ min-height: 280px;
+ }
+
+ .editor-divider {
+ cursor: row-resize;
+ border-inline: 0;
+ border-block: 1px solid rgba(134, 170, 249, 0.1);
+ }
+
+ .editor-divider .divider-handle {
+ width: 84px;
+ height: 4px;
+ background: linear-gradient(
+ 90deg,
+ rgba(134, 170, 249, 0.2),
+ rgba(134, 170, 249, 0.7),
+ rgba(134, 170, 249, 0.2)
+ );
+ }
+
+ .editor-divider,
+ .workspace-divider {
+ display: none;
+ }
+
+ .sample-pane-controls {
+ position: static;
+ height: auto;
+ padding: 0 16px 10px;
+ max-width: 100%;
+ }
+
+ .sample-pane {
+ grid-template-rows: auto auto minmax(0, auto);
+ }
+
+ .file-state-shell {
+ padding: 16px;
+ }
+
+ .file-state {
+ padding: 24px 18px;
+ }
+
+ .pane-head,
+ .results-head {
+ flex-wrap: wrap;
+ }
+
+ .metrics-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+}
+
+@media (max-width: 640px) {
+ .run-tooltip {
+ left: auto;
+ right: 0;
+ transform: translateY(-4px);
+ max-width: calc(100vw - 28px);
+ }
+
+ .run-button:hover .run-tooltip,
+ .run-button:focus-visible .run-tooltip {
+ transform: translateY(0);
+ }
+
+ .topbar,
+ .results-head,
+ .result-row,
+ .pattern-row {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .status-row,
+ .results-controls {
+ flex-wrap: wrap;
+ }
+
+ .sample-pane-controls {
+ padding: 0 14px 10px;
+ }
+
+ .sample-pane {
+ grid-template-rows: auto auto minmax(0, 1fr);
+ }
+
+ .editor-shell,
+ .sample-pane-shell,
+ .results-body {
+ min-height: 240px;
+ }
+
+ .results-body,
+ .pane-head,
+ .results-head {
+ padding-inline: 14px;
+ }
+
+ .file-state-shell {
+ padding: 14px;
+ }
+
+ .file-state {
+ padding: 20px 16px;
+ border-radius: 20px;
+ }
+
+ .stat {
+ flex: 1 1 calc(50% - 10px);
+ }
+
+ .metrics-grid {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/playground/src/types/execution.ts b/playground/src/types/execution.ts
new file mode 100644
index 000000000..0d7727b7b
--- /dev/null
+++ b/playground/src/types/execution.ts
@@ -0,0 +1,16 @@
+import type { ResultSummary } from "./result-summary";
+
+export type ResultMode = "summary" | "raw";
+export type SampleMode = "text" | "file";
+
+export type LoadedSampleFile = {
+ name: string;
+ size: number;
+ bytes: Uint8Array;
+};
+
+export type ExecutionState = {
+ raw: unknown;
+ durationMs: number | null;
+ summary: ResultSummary;
+};
diff --git a/playground/src/types/result-summary.ts b/playground/src/types/result-summary.ts
new file mode 100644
index 000000000..b474be86f
--- /dev/null
+++ b/playground/src/types/result-summary.ts
@@ -0,0 +1,28 @@
+export type ResultTone = "idle" | "clean" | "match" | "issues";
+
+export type PatternSummary = {
+ identifier: string;
+ hits: number;
+ ranges: string[];
+};
+
+export type RuleSummary = {
+ identifier: string;
+ namespace: string;
+ hits: number;
+ patterns: PatternSummary[];
+};
+
+export type ResultSummary = {
+ errors: number;
+ warnings: number;
+ matches: number;
+ nonMatches: number;
+ hitCount: number;
+ headline: string;
+ tone: ResultTone;
+ errorsList: string[];
+ warningsList: string[];
+ matchingRules: RuleSummary[];
+ nonMatchingRules: string[];
+};
diff --git a/playground/src/types/service-status.ts b/playground/src/types/service-status.ts
new file mode 100644
index 000000000..746655b9a
--- /dev/null
+++ b/playground/src/types/service-status.ts
@@ -0,0 +1 @@
+export type ServiceStatus = "idle" | "loading" | "ready" | "error";
diff --git a/playground/src/workers/yara-ls.worker.ts b/playground/src/workers/yara-ls.worker.ts
new file mode 100644
index 000000000..214b811fc
--- /dev/null
+++ b/playground/src/workers/yara-ls.worker.ts
@@ -0,0 +1,18 @@
+async function main() {
+ /**
+ * TODO(@kevinmuoz): Replace this placeholder with the upstream browser
+ * worker package once the "yara-x-ls" worker entrypoint and packaging
+ * strategy are agreed.
+ *
+ * Possible future config:
+ *
+ * import initYaraLs, { runWorkerServer } from "@virustotal/yara-x-ls-web";
+ *
+ * await initYaraLs();
+ * runWorkerServer();
+ */
+}
+
+void main().catch((error) => {
+ console.error("failed to start yara-x-ls worker", error);
+});
diff --git a/playground/tsconfig.json b/playground/tsconfig.json
new file mode 100644
index 000000000..4811fcdec
--- /dev/null
+++ b/playground/tsconfig.json
@@ -0,0 +1,27 @@
+{
+ "compilerOptions": {
+ "target": "ES2023",
+ "experimentalDecorators": true,
+ "useDefineForClassFields": false,
+ "module": "ESNext",
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
+ "types": ["vite/client"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "verbatimModuleSyntax": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "erasableSyntaxOnly": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src"]
+}
diff --git a/playground/vite.config.ts b/playground/vite.config.ts
new file mode 100644
index 000000000..33c0bae78
--- /dev/null
+++ b/playground/vite.config.ts
@@ -0,0 +1,29 @@
+import { defineConfig } from "vite";
+import os from "node:os";
+import path from "node:path";
+
+export default defineConfig({
+ base: process.env.GITHUB_ACTIONS ? "/playground/" : "/",
+ worker: {
+ format: "es",
+ },
+ optimizeDeps: {
+ include: ["@codingame/monaco-vscode-editor-api"],
+ },
+ build: {
+ rollupOptions: {
+ output: {
+ format: "es",
+ },
+ },
+ },
+ server: {
+ fs: {
+ allow: [
+ path.resolve(__dirname, ".."),
+ path.resolve(__dirname, "node_modules"),
+ path.resolve(os.homedir(), "node_modules"),
+ ],
+ },
+ },
+});