From 9362c9ac8045fd0ff893a276349c7d760c3c53d2 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 6 Sep 2026 11:44:59 +0200 Subject: [PATCH 01/14] Lesss gooo claude --- .gitignore | 4 + CLAUDE.md | 14 +- package-lock.json | 3101 +++++++++++++++++ package.json | 21 + src/background/lock-state.ts | 51 + .../adapters/adapter-interface.ts | 15 + src/content-scripts/adapters/axiom.ts | 39 + src/content-scripts/content-index.ts | 100 + .../interventions/blur-disable.ts | 88 + .../interventions/full-block.ts | 16 + .../interventions/intervention-interface.ts | 22 + src/content-scripts/observer.ts | 28 + src/entrypoints/background.ts | 25 + src/entrypoints/content.ts | 9 + src/entrypoints/popup/index.html | 12 + src/entrypoints/popup/main.ts | 101 + src/entrypoints/popup/style.css | 91 + src/notifications/toast.ts | 83 + src/shared/messaging.ts | 15 + src/shared/types.ts | 14 + tsconfig.json | 3 + wxt.config.ts | 20 + 22 files changed, 3871 insertions(+), 1 deletion(-) create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/background/lock-state.ts create mode 100644 src/content-scripts/adapters/adapter-interface.ts create mode 100644 src/content-scripts/adapters/axiom.ts create mode 100644 src/content-scripts/content-index.ts create mode 100644 src/content-scripts/interventions/blur-disable.ts create mode 100644 src/content-scripts/interventions/full-block.ts create mode 100644 src/content-scripts/interventions/intervention-interface.ts create mode 100644 src/content-scripts/observer.ts create mode 100644 src/entrypoints/background.ts create mode 100644 src/entrypoints/content.ts create mode 100644 src/entrypoints/popup/index.html create mode 100644 src/entrypoints/popup/main.ts create mode 100644 src/entrypoints/popup/style.css create mode 100644 src/notifications/toast.ts create mode 100644 src/shared/messaging.ts create mode 100644 src/shared/types.ts create mode 100644 tsconfig.json create mode 100644 wxt.config.ts diff --git a/.gitignore b/.gitignore index 872d5f6..5f9d03b 100644 --- a/.gitignore +++ b/.gitignore @@ -141,3 +141,7 @@ dist vite.config.js.timestamp-* vite.config.ts.timestamp-* .vite/ + +# WXT +.wxt/ +*.zip diff --git a/CLAUDE.md b/CLAUDE.md index 390a99c..b0ee0e4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,19 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Repository state -This repo currently contains only a spec (`NEXA_SPEC.md`), `README.md`, and `LICENSE` — no source code, `package.json`, or `manifest.json` exist yet. There are no build/lint/test commands to run because nothing has been scaffolded. When implementation begins, this file should be updated with the actual commands (build, lint, test, load-unpacked instructions) once a package manager and bundler are chosen. +Scaffolded with [WXT](https://wxt.dev) (Vite-based, TypeScript-first, built on top of `webextension-polyfill` for the cross-browser `browser.*` API) — the popular framework for building Manifest V3 extensions that target both Firefox and Chromium from one codebase. Package manager: npm. + +Commands: +- `npm install` — install deps (also runs `wxt prepare` via `postinstall` to generate `.wxt/` types). +- `npm run dev` — dev build + watch, targets Chromium by default. +- `npm run dev:firefox` — dev build + watch targeting Firefox (use this for Zen). +- `npm run build` / `npm run build:firefox` — production build, output in `.output/chrome-mv3/` or `.output/firefox-mv3/`. +- `npm run compile` — `tsc --noEmit` type-check only. +- `npm run zip` / `npm run zip:firefox` — production build packaged as a `.zip` for store upload. + +Load-unpacked: run a build, then in Chromium go to `chrome://extensions` → enable Developer Mode → "Load unpacked" → select `.output/chrome-mv3/`. In Firefox/Zen go to `about:debugging#/runtime/this-firefox` → "Load Temporary Add-on" → select any file inside `.output/firefox-mv3/` (or use `npm run dev:firefox`, which auto-opens a temporary profile with the extension loaded). + +There is no lint/test setup yet — add one when the project needs it rather than pre-emptively scaffolding. ## What this project is diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9dd3283 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3101 @@ +{ + "name": "nexa-extension", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nexa-extension", + "version": "0.1.0", + "hasInstallScript": true, + "devDependencies": { + "typescript": "^7.0.2", + "wxt": "^0.21.4" + } + }, + "node_modules/@1natsu/wait-element": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@1natsu/wait-element/-/wait-element-4.2.0.tgz", + "integrity": "sha512-Om0Q+WE9mNrpY4AwMTvkFiYHv8VM7TML3PvOqXy+w6kAjLjKhGYHYX+305+a6J8RVpds9s7IF2Z5aOPYwULFNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "many-keys-map": "^3.0.0" + } + }, + "node_modules/@aklinker1/rollup-plugin-visualizer": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/@aklinker1/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.12.0.tgz", + "integrity": "sha512-X24LvEGw6UFmy0lpGJDmXsMyBD58XmX1bbwsaMLhNoM+UMQfQ3b2RtC+nz4b/NoRK5r6QJSKJHBNVeUdwqybaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "open": "^8.4.0", + "picomatch": "^2.3.1", + "source-map": "^0.7.4", + "yargs": "^17.5.1" + }, + "bin": { + "rollup-plugin-visualizer": "dist/bin/cli.js" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "rollup": "2.x || 3.x || 4.x" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@aklinker1/rollup-plugin-visualizer/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@aklinker1/zero-zip": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@aklinker1/zero-zip/-/zero-zip-1.0.1.tgz", + "integrity": "sha512-07a596Bd1QO0bi3tIyt2xruq6vKk7hCUt9enHBwggvipB0iiycoJ9/CqzN6UFawaKbOi6NBfMlUxUXzIOZ7Dsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.1.tgz", + "integrity": "sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.1.tgz", + "integrity": "sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.1.tgz", + "integrity": "sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.1.tgz", + "integrity": "sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.1.tgz", + "integrity": "sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.1.tgz", + "integrity": "sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.1.tgz", + "integrity": "sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.1.tgz", + "integrity": "sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.1.tgz", + "integrity": "sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.1.tgz", + "integrity": "sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.1.tgz", + "integrity": "sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.1.tgz", + "integrity": "sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.1.tgz", + "integrity": "sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.1.tgz", + "integrity": "sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.1.tgz", + "integrity": "sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.1.tgz", + "integrity": "sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.1.tgz", + "integrity": "sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.1.tgz", + "integrity": "sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.1.tgz", + "integrity": "sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.1.tgz", + "integrity": "sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.1.tgz", + "integrity": "sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "peer": true + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.1.tgz", + "integrity": "sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.1.tgz", + "integrity": "sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.1.tgz", + "integrity": "sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.1.tgz", + "integrity": "sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true + }, + "node_modules/@topcli/prompts": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@topcli/prompts/-/prompts-4.0.0.tgz", + "integrity": "sha512-kkKYPb4k/6kRdnESt77B5vJrrYnYHIYczwv0P1c+tfN/IWEx3KZeKEHoUq7sImIyBEXWdA2uakIroD9pmRJziQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/filesystem": { + "version": "0.0.36", + "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.36.tgz", + "integrity": "sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/filewriter": "*" + } + }, + "node_modules/@types/filewriter": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.33.tgz", + "integrity": "sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/har-format": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.16.tgz", + "integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@webext-core/fake-browser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@webext-core/fake-browser/-/fake-browser-2.0.1.tgz", + "integrity": "sha512-4x5z1z8F0KU8ShF4ForXJ8qnA8oZTy7HYjI91Mbpdzp3H3hU9HR6TNPUxfWtSpFz2FwgEircuJKQg0qFIn6RLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@wxt-dev/browser": "*", + "lodash.merge": "^4.6.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@webext-core/isolated-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@webext-core/isolated-element/-/isolated-element-3.0.0.tgz", + "integrity": "sha512-PmSIBMSe+rFw6eXzpJoV+glc1HPO40clcT9FJ7rSfCQzNewK8/nQZoBYWen2cMqdXdBNM9to/IbNNu5mdQLaPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-potential-custom-element-name": "^1.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@webext-core/match-patterns": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@webext-core/match-patterns/-/match-patterns-2.0.0.tgz", + "integrity": "sha512-EsyMMKfQuSE4rWCjP/TifwvUWlhC/OhiGn5OK3p9F0d57UmtLib0nzguzPKyxM1/kZV7SkE+jYXkE8gsdPRLmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/@wxt-dev/browser": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@wxt-dev/browser/-/browser-0.2.9.tgz", + "integrity": "sha512-I//1sxt6sSd2Ouj48Q7NfXvGFEKbQAL0wf4I/7sxduMx6S0i5xhJqDUkPCCUj42QnnbTpzybI0TA6OIz9NfMgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/filesystem": "*", + "@types/har-format": "*" + } + }, + "node_modules/@wxt-dev/storage": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@wxt-dev/storage/-/storage-1.2.9.tgz", + "integrity": "sha512-dh9TJwvLBM2x4wyy9nE7eYE3wA8pgz1TXSyz7RbdjkJxLfFfWkbeIqiU7VkGy7Vx8sJBmQjoSpk9zSUb3Bjy0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@wxt-dev/browser": ">=0.1", + "superlock": "^1.3.2" + }, + "funding": { + "url": "https://github.com/sponsors/wxt-dev" + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/boolbase": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-2.0.0.tgz", + "integrity": "sha512-DkVaaQHymRhpYEYo9x1oo7Q7B0Y6KJUsjm3c9eTyFDby4MHLBTwZ6ZDWBel5zrYxj1WsZgC5oLpiz+93MluXeA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/c12": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz", + "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "confbox": "^0.2.4", + "defu": "^6.1.6", + "dotenv": "^17.3.1", + "exsolve": "^1.0.8", + "giget": "^3.2.0", + "jiti": "^2.6.1", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^2.1.0", + "pkg-types": "^2.3.0", + "rc9": "^3.0.1" + }, + "peerDependencies": { + "magicast": "*" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/cac": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cac/-/cac-7.0.0.tgz", + "integrity": "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/citty": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.2.tgz", + "integrity": "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/css-select": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-7.0.0.tgz", + "integrity": "sha512-snmjEVXy+1LnwXdxhYvTMj1d9tOh4HxkA1YmoayVBeeyR2C14Pum7fcxJIm4SswYspVy866eYNwlH6xC3/VH5g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^2.0.0", + "css-what": "^8.0.0", + "domhandler": "^6.0.1", + "domutils": "^4.0.2", + "nth-check": "^3.0.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-8.0.0.tgz", + "integrity": "sha512-DH0Bqq3DNp5tdOReuNyAA+Ev4Y2GS5FMbZpeTLP6C4CDi0h5nL0BmUPChXw3o/qbHLDWHl49sbNqQVY7bMSDdw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/dom-serializer": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-3.1.1.tgz", + "integrity": "sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^3.0.0", + "domhandler": "^6.0.0", + "entities": "^8.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-3.0.0.tgz", + "integrity": "sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/domhandler": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-6.0.1.tgz", + "integrity": "sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^3.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-4.0.2.tgz", + "integrity": "sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^3.0.0", + "domelementtype": "^3.0.0", + "domhandler": "^6.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-13.0.0.tgz", + "integrity": "sha512-aBfBS8eYIeXmpHI9ThIlA7/WLq+SLt18iXUZhb52rW89QLKQFoIpPG1bPeewoPZsTyjSSO3T7234FBVUM1V2rA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^17.4.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/exsolve": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.1.tgz", + "integrity": "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/filesize": { + "version": "11.0.23", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-11.0.23.tgz", + "integrity": "sha512-EzB9Km94xm3V7szupSlcGVJpkvXWuNtSZmr7qBoj229q7lEJEEYGMk8gwGnA4ZTAGQmHZigTDEIuOfCQXec1fQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 10.8.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/giget": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/giget/-/giget-3.3.1.tgz", + "integrity": "sha512-r+mvuDjrjMpsdw46Kmeydb8bdHm7wOKw8wNBtTndkjbPjgAp5oUJUxRE76wZFknxIPokfWvep2qSXK37aXE6zg==", + "dev": true, + "license": "MIT", + "bin": { + "giget": "dist/cli.mjs" + } + }, + "node_modules/hookable": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-6.1.1.tgz", + "integrity": "sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "entities": "^7.0.1" + } + }, + "node_modules/htmlparser2/node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/htmlparser2/node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/htmlparser2/node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/htmlparser2/node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/htmlparser2/node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/linkedom": { + "version": "0.18.13", + "resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.18.13.tgz", + "integrity": "sha512-ES/o9qotMpzpN2MHs+Iq/JcVoOj8Fa5wiQYrTdFpvAnwXL0g66XHHUc9WUMk6nAlBtGsFQ24ne+SYnvnaQ2FSw==", + "dev": true, + "license": "ISC", + "dependencies": { + "css-select": "^7.0.0", + "cssom": "^0.5.0", + "html-escaper": "^3.0.3", + "htmlparser2": "^10.1.0", + "uhyphen": "^0.2.0" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "canvas": ">= 2" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/local-pkg": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.2.1.tgz", + "integrity": "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.2.3.tgz", + "integrity": "sha512-Bpb0W2TbLKOZ7vJnOUnVRGq3WL2p+ISV29M6hYPL1AFCpyKZpdr5ytiXoTSSxRVhg8YW7f65+6gbG8WG6PCa/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.4.tgz", + "integrity": "sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "source-map-js": "^1.2.1" + } + }, + "node_modules/many-keys-map": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/many-keys-map/-/many-keys-map-3.0.3.tgz", + "integrity": "sha512-1DiZmDHPXMBgMRjeUtHy1q1VYmeJscHxhIAexX9z/zjRMP80+0ETuPfssi8z+kMY4DwUgsKuHqpjxgmeA9gBNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/fregante" + } + }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nth-check": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-3.0.1.tgz", + "integrity": "sha512-GX0gsdbGVCgnRgbeGaubfjpBXyYRWOOCVeYh08bSQvDZqxz5ndXs1OTfAt/h36G1xvI94YIspsI0sVFqAV9+RQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^2.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nypm": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.9.tgz", + "integrity": "sha512-zxlE2yvSWZWmHcNdT3+5zV2lrCogeE9YOklHrR3dFjqutq5wO7GFDYLFDRXLsYnJzwvy/im9fYoxePvS0VTW0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "citty": "^0.2.2", + "pathe": "^2.0.3", + "tinyexec": "^1.2.4" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ohash": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.12.tgz", + "integrity": "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==", + "dev": true, + "license": "MIT" + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.2.tgz", + "integrity": "sha512-v0sVXzj7oPGysr543YYZLYbcJNJsKikSsp/fFzoxQ12ewY3ZZr7oCPC8y7OlmxfYB3QPvriXmuPD8KZggE1vqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.3.0", + "exsolve": "^1.1.1", + "pathe": "^2.0.3" + } + }, + "node_modules/pkg-types/node_modules/confbox": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.3.1.tgz", + "integrity": "sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/publish-browser-extension": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/publish-browser-extension/-/publish-browser-extension-6.1.1.tgz", + "integrity": "sha512-ovfBOz+cZIOBHZ+oxTfpv1kSGU7ruzy8CXoFGvhXsUbJuty45srGzf1ufEePs69virOWXfFbhZRoNQUdgPvwXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@topcli/prompts": "^4.0.0", + "cac": "^7.0.0", + "tasuku": "^2.3.0" + }, + "bin": { + "publish-extension": "bin/publish-extension.mjs" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/rc9": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.1.0.tgz", + "integrity": "sha512-ufjkNVzbRHKcCOmTahZkmVsyc3W+MSk3jY03m+a7tGHkIsdVMG9l10/3HvFbWkkKzY5VFp3pkRsIo/UYgmFL7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.7", + "destr": "^2.0.5" + } + }, + "node_modules/readdirp": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.1.tgz", + "integrity": "sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.1", + "@rollup/rollup-android-arm64": "4.63.1", + "@rollup/rollup-darwin-arm64": "4.63.1", + "@rollup/rollup-darwin-x64": "4.63.1", + "@rollup/rollup-freebsd-arm64": "4.63.1", + "@rollup/rollup-freebsd-x64": "4.63.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.1", + "@rollup/rollup-linux-arm-musleabihf": "4.63.1", + "@rollup/rollup-linux-arm64-gnu": "4.63.1", + "@rollup/rollup-linux-arm64-musl": "4.63.1", + "@rollup/rollup-linux-loong64-gnu": "4.63.1", + "@rollup/rollup-linux-loong64-musl": "4.63.1", + "@rollup/rollup-linux-ppc64-gnu": "4.63.1", + "@rollup/rollup-linux-ppc64-musl": "4.63.1", + "@rollup/rollup-linux-riscv64-gnu": "4.63.1", + "@rollup/rollup-linux-riscv64-musl": "4.63.1", + "@rollup/rollup-linux-s390x-gnu": "4.63.1", + "@rollup/rollup-linux-x64-gnu": "4.63.1", + "@rollup/rollup-linux-x64-musl": "4.63.1", + "@rollup/rollup-openbsd-x64": "4.63.1", + "@rollup/rollup-openharmony-arm64": "4.63.1", + "@rollup/rollup-win32-arm64-msvc": "4.63.1", + "@rollup/rollup-win32-ia32-msvc": "4.63.1", + "@rollup/rollup-win32-x64-gnu": "4.63.1", + "@rollup/rollup-win32-x64-msvc": "4.63.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "dev": true, + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-literal": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-4.0.0.tgz", + "integrity": "sha512-PaqAvfUZKBwc/SLmNZtHmzK+v19Z4O4eS3cKPeGvbIv/U3pnyEq4Tuw3/4v/FwfM8VQaEawsyCcOQ0P+kpwWWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^10.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/superlock": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/superlock/-/superlock-1.3.5.tgz", + "integrity": "sha512-XpWNthvezZnWp0u7/UL8rBbBOnq2Qx39fw+0RNMC/+eotOd81glzsmIWVH0ejarhTeDQihxOKSbjm2XXFo5a8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/tasuku": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tasuku/-/tasuku-2.3.0.tgz", + "integrity": "sha512-9Jtk+XAnttslCw4i9RceSZGr2PT5TLn0vUyWnoP2SdlSYzkiYRY6kB5qnPi5IQ/Em8e4oBow1rpaA39iijTIrA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/tasuku?sponsor=1" + } + }, + "node_modules/tiny-open": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tiny-open/-/tiny-open-1.3.0.tgz", + "integrity": "sha512-GUFS8yjJZq0oWqCKCJVHcBgMpmi2WEGXY1le3E5ncR0DsgTII5uUyxtfk8/vGyDDGBE42UYHR6Ocvlk8mkXSRg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc" + }, + "engines": { + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/uhyphen": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/uhyphen/-/uhyphen-0.2.0.tgz", + "integrity": "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==", + "dev": true, + "license": "ISC" + }, + "node_modules/unimport": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/unimport/-/unimport-6.4.0.tgz", + "integrity": "sha512-JJOOuNMFq8b4ZPBKwQUxEcba4MplskDzYI1Lvrf8rJfWphZTWvPNXWa493qsPngHUmub89w6C7j+SeLWTE/UIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.18.0", + "escape-string-regexp": "^5.0.0", + "estree-walker": "^3.0.3", + "local-pkg": "^1.2.1", + "magic-string": "^1.1.0", + "mlly": "^1.8.2", + "pathe": "^2.0.3", + "picomatch": "^4.0.5", + "pkg-types": "^2.3.1", + "scule": "^1.3.0", + "strip-literal": "^4.0.0", + "tinyglobby": "^0.2.17", + "unplugin": "^3.3.0", + "unplugin-utils": "^0.3.2" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "oxc-parser": "*", + "rolldown": "^1.0.0" + }, + "peerDependenciesMeta": { + "oxc-parser": { + "optional": true + }, + "rolldown": { + "optional": true + } + } + }, + "node_modules/unplugin": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.3.0.tgz", + "integrity": "sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "picomatch": "^4.0.4", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@farmfe/core": "*", + "@rspack/core": "*", + "bun-types-no-globals": "*", + "esbuild": "*", + "rolldown": "*", + "rollup": "*", + "unloader": "*", + "vite": "*", + "webpack": "*" + }, + "peerDependenciesMeta": { + "@farmfe/core": { + "optional": true + }, + "@rspack/core": { + "optional": true + }, + "bun-types-no-globals": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "rolldown": { + "optional": true + }, + "rollup": { + "optional": true + }, + "unloader": { + "optional": true + }, + "vite": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/unplugin-utils": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.2.tgz", + "integrity": "sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "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 + } + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wxt": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/wxt/-/wxt-0.21.4.tgz", + "integrity": "sha512-iyVMTXb37BQAbsPHCuPzrK3ZxnO9pZmvbKl7VJWIHe3RG40On2NyP2FOjArtOITO96plEYnh3o+iUlRp9qE90w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@1natsu/wait-element": "^4.1.2", + "@aklinker1/rollup-plugin-visualizer": "5.12.0", + "@aklinker1/zero-zip": "^1.0.1", + "@topcli/prompts": "^4.0.0", + "@webext-core/fake-browser": "^2.0.1", + "@webext-core/isolated-element": "^1.1.3 || ^2 || ^3", + "@webext-core/match-patterns": "^2.0.0", + "@wxt-dev/browser": ">=0.1", + "@wxt-dev/storage": "^1.0.0", + "c12": "^3.3.4", + "cac": "^6.7.14 || ^7.0.0", + "chokidar": "^5.0.0", + "consola": "^3.4.2", + "defu": "^6.1.4", + "dotenv-expand": "^13.0.0", + "filesize": "^11.0.17", + "giget": "^1.2.3 || ^2.0.0 || ^3.0.0", + "hookable": "^6.1.0", + "json5": "^2.2.3", + "linkedom": "^0.18.12", + "magicast": "^0.5.2", + "nypm": "^0.6.5", + "picomatch": "^4.0.3", + "publish-browser-extension": "^5.1.0 || ^6.0.0", + "superlock": "^1.3.2", + "tiny-open": "^1.3.0", + "tinyexec": "^1.2.4", + "tinyglobby": "^0.2.16", + "unimport": "^3.13.1 || ^4.0.0 || ^5.0.0 || ^6.0.0" + }, + "bin": { + "wxt": "bin/wxt.mjs", + "wxt-publish-extension": "bin/wxt-publish-extension.mjs" + }, + "engines": { + "bun": ">=1.2.0", + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sponsors/wxt-dev" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=5.4", + "vite": "^6.3.4 || ^7.0.0 || ^8.0.0-0", + "web-ext": ">=9.2.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + }, + "typescript": { + "optional": true + }, + "web-ext": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..86a211f --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "nexa-extension", + "private": true, + "version": "0.1.0", + "description": "Nexa browser extension frontend — your blockchain powered agent to help with your trading emotions.", + "type": "module", + "scripts": { + "dev": "wxt", + "dev:firefox": "wxt -b firefox", + "build": "wxt build", + "build:firefox": "wxt build -b firefox", + "zip": "wxt zip", + "zip:firefox": "wxt zip -b firefox", + "compile": "tsc --noEmit", + "postinstall": "wxt prepare" + }, + "devDependencies": { + "typescript": "^7.0.2", + "wxt": "^0.21.4" + } +} diff --git a/src/background/lock-state.ts b/src/background/lock-state.ts new file mode 100644 index 0000000..668bc42 --- /dev/null +++ b/src/background/lock-state.ts @@ -0,0 +1,51 @@ +import { browser } from 'wxt/browser'; +import { DEFAULT_LOCK_STATE, type LockState } from '@/shared/types'; +import { LOCK_STATE_STORAGE_KEY, type NexaMessage } from '@/shared/messaging'; + +/** + * Single isolated seam for lock state. Nothing else in the extension reads or + * writes lock state directly — this is where the real backend WebSocket + * client plugs in later (replacing the storage-backed mock below) without + * any other module needing to change. + */ + +let currentLockState: LockState = DEFAULT_LOCK_STATE; +let initialized: Promise | null = null; + +function loadPersisted(): Promise { + return browser.storage.local.get(LOCK_STATE_STORAGE_KEY).then((stored) => { + const persisted = stored[LOCK_STATE_STORAGE_KEY] as LockState | undefined; + if (persisted) currentLockState = persisted; + }); +} + +function ensureInitialized(): Promise { + if (!initialized) initialized = loadPersisted(); + return initialized; +} + +export async function getLockState(): Promise { + await ensureInitialized(); + return currentLockState; +} + +export async function setLockState(next: LockState): Promise { + await ensureInitialized(); + currentLockState = next; + await browser.storage.local.set({ [LOCK_STATE_STORAGE_KEY]: next }); + await broadcastLockState(next); +} + +async function broadcastLockState(state: LockState): Promise { + const message: NexaMessage = { type: 'nexa:lock-state-changed', state }; + + const tabs = await browser.tabs.query({}); + await Promise.allSettled( + tabs + .filter((tab): tab is typeof tab & { id: number } => tab.id != null) + .map((tab) => browser.tabs.sendMessage(tab.id, message).catch(() => undefined)), + ); + + // Also notify any open extension surfaces (e.g. the popup) listening via runtime messages. + await browser.runtime.sendMessage(message).catch(() => undefined); +} diff --git a/src/content-scripts/adapters/adapter-interface.ts b/src/content-scripts/adapters/adapter-interface.ts new file mode 100644 index 0000000..3b0269e --- /dev/null +++ b/src/content-scripts/adapters/adapter-interface.ts @@ -0,0 +1,15 @@ +/** + * Contract every site adapter implements. Adapters only know how to *find* + * buy elements on their site — they never decide whether to lock, and never + * apply any visual treatment themselves. + */ +export interface SiteAdapter { + /** Unique identifier, e.g. the site's hostname. */ + id: string; + /** Whether this adapter applies to the given page URL. */ + matches(url: string): boolean; + /** All current buy-action DOM nodes on the page (or within `root`). */ + findBuyElements(root: ParentNode): HTMLElement[]; + /** Stable ancestor of `el` to apply lock styling to, so the whole control is styled. */ + getContainer(el: HTMLElement): HTMLElement; +} diff --git a/src/content-scripts/adapters/axiom.ts b/src/content-scripts/adapters/axiom.ts new file mode 100644 index 0000000..a6bdd7b --- /dev/null +++ b/src/content-scripts/adapters/axiom.ts @@ -0,0 +1,39 @@ +import type { SiteAdapter } from './adapter-interface'; + +/** Matches "Buy", "Buy pixelcat", etc. — never a hardcoded token name. */ +const NOOB_MODE_BUY_TEXT = /^Buy\s/i; + +function findQuickBuyContainers(root: ParentNode): HTMLElement[] { + // The reliable anchor is `.buy-click-container` itself, not a fixed + // ancestor depth from the inner pill — DOM depth here isn't stable. + return Array.from(root.querySelectorAll('.buy-click-container')); +} + +function findNoobModeBuyButtons(root: ParentNode): HTMLElement[] { + return Array.from(root.querySelectorAll('button.bg-increase')).filter((button) => + NOOB_MODE_BUY_TEXT.test(button.textContent?.trim() ?? ''), + ); +} + +export const axiomAdapter: SiteAdapter = { + id: 'axiom.trade', + + matches(url) { + try { + return new URL(url).hostname.endsWith('axiom.trade'); + } catch { + return false; + } + }, + + findBuyElements(root) { + return [...findQuickBuyContainers(root), ...findNoobModeBuyButtons(root)]; + }, + + getContainer(el) { + // Quick Buy pills are already `.buy-click-container` (closest() matches + // the element itself). Noob Mode buttons have no such ancestor, so the + // button itself is the stable container to style. + return el.closest('.buy-click-container') ?? el; + }, +}; diff --git a/src/content-scripts/content-index.ts b/src/content-scripts/content-index.ts new file mode 100644 index 0000000..00ca35e --- /dev/null +++ b/src/content-scripts/content-index.ts @@ -0,0 +1,100 @@ +import { browser } from 'wxt/browser'; +import { axiomAdapter } from './adapters/axiom'; +import type { SiteAdapter } from './adapters/adapter-interface'; +import { blurDisable } from './interventions/blur-disable'; +import { fullBlock } from './interventions/full-block'; +import type { Intervention } from './interventions/intervention-interface'; +import { observeForElements } from './observer'; +import { showLockToast, hideLockToast } from '@/notifications/toast'; +import { DEFAULT_LOCK_STATE, type LockState } from '@/shared/types'; +import type { NexaMessage } from '@/shared/messaging'; + +// Registering a new site = adding it here. Nothing below this line needs to +// change to support another adapter. +const adapters: SiteAdapter[] = [axiomAdapter]; + +// Registering a new intervention = adding it here (and to LockState['scope']). +// Nothing below this line needs to change to support another intervention. +const interventionsByScope: Record = { + 'buy-only': blurDisable, + 'full-block': fullBlock, +}; + +/** Wires an adapter + intervention set + background messages together for the current page. */ +export function initContentScript(): void { + const matchedAdapter = adapters.find((candidate) => candidate.matches(window.location.href)); + if (!matchedAdapter) return; + // Re-bound with a non-optional type so nested closures below don't lose + // the narrowing TS can't carry across function boundaries. + const adapter: SiteAdapter = matchedAdapter; + + let currentState: LockState = DEFAULT_LOCK_STATE; + const lockedContainers = new Set(); + + const activeIntervention = () => interventionsByScope[currentState.scope]; + + const onLockedInteraction = (reason: string | undefined) => { + showLockToast(reason); + }; + + function reconcile(buyElements: HTMLElement[]) { + if (!currentState.locked) return; + + const containers = new Set(buyElements.map((el) => adapter.getContainer(el))); + + for (const container of containers) { + if (!lockedContainers.has(container)) { + activeIntervention().apply(container, currentState.reason, onLockedInteraction); + lockedContainers.add(container); + } + } + + // Containers that unmounted (e.g. SPA route change) leave the DOM with + // their overlay attached; just stop tracking them, nothing to restore. + for (const container of lockedContainers) { + if (!containers.has(container) && !container.isConnected) { + lockedContainers.delete(container); + } + } + } + + function applyLockState(next: LockState) { + const scopeChanged = next.scope !== currentState.scope; + const wasLocked = currentState.locked; + + if (wasLocked && (!next.locked || scopeChanged)) { + const previousIntervention = interventionsByScope[currentState.scope]; + for (const container of lockedContainers) { + previousIntervention.remove(container); + } + lockedContainers.clear(); + } + + if (wasLocked && !next.locked) { + hideLockToast(); + } + + currentState = next; + + if (next.locked) { + showLockToast(next.reason); + } + + reconcile(adapter.findBuyElements(document.body)); + } + + const handle = observeForElements(document.body, () => adapter.findBuyElements(document.body), reconcile); + + browser.runtime + .sendMessage({ type: 'nexa:get-lock-state' } satisfies NexaMessage) + .then((state: LockState) => applyLockState(state)) + .catch(() => undefined); + + browser.runtime.onMessage.addListener((message: NexaMessage) => { + if (message?.type === 'nexa:lock-state-changed') { + applyLockState(message.state); + } + }); + + window.addEventListener('pagehide', () => handle.disconnect(), { once: true }); +} diff --git a/src/content-scripts/interventions/blur-disable.ts b/src/content-scripts/interventions/blur-disable.ts new file mode 100644 index 0000000..7e05369 --- /dev/null +++ b/src/content-scripts/interventions/blur-disable.ts @@ -0,0 +1,88 @@ +import type { Intervention } from './intervention-interface'; + +interface LockedRecord { + originalStyleAttr: string | null; + overlay: HTMLElement; + onOverlayClick: (event: MouseEvent) => void; +} + +// Tracked outside the DOM so remove() can restore each container exactly, +// without relying on any marker class/attribute the site itself might touch. +const locked = new WeakMap(); + +const LOCK_ICON_SVG = + ''; + +const OVERLAY_STYLE = [ + 'position:absolute', + 'inset:0', + 'display:flex', + 'align-items:center', + 'justify-content:center', + 'pointer-events:auto', + 'cursor:not-allowed', + 'z-index:2147483000', + 'filter:none', + 'opacity:1', + 'color:#fff', + 'background:transparent', +].join(';'); + +/** + * v1 default visual treatment: blur + reduced opacity + a non-blurred lock + * icon overlay that also acts as a click-catcher. The original container is + * never mutated destructively — its full inline `style` attribute is + * snapshotted and restored verbatim on remove(). + */ +export const blurDisable: Intervention = { + id: 'blur-disable', + + apply(container, reason, onLockedInteraction) { + if (locked.has(container)) return; + + const originalStyleAttr = container.getAttribute('style'); + const computedPosition = getComputedStyle(container).position; + + container.style.filter = 'blur(3px)'; + container.style.opacity = '0.5'; + container.style.cursor = 'not-allowed'; + container.style.pointerEvents = 'none'; + if (computedPosition === 'static') { + // Needed so the overlay below can position itself against this + // container instead of the nearest existing positioned ancestor. + container.style.position = 'relative'; + } + + const overlay = document.createElement('div'); + overlay.dataset.nexaOverlay = 'true'; + overlay.style.cssText = OVERLAY_STYLE; + overlay.innerHTML = LOCK_ICON_SVG; + + const onOverlayClick = (event: MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + event.stopImmediatePropagation(); + onLockedInteraction(reason); + }; + overlay.addEventListener('click', onOverlayClick, true); + + container.appendChild(overlay); + locked.set(container, { originalStyleAttr, overlay, onOverlayClick }); + }, + + remove(container) { + const record = locked.get(container); + if (!record) return; + + record.overlay.removeEventListener('click', record.onOverlayClick, true); + record.overlay.remove(); + + if (record.originalStyleAttr === null) { + container.removeAttribute('style'); + } else { + container.setAttribute('style', record.originalStyleAttr); + } + + locked.delete(container); + }, +}; diff --git a/src/content-scripts/interventions/full-block.ts b/src/content-scripts/interventions/full-block.ts new file mode 100644 index 0000000..ae80257 --- /dev/null +++ b/src/content-scripts/interventions/full-block.ts @@ -0,0 +1,16 @@ +import type { Intervention } from './intervention-interface'; + +/** + * Escalation intervention: full-site block. Stub only for v1 — structure so + * a future pass can fill in a real full-page overlay without touching + * anything outside this file. + */ +export const fullBlock: Intervention = { + id: 'full-block', + + apply(_container, _reason, _onLockedInteraction) { + console.warn('[nexa] full-block intervention is a v1 stub and is not yet implemented.'); + }, + + remove(_container) {}, +}; diff --git a/src/content-scripts/interventions/intervention-interface.ts b/src/content-scripts/interventions/intervention-interface.ts new file mode 100644 index 0000000..f24f0ce --- /dev/null +++ b/src/content-scripts/interventions/intervention-interface.ts @@ -0,0 +1,22 @@ +/** + * Contract every intervention strategy implements. Interventions only know + * how to visually enforce a lock on a given container — they never detect + * buy elements (that's the adapter's job) and never decide lock/unlock + * timing (that's the background lock-state seam's job). + */ +export interface Intervention { + /** Unique identifier, e.g. 'blur-disable'. */ + id: string; + /** + * Apply the lock treatment to `container`. Must be idempotent — calling + * apply() twice on the same container without an intervening remove() + * should not double-apply. + * + * `onLockedInteraction` should be called whenever the user tries to + * interact with the locked container, so the caller can surface the + * reason toast. + */ + apply(container: HTMLElement, reason: string | undefined, onLockedInteraction: (reason: string | undefined) => void): void; + /** Reverse apply() on `container`, restoring its original markup exactly. */ + remove(container: HTMLElement): void; +} diff --git a/src/content-scripts/observer.ts b/src/content-scripts/observer.ts new file mode 100644 index 0000000..a595b13 --- /dev/null +++ b/src/content-scripts/observer.ts @@ -0,0 +1,28 @@ +export interface ObserverHandle { + disconnect(): void; +} + +/** + * Generic, adapter-agnostic MutationObserver. SPA-safe: re-runs + * `findElements` on every DOM mutation under `root` (rather than a one-time + * querySelectorAll on load) so elements that mount/unmount client-side are + * still caught. + */ +export function observeForElements( + root: Node, + findElements: () => HTMLElement[], + onChange: (elements: HTMLElement[]) => void, +): ObserverHandle { + const emit = () => onChange(findElements()); + + const observer = new MutationObserver(() => emit()); + observer.observe(root, { childList: true, subtree: true }); + + emit(); + + return { + disconnect() { + observer.disconnect(); + }, + }; +} diff --git a/src/entrypoints/background.ts b/src/entrypoints/background.ts new file mode 100644 index 0000000..3cd5259 --- /dev/null +++ b/src/entrypoints/background.ts @@ -0,0 +1,25 @@ +import { getLockState, setLockState } from '@/background/lock-state'; +import type { NexaMessage } from '@/shared/messaging'; + +export default defineBackground(() => { + browser.runtime.onMessage.addListener((message: NexaMessage, _sender, sendResponse) => { + switch (message?.type) { + case 'nexa:get-lock-state': + getLockState().then(sendResponse); + return true; // keep the message channel open for the async response + + case 'nexa:set-lock-state': + setLockState(message.state).then(() => sendResponse({ ok: true })); + return true; + + case 'nexa:open-popup': + // Best-effort: not all browsers/contexts allow programmatic popup + // opening outside a direct user gesture on the action icon. + browser.action.openPopup().catch(() => undefined); + return false; + + default: + return undefined; + } + }); +}); diff --git a/src/entrypoints/content.ts b/src/entrypoints/content.ts new file mode 100644 index 0000000..f6b4f8f --- /dev/null +++ b/src/entrypoints/content.ts @@ -0,0 +1,9 @@ +import { initContentScript } from '@/content-scripts/content-index'; + +export default defineContentScript({ + matches: ['https://axiom.trade/*'], + runAt: 'document_idle', + main() { + initContentScript(); + }, +}); diff --git a/src/entrypoints/popup/index.html b/src/entrypoints/popup/index.html new file mode 100644 index 0000000..d31b2c9 --- /dev/null +++ b/src/entrypoints/popup/index.html @@ -0,0 +1,12 @@ + + + + + Nexa + + + +
+ + + diff --git a/src/entrypoints/popup/main.ts b/src/entrypoints/popup/main.ts new file mode 100644 index 0000000..7832b83 --- /dev/null +++ b/src/entrypoints/popup/main.ts @@ -0,0 +1,101 @@ +import { browser } from 'wxt/browser'; +import { DEFAULT_LOCK_STATE, type LockScope, type LockState } from '@/shared/types'; +import type { NexaMessage } from '@/shared/messaging'; + +const app = document.getElementById('app'); +if (!app) throw new Error('Popup root element #app not found.'); + +app.innerHTML = ` +
+

Nexa

+

Trading companion

+
+ +
+ +
+

Dev mock controls

+

No backend connected yet — use this to simulate a lock signal.

+ + + + + + +
+ + + Adjust loss thresholds → + +`; + +const statusEl = document.getElementById('status') as HTMLElement; +const lockedToggle = document.getElementById('locked-toggle') as HTMLInputElement; +const scopeSelect = document.getElementById('scope-select') as HTMLSelectElement; +const reasonInput = document.getElementById('reason-input') as HTMLInputElement; + +function escapeHtml(value: string): string { + const div = document.createElement('div'); + div.textContent = value; + return div.innerHTML; +} + +function renderStatus(state: LockState): void { + statusEl.innerHTML = state.locked + ? `
🔒 Locked
+

${escapeHtml(state.reason ?? 'No reason provided.')}

+

Scope: ${escapeHtml(state.scope)}

` + : `
Unlocked
`; +} + +function syncFormFromState(state: LockState): void { + lockedToggle.checked = state.locked; + scopeSelect.value = state.scope; + reasonInput.value = state.reason ?? ''; +} + +async function loadState(): Promise { + const message: NexaMessage = { type: 'nexa:get-lock-state' }; + const state = (await browser.runtime.sendMessage(message)) as LockState | undefined; + return state ?? DEFAULT_LOCK_STATE; +} + +async function pushState(): Promise { + const next: LockState = { + locked: lockedToggle.checked, + scope: scopeSelect.value as LockScope, + reason: reasonInput.value.trim() || undefined, + lockedAt: lockedToggle.checked ? Date.now() : undefined, + }; + const message: NexaMessage = { type: 'nexa:set-lock-state', state: next }; + await browser.runtime.sendMessage(message); + renderStatus(next); +} + +for (const el of [lockedToggle, scopeSelect, reasonInput]) { + el.addEventListener('change', () => void pushState()); +} + +loadState().then((state) => { + syncFormFromState(state); + renderStatus(state); +}); + +browser.runtime.onMessage.addListener((message: NexaMessage) => { + if (message?.type === 'nexa:lock-state-changed') { + syncFormFromState(message.state); + renderStatus(message.state); + } +}); diff --git a/src/entrypoints/popup/style.css b/src/entrypoints/popup/style.css new file mode 100644 index 0000000..d91d45f --- /dev/null +++ b/src/entrypoints/popup/style.css @@ -0,0 +1,91 @@ +:root { + color-scheme: light dark; +} + +body { + width: 280px; + margin: 0; + padding: 16px; + font: 13px/1.4 -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; +} + +header h1 { + margin: 0; + font-size: 16px; +} + +header .subtitle { + margin: 2px 0 12px; + opacity: 0.65; +} + +.status { + margin-bottom: 16px; +} + +.badge { + display: inline-block; + padding: 3px 8px; + border-radius: 999px; + font-weight: 600; + font-size: 12px; +} + +.badge-locked { + background: rgba(220, 38, 38, 0.15); + color: #dc2626; +} + +.badge-unlocked { + background: rgba(22, 163, 74, 0.15); + color: #16a34a; +} + +.status .reason { + margin: 8px 0 2px; +} + +.status .scope { + margin: 0; + opacity: 0.65; +} + +.dev-controls { + border-top: 1px solid rgba(128, 128, 128, 0.25); + padding-top: 12px; +} + +.dev-controls h2 { + margin: 0 0 4px; + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.04em; + opacity: 0.65; +} + +.dev-controls .hint { + margin: 0 0 10px; + opacity: 0.6; +} + +.field { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin-bottom: 8px; +} + +.field input[type='text'] { + flex: 1; + min-width: 0; +} + +.thresholds-link { + display: block; + margin-top: 14px; + color: inherit; + opacity: 0.5; + text-decoration: none; + pointer-events: none; +} diff --git a/src/notifications/toast.ts b/src/notifications/toast.ts new file mode 100644 index 0000000..d925ca1 --- /dev/null +++ b/src/notifications/toast.ts @@ -0,0 +1,83 @@ +import { browser } from 'wxt/browser'; +import type { NexaMessage } from '@/shared/messaging'; + +const TOAST_ID = 'nexa-lock-toast'; +const STYLE_ID = 'nexa-lock-toast-styles'; + +function ensureStyles(): void { + if (document.getElementById(STYLE_ID)) return; + + const style = document.createElement('style'); + style.id = STYLE_ID; + style.textContent = ` + #${TOAST_ID} { + position: fixed; + top: 16px; + right: 16px; + z-index: 2147483647; + max-width: 320px; + display: flex; + gap: 10px; + align-items: flex-start; + background: #1a1a1a; + color: #fff; + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 10px; + padding: 12px 14px; + font: 13px/1.4 -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35); + } + #${TOAST_ID} .nexa-toast-icon { flex: none; margin-top: 1px; } + #${TOAST_ID} .nexa-toast-body { flex: 1; } + #${TOAST_ID} .nexa-toast-title { font-weight: 600; margin-bottom: 2px; } + #${TOAST_ID} .nexa-toast-reason { opacity: 0.85; } + #${TOAST_ID} .nexa-toast-action { + margin-top: 8px; + display: inline-block; + font-weight: 600; + color: #7ab8ff; + cursor: pointer; + } + #${TOAST_ID} .nexa-toast-action:hover { text-decoration: underline; } + `; + document.head.appendChild(style); +} + +function ensureToast(): HTMLElement { + ensureStyles(); + + let el = document.getElementById(TOAST_ID); + if (el) return el; + + el = document.createElement('div'); + el.id = TOAST_ID; + el.innerHTML = ` +
🔒
+
+
Nexa: buying locked
+
+
View details
+
+ `; + // Deliberately no close/dismiss control — unlocking is the only way this + // toast goes away. See hideLockToast(), called only on an actual unlock. + el.querySelector('.nexa-toast-action')?.addEventListener('click', () => { + const message: NexaMessage = { type: 'nexa:open-popup' }; + browser.runtime.sendMessage(message).catch(() => undefined); + }); + + document.body.appendChild(el); + return el; +} + +export function showLockToast(reason: string | undefined): void { + const el = ensureToast(); + const reasonEl = el.querySelector('.nexa-toast-reason'); + if (reasonEl) { + reasonEl.textContent = reason ?? 'Buy actions are temporarily locked.'; + } +} + +export function hideLockToast(): void { + document.getElementById(TOAST_ID)?.remove(); +} diff --git a/src/shared/messaging.ts b/src/shared/messaging.ts new file mode 100644 index 0000000..7032d9c --- /dev/null +++ b/src/shared/messaging.ts @@ -0,0 +1,15 @@ +import type { LockState } from './types'; + +/** browser.storage.local key backing the lock state seam in the background script. */ +export const LOCK_STATE_STORAGE_KEY = 'nexa:lockState'; + +/** + * Typed message contracts between background <-> content scripts <-> popup. + * All lock-state reads/writes go through these so no module reaches into + * storage directly (see src/background/lock-state.ts). + */ +export type NexaMessage = + | { type: 'nexa:get-lock-state' } + | { type: 'nexa:set-lock-state'; state: LockState } + | { type: 'nexa:lock-state-changed'; state: LockState } + | { type: 'nexa:open-popup' }; diff --git a/src/shared/types.ts b/src/shared/types.ts new file mode 100644 index 0000000..46480ba --- /dev/null +++ b/src/shared/types.ts @@ -0,0 +1,14 @@ +export type LockScope = 'buy-only' | 'full-block'; + +export interface LockState { + locked: boolean; + reason?: string; + scope: LockScope; + /** epoch ms when this lock was activated; undefined while unlocked */ + lockedAt?: number; +} + +export const DEFAULT_LOCK_STATE: LockState = { + locked: false, + scope: 'buy-only', +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..008bc3c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./.wxt/tsconfig.json" +} diff --git a/wxt.config.ts b/wxt.config.ts new file mode 100644 index 0000000..62fd816 --- /dev/null +++ b/wxt.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from 'wxt'; + +// See https://wxt.dev/api/config.html +export default defineConfig({ + srcDir: 'src', + // Target Manifest V3 on both Chromium and Firefox (modern Firefox / Zen support it). + manifestVersion: 3, + manifest: { + name: 'Nexa', + description: 'Your blockchain powered agent to help with your trading emotions.', + permissions: ['storage'], + host_permissions: ['https://axiom.trade/*'], + browser_specific_settings: { + gecko: { + // Placeholder id for local/dev builds; replace before publishing to AMO. + id: 'nexa-extension@nexa-sol.dev', + }, + }, + }, +}); From 0099a6b7530a24eed566d7cb26372d60e1d81c81 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 6 Sep 2026 12:28:38 +0200 Subject: [PATCH 02/14] Fix sell --- src/content-scripts/adapters/axiom.ts | 37 +++++++++++++++---- .../interventions/blur-disable.ts | 25 ++++++++++--- src/notifications/toast.ts | 18 ++++++++- 3 files changed, 64 insertions(+), 16 deletions(-) diff --git a/src/content-scripts/adapters/axiom.ts b/src/content-scripts/adapters/axiom.ts index a6bdd7b..12c2316 100644 --- a/src/content-scripts/adapters/axiom.ts +++ b/src/content-scripts/adapters/axiom.ts @@ -3,15 +3,33 @@ import type { SiteAdapter } from './adapter-interface'; /** Matches "Buy", "Buy pixelcat", etc. — never a hardcoded token name. */ const NOOB_MODE_BUY_TEXT = /^Buy\s/i; +/** Axiom's "increase"/"decrease" Tailwind color tokens are its buy(green)/sell(red) convention. */ +const INCREASE_MARKER_SELECTOR = '[class*="increase"]'; + +function looksLikeBuyPill(el: HTMLElement): boolean { + return el.matches(INCREASE_MARKER_SELECTOR) || el.querySelector(INCREASE_MARKER_SELECTOR) !== null; +} + function findQuickBuyContainers(root: ParentNode): HTMLElement[] { - // The reliable anchor is `.buy-click-container` itself, not a fixed - // ancestor depth from the inner pill — DOM depth here isn't stable. - return Array.from(root.querySelectorAll('.buy-click-container')); + // `.buy-click-container` is a *shared* wrapper around both the buy and + // sell quick-amount pills as siblings — it is NOT buy-only. The buy pill + // is always its first child element; the sell pill is another sibling. + // Never treat the wrapper itself as the buy element/container. + const buyPills: HTMLElement[] = []; + for (const wrapper of root.querySelectorAll('.buy-click-container')) { + const firstChild = wrapper.firstElementChild; + if (firstChild instanceof HTMLElement && looksLikeBuyPill(firstChild)) { + buyPills.push(firstChild); + } + } + return buyPills; } function findNoobModeBuyButtons(root: ParentNode): HTMLElement[] { + // Buy and Sell noob-mode buttons are siblings too; the buy one is + // identified by its own text starting with "Buy", never by position. return Array.from(root.querySelectorAll('button.bg-increase')).filter((button) => - NOOB_MODE_BUY_TEXT.test(button.textContent?.trim() ?? ''), + {console.log(button.textContent); return NOOB_MODE_BUY_TEXT.test(button.textContent?.trim() ?? '')}, ); } @@ -31,9 +49,12 @@ export const axiomAdapter: SiteAdapter = { }, getContainer(el) { - // Quick Buy pills are already `.buy-click-container` (closest() matches - // the element itself). Noob Mode buttons have no such ancestor, so the - // button itself is the stable container to style. - return el.closest('.buy-click-container') ?? el; + // findBuyElements() above already resolves to exactly the buy-side + // element in both cases — the quick-buy pill (first child of the shared + // buy/sell wrapper) or the noob-mode button whose own text is "Buy …". + // Do NOT climb to any ancestor (e.g. via closest('.buy-click-container')) + // — that wrapper holds the sell pill too, so treating it as the + // container would blur/disable sell right along with buy. + return el; }, }; diff --git a/src/content-scripts/interventions/blur-disable.ts b/src/content-scripts/interventions/blur-disable.ts index 7e05369..cbb7716 100644 --- a/src/content-scripts/interventions/blur-disable.ts +++ b/src/content-scripts/interventions/blur-disable.ts @@ -3,13 +3,20 @@ import type { Intervention } from './intervention-interface'; interface LockedRecord { originalStyleAttr: string | null; overlay: HTMLElement; - onOverlayClick: (event: MouseEvent) => void; + onOverlayEvent: (event: Event) => void; } // Tracked outside the DOM so remove() can restore each container exactly, // without relying on any marker class/attribute the site itself might touch. const locked = new WeakMap(); +// Fast trading UIs (Axiom included) often fire the actual buy on +// `pointerdown`/`mousedown` rather than waiting for `click`, to shave off +// the mouseup round-trip. Intercepting only `click` lets those through — +// so every stage of a click gesture is captured and swallowed here, and the +// reason toast is surfaced on `click` once the gesture completes. +const INTERCEPTED_EVENT_TYPES = ['pointerdown', 'mousedown', 'mouseup', 'click'] as const; + const LOCK_ICON_SVG = ''; @@ -58,23 +65,29 @@ export const blurDisable: Intervention = { overlay.style.cssText = OVERLAY_STYLE; overlay.innerHTML = LOCK_ICON_SVG; - const onOverlayClick = (event: MouseEvent) => { + const onOverlayEvent = (event: Event) => { event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation(); - onLockedInteraction(reason); + if (event.type === 'click') { + onLockedInteraction(reason); + } }; - overlay.addEventListener('click', onOverlayClick, true); + for (const type of INTERCEPTED_EVENT_TYPES) { + overlay.addEventListener(type, onOverlayEvent, true); + } container.appendChild(overlay); - locked.set(container, { originalStyleAttr, overlay, onOverlayClick }); + locked.set(container, { originalStyleAttr, overlay, onOverlayEvent }); }, remove(container) { const record = locked.get(container); if (!record) return; - record.overlay.removeEventListener('click', record.onOverlayClick, true); + for (const type of INTERCEPTED_EVENT_TYPES) { + record.overlay.removeEventListener(type, record.onOverlayEvent, true); + } record.overlay.remove(); if (record.originalStyleAttr === null) { diff --git a/src/notifications/toast.ts b/src/notifications/toast.ts index d925ca1..03a3a94 100644 --- a/src/notifications/toast.ts +++ b/src/notifications/toast.ts @@ -3,6 +3,9 @@ import type { NexaMessage } from '@/shared/messaging'; const TOAST_ID = 'nexa-lock-toast'; const STYLE_ID = 'nexa-lock-toast-styles'; +const AUTO_HIDE_MS = 5000; + +let hideTimer: ReturnType | undefined; function ensureStyles(): void { if (document.getElementById(STYLE_ID)) return; @@ -59,8 +62,9 @@ function ensureToast(): HTMLElement {
View details
`; - // Deliberately no close/dismiss control — unlocking is the only way this - // toast goes away. See hideLockToast(), called only on an actual unlock. + // No manual close button — the toast only goes away on its own (auto-hide + // timer below) or when the lock state actually changes; clicking it never + // unlocks anything. el.querySelector('.nexa-toast-action')?.addEventListener('click', () => { const message: NexaMessage = { type: 'nexa:open-popup' }; browser.runtime.sendMessage(message).catch(() => undefined); @@ -76,8 +80,18 @@ export function showLockToast(reason: string | undefined): void { if (reasonEl) { reasonEl.textContent = reason ?? 'Buy actions are temporarily locked.'; } + + if (hideTimer) clearTimeout(hideTimer); + hideTimer = setTimeout(() => { + hideTimer = undefined; + hideLockToast(); + }, AUTO_HIDE_MS); } export function hideLockToast(): void { + if (hideTimer) { + clearTimeout(hideTimer); + hideTimer = undefined; + } document.getElementById(TOAST_ID)?.remove(); } From 41c6f232fe36dd2a52c09dae44765ea6d74d5c92 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 6 Sep 2026 12:32:09 +0200 Subject: [PATCH 03/14] Fix sell on noob --- src/content-scripts/adapters/axiom.ts | 2 +- src/content-scripts/content-index.ts | 12 +++++++++--- src/content-scripts/observer.ts | 12 ++++++++++-- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/content-scripts/adapters/axiom.ts b/src/content-scripts/adapters/axiom.ts index 12c2316..26ece9f 100644 --- a/src/content-scripts/adapters/axiom.ts +++ b/src/content-scripts/adapters/axiom.ts @@ -29,7 +29,7 @@ function findNoobModeBuyButtons(root: ParentNode): HTMLElement[] { // Buy and Sell noob-mode buttons are siblings too; the buy one is // identified by its own text starting with "Buy", never by position. return Array.from(root.querySelectorAll('button.bg-increase')).filter((button) => - {console.log(button.textContent); return NOOB_MODE_BUY_TEXT.test(button.textContent?.trim() ?? '')}, + NOOB_MODE_BUY_TEXT.test(button.textContent?.trim() ?? ''), ); } diff --git a/src/content-scripts/content-index.ts b/src/content-scripts/content-index.ts index 00ca35e..d85074a 100644 --- a/src/content-scripts/content-index.ts +++ b/src/content-scripts/content-index.ts @@ -49,10 +49,16 @@ export function initContentScript(): void { } } - // Containers that unmounted (e.g. SPA route change) leave the DOM with - // their overlay attached; just stop tracking them, nothing to restore. for (const container of lockedContainers) { - if (!containers.has(container) && !container.isConnected) { + if (!containers.has(container)) { + // No longer a qualifying buy element — either it unmounted (SPA + // route/token change), or an in-place SPA toggle turned it into + // something else (e.g. a Buy/Sell mode switch on the same node). + // Reverse the intervention if the node is still on the page; if + // it's gone there's nothing left to restore. + if (container.isConnected) { + activeIntervention().remove(container); + } lockedContainers.delete(container); } } diff --git a/src/content-scripts/observer.ts b/src/content-scripts/observer.ts index a595b13..bc0881d 100644 --- a/src/content-scripts/observer.ts +++ b/src/content-scripts/observer.ts @@ -6,7 +6,9 @@ export interface ObserverHandle { * Generic, adapter-agnostic MutationObserver. SPA-safe: re-runs * `findElements` on every DOM mutation under `root` (rather than a one-time * querySelectorAll on load) so elements that mount/unmount client-side are - * still caught. + * still caught. Also watches class/text changes on existing nodes — some + * SPA toggles (e.g. a Buy/Sell mode switch) mutate a node in place rather + * than replacing it, which a childList-only observer would miss entirely. */ export function observeForElements( root: Node, @@ -16,7 +18,13 @@ export function observeForElements( const emit = () => onChange(findElements()); const observer = new MutationObserver(() => emit()); - observer.observe(root, { childList: true, subtree: true }); + observer.observe(root, { + childList: true, + subtree: true, + attributes: true, + attributeFilter: ['class'], + characterData: true, + }); emit(); From 5643eebde7d2c9e990aa185c6bbe8e36a4c3e60a Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Sun, 6 Sep 2026 12:49:29 +0200 Subject: [PATCH 04/14] Fold spec docs into CLAUDE.md Consolidate NEXA_SPEC.md and NEXA_PROTOCOL_SPEC.md into CLAUDE.md as the single source of truth, updating the behavioral notes to reflect what live debugging on axiom.trade actually found (rather than the original spec's assumptions) and adding the backend WebSocket wire contract. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01CHESr7MTKG5Dc3mRPvWPn7 --- CLAUDE.md | 82 ++++++++++++++++++++++++++---- NEXA_SPEC.md | 140 --------------------------------------------------- 2 files changed, 73 insertions(+), 149 deletions(-) delete mode 100644 NEXA_SPEC.md diff --git a/CLAUDE.md b/CLAUDE.md index b0ee0e4..92e92b9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,7 +24,7 @@ Nexa is a blockchain-powered trading companion focused on the emotional/behavior License: Apache 2.0. The backend stays closed-source; this frontend repo is the OSS component. -Full behavioral spec lives in `NEXA_SPEC.md` — read it before implementing the lockout feature or any site adapter/intervention. Key points below are a summary, not a replacement. +The behavioral spec and the backend WebSocket protocol spec (formerly `NEXA_SPEC.md` and `NEXA_PROTOCOL_SPEC.md`) have been folded into this file — this document is now the source of truth for both, not a summary of them. ## Target platform @@ -45,7 +45,7 @@ This is the most important design rule in the spec: **lock/unlock decision-makin - Adding a new intervention type (full-site block, delayed buy with countdown, confirmation modal) = a new intervention strategy module, without touching adapters. - The decision of *when/how long/why* to lock is decoupled from *how it's enforced on the page* and from *how it's detected on the page*. -Planned structure (from `NEXA_SPEC.md` §3.2): +Planned structure: ``` /src @@ -72,24 +72,88 @@ A site adapter's contract: `matches` (URL pattern), `findBuyElements()`, `getCon ## Axiom.trade adapter specifics (first target site) -- Axiom is a React SPA — buy elements mount/unmount without full page reloads. Use a `MutationObserver` on a stable root (e.g. `document.body`), not a one-time `querySelectorAll` on load. -- Quick Buy button: find via `closest('.buy-click-container')`, not a hardcoded ancestor depth (DOM depth is not stable). -- Noob Mode Buy button: match `button` elements with class `bg-increase` whose text matches `/^Buy\s/i`. Do not hardcode token names — they vary per token page. -- Full selector details and example markup are in `NEXA_SPEC.md` §3.3. +- Axiom is a React SPA — buy elements mount/unmount without full page reloads, and some controls (see Buy/Sell toggle below) mutate class/text on the *same* node in place instead of remounting. `observer.ts` watches `childList`, `attributes` (`class` only), and `characterData`, all with `subtree: true`, on `document.body` — a `childList`-only observer misses in-place toggles entirely. +- **Quick Buy**: `.buy-click-container` is a *shared* wrapper around both the buy and sell quick-amount pills as siblings — it is not buy-specific, despite the name. The buy pill is always `wrapper.firstElementChild`; never treat the wrapper itself as the styleable element, or the sell pill gets dragged in with it. A secondary check requires the candidate first child to carry an `[class*="increase"]` marker (Axiom's buy/green color token) as a sanity guard. +- **Noob Mode Buy button**: `button.bg-increase` whose own text matches `/^Buy\s/i`. Do not hardcode token names — they vary per token page. The Buy/Sell toggle here is the *same* button element switching state in place (not two separate buttons), which is exactly the in-place-mutation case the observer above exists for — and `content-index.ts`'s `reconcile()` must actively call `intervention.remove()` on a still-connected container that stops matching (e.g. toggled to Sell), not just stop tracking nodes that get removed from the DOM entirely. +- `getContainer(el)` for this adapter is intentionally the identity function — both `findBuyElements()` branches above already resolve to exactly the buy-side element, so there's no ancestor left to climb to (and climbing is what caused the sell-touching bugs in the first place). ## Lock state contract The lock/unlock state is a simple typed interface: `{ locked: boolean, reason?: string, scope: 'buy-only' | 'full-block' }`. For v1 (no real backend yet), this is driven by a mock/dev toggle (e.g. via popup or `browser.storage`). The background script must expose a single isolated seam (`getLockState()` / `setLockState()`) where the real WebSocket client will later plug in — do not scatter lock-state reads/writes across modules. +Note the naming mismatch with the wire protocol below: the frontend's internal `LockScope` uses kebab-case (`'buy-only' | 'full-block'`), while the wire protocol (§ "WebSocket protocol") uses snake_case (`"buy_only" | "full_block"`). The future WebSocket client is exactly where that translation belongs — inside the `getLockState()`/`setLockState()` seam, not leaked into content scripts or the popup. + +## WebSocket protocol (backend wire contract) + +Single source of truth for the wire protocol between the Nexa backend (Rust/Axum) and this extension's background script. Backend and frontend repos both implement against this section — not against each other's internal types. If either side needs to change a message shape, this doc changes first. + +**Transport** +- WebSocket, endpoint `/ws` on the backend. +- One connection per authenticated user session (a user may have multiple extension instances connected; backend broadcasts to all of that user's active connections). +- Auth: session token obtained via the REST auth flow, passed as a query param on the WS upgrade request: `wss:///ws?token=`. No cookies (browser extension client). +- JSON text frames only (no binary frames in v1). Every message has a top-level `type` field (snake_case string) that determines the rest of the shape. +- Wire field naming is **snake_case throughout**, regardless of language-side convention on either end (Rust: `#[serde(rename_all = "snake_case")]`; TS/JS: use the wire names directly, converting only at the frontend's lock-state seam — see note above). + +**Shared enum**: `type LockScope = "buy_only" | "full_block"`. `buy_only` disables new-position buy actions only (sell/manage stay enabled); `full_block` blocks the site entirely (escalation state). + +**Server → client messages** + +- `lock_state` — sent immediately on connect (so a freshly opened browser isn't out of sync) and on every state transition (new lock, escalation, unlock): + ```json + { "type": "lock_state", "locked": true, "scope": "buy_only", "reason": "Down $62 on PIXELCAT, past your $50 threshold", "triggered_at": "2026-09-06T12:00:00Z" } + ``` + `locked` (bool, required). `scope` (required, present even when `locked: false` — represents the scope that would/last applied, so the client never has to guess). `reason` and `triggered_at` (ISO 8601 UTC string) are nullable, null/omitted when `locked: false`. + +- `ack` — response to every client request message (§ below), exactly one per request. A resulting `lock_state` (if the request changed state) is sent separately, after the `ack`: + ```json + { "type": "ack", "for": "unlock_request", "success": true, "error": null } + ``` + `for` echoes the request's `type`. `error` is a human-readable failure reason (e.g. "unlock requires confirmation"), null on success. + +- `error` — protocol-level error not tied to a specific request (e.g. expired auth mid-connection): + ```json + { "type": "error", "code": "auth_expired", "message": "Session token expired, reconnect required." } + ``` + Client should close and re-auth on `auth_expired`. + +**Client → server messages** + +- `unlock_request` — user explicitly chose to unlock: + ```json + { "type": "unlock_request", "confirmed": true } + ``` + `confirmed` must be `true` — this forces the extension UI to have gone through an explicit confirmation step before this message is even sent; the backend still independently rejects (`ack.success: false`) if `confirmed` isn't `true`. Approved requests get an `ack` followed by a `lock_state` with `locked: false`. + +- `escalate_request` — frontend detected a circumvention attempt: + ```json + { "type": "escalate_request", "reason": "circumvention_detected", "detail": "buy element re-clicked 3x after lock applied" } + ``` + `reason` is a short machine-readable code (`circumvention_detected` is the only defined value in v1, kept open-ended for future reasons). `detail` is free-text for logging only, never shown to the user. Accepted escalations get an `ack` followed by a `lock_state` with `scope: "full_block"`. + +- `ping` (optional fallback) — `{ "type": "ping" }`, server replies `{ "type": "pong" }`. Prefer native WebSocket ping/pong frames if the client library supports them; this JSON-level version is only a fallback if it doesn't. + +**Connection lifecycle** +1. Client opens `wss://.../ws?token=...`. +2. Server validates the token; on failure, closes with code `4001` (custom: auth failed) — it must not silently accept and send an `error` message, since the connection itself was never established. +3. On success, server immediately sends `lock_state` (current state). +4. Client and server exchange messages per the message types above for the life of the connection. +5. On disconnect (network drop, backgrounded browser, etc.), client reconnects with backoff (suggested: 1s, 2s, 5s, 10s, capped at 30s). Lock state lives server-side — reconnecting is resuming, not starting a new session; no client-side "resume" message is needed, the post-connect `lock_state` send handles resync. +6. If the server needs to force a disconnect (e.g. session revoked), it sends `error` with `code: "session_revoked"` then closes the socket; client should not auto-reconnect in that case and should route the user back to re-auth. + +**Ownership**: Backend decides *when* to lock/unlock/escalate (loss detection, threshold logic) and authorizes unlock/escalate requests — it is the single source of truth for lock state. Frontend decides *how* a lock is enforced visually on a given site (the site-adapter/intervention-strategy architecture above) and detects circumvention attempts to send `escalate_request`. Neither side should infer state on its own — the frontend's `getLockState()`/`setLockState()` seam should be a thin wrapper that just reflects whatever the last `lock_state` message said, nothing more. + +**Versioning**: v1 has no version field on the wire yet. If a breaking change is needed later, add a `protocol_version` field to all messages and negotiate on connect rather than assuming both sides redeploy simultaneously — the frontend is OSS/user-installed, so it can lag behind backend deploys. + ## Visual treatment for v1 (`blur-disable`) -- Wrap or overlay the original element rather than mutating its classes destructively, so the original markup can be restored exactly on unlock. +- Wrap or overlay the original element rather than mutating its classes destructively, so the original markup can be restored exactly on unlock (snapshot the full inline `style` attribute and restore it verbatim, rather than adding/removing individual classes). - `filter: blur(2-3px)`, ~0.5 opacity, `cursor: not-allowed`, a non-blurred centered lock icon overlay, and `pointer-events: none` (or a transparent click-catcher div). -- A click on a locked element (or its click-catcher) must trigger the same reason toast as lock activation. +- The click-catcher must intercept `pointerdown`, `mousedown`, `mouseup`, *and* `click` (capture phase, `stopImmediatePropagation` on each) — fast trading UIs commonly execute the trade on `mousedown`/`pointerdown` rather than waiting for `click`, so intercepting `click` alone lets the action through before the catcher ever runs. Show the reason toast on `click` only, to avoid firing it 3-4x per gesture. +- The reason toast auto-hides ~5s after being shown (resets the timer on each re-trigger, e.g. a repeated locked click); it has no manual close/dismiss control, but it is not meant to persist indefinitely. ## Non-goals for this milestone -- No real backend/WebSocket connection (mock state only). +- No real backend/WebSocket connection (mock state only) — see "WebSocket protocol" above for the wire contract to implement against when this milestone is picked up. - No cost-basis tracking logic (backend concern). - No full-site-block implementation beyond a stub module. - No threshold-setting UI (placeholder link only). diff --git a/NEXA_SPEC.md b/NEXA_SPEC.md deleted file mode 100644 index 1a38cb3..0000000 --- a/NEXA_SPEC.md +++ /dev/null @@ -1,140 +0,0 @@ -# Nexa — Frontend Browser Extension Spec - -## 1. Overview - -Nexa is a blockchain-powered trading companion that helps traders manage the emotional/behavioral side of trading rather than the trades themselves. Because trades are already visible on-chain, Nexa doesn't need the user to manually log anything — it observes wallet activity and reacts to it. - -This spec covers **only the frontend**: an open-source browser extension. The backend (wallet/RPC listening, loss detection, auth) is a separate private service and is treated here as an external API the extension talks to. - -**Target browser:** built and tested on Zen (Firefox fork), but must be cross-platform (Firefox + Chromium-based via WebExtensions API / manifest v3 where possible). Avoid browser-specific APIs unless behind a compatibility shim. - -**Current milestone:** the "Lockout" feature — disabling quick-buy interactions on supported trading sites after a qualifying loss, with a visible reason/notification, built in a modular way so more sites and more intervention types can be added later. - -## 2. Core Concept Recap (for context, not build scope) - -- User sets their own loss thresholds while calm (Ulysses-contract style). -- Backend watches the user's wallet (Solana RPC / Helius) and detects when a threshold-qualifying loss occurs. -- Backend pushes a lock/unlock signal to the extension (WebSocket). -- Extension enforces the lock in the DOM of the trading site the user is currently on. -- Escalation model: first disable new-position buy actions only (sell/manage stays enabled); if the user tries to circumvent it, escalate to fully blocking the site. - -## 3. Scope of This Spec: The Lockout Feature (v1) - -### 3.1 Behavior - -When the extension receives a "locked" state (from backend, or a mocked/local state for UI dev purposes): - -1. **Detect** buy-action elements on the current page for the active site adapter (see Site Adapters below). -2. **Visually disable** each detected buy element — do not just hide it. The user should see that an action exists but is blocked, not that it vanished. Preferred treatment: blur + reduced opacity + `pointer-events: none`, with a small lock icon overlay on hover/tap. -3. **Intercept** any click that somehow still reaches the underlying element (defense in depth) and prevent default/propagation. -4. **Show a reason.** On lock activation, surface a non-intrusive extension notification/popup (e.g. toast anchored near the browser action icon, or an injected banner) stating why it's locked, e.g. "Locked: -$62 loss on SOL/PIXELCAT crossed your $50 threshold." Include a way to view details (opens extension popup with more info) but no way to silently dismiss-and-unlock from that toast — unlocking is a deliberate action. -5. **Sell/manage actions remain untouched** — only buy-side elements are targeted. -6. When state changes to "unlocked," reverse all DOM modifications cleanly (no leftover classes/overlays). - -### 3.2 Modularity Requirement - -This must be built so that: -- Adding a new supported site = adding a new "site adapter" module, not touching core logic. -- Adding a new intervention type (e.g. full-site block, delayed buy with countdown, confirmation modal) = adding a new "intervention strategy" module, not touching site adapters. -- The lock/unlock *decision* (when to lock, for how long, why) is fully decoupled from *how it's enforced on the page* and from *how it's detected on the page*. - -Suggested architecture: - -``` -/src - /background - extension background/service worker: owns WebSocket connection to backend, holds current lock state, messages content scripts - /content-scripts - /adapters - axiom.ts - site-specific selectors + DOM strategy for axiom.trade - .ts - adapter-interface.ts - shared type/interface all adapters implement - /interventions - blur-disable.ts - visual lock treatment (v1 default) - full-block.ts - escalation: full page block overlay (stub for v1, structure only) - intervention-interface.ts - observer.ts - generic mutation observer that watches for adapter-declared selectors appearing/disappearing (SPA-safe) - content-index.ts - wires adapter + intervention + background messages together - /popup - extension popup UI: current status, reason for lock, threshold settings link - /notifications - toast/banner component injected into page or shown via browser notification API - /shared - messaging.ts - typed message contracts between background <-> content scripts <-> popup - types.ts -manifest.json -``` - -### 3.3 Site Adapter: Axiom.trade (first target) - -An adapter declares: -- `matches`: URL pattern(s) this adapter applies to. -- `findBuyElements()`: returns all current buy-action DOM nodes on the page. -- `getContainer(el)`: given a buy element, returns the stable ancestor container to apply lock styling to (so we style the whole control, not just an inner span). - -Known selectors/patterns to encode (from live markup on axiom.trade): - -**Quick Buy button** (small pill-style buy amount button, e.g. "0.04"): -```html -
0.04
-``` -- Detection: the reliable anchor is the ancestor with class `buy-click-container` — walk up from any `.border-increase\/50.text-increase` candidate, or more robustly, query `.buy-click-container` directly and treat it (or its clickable child) as the target. -- The 4th ancestor being `buy-click-container` is a *current* DOM depth fact, not something to hardcode as "4 levels up" — instead use `closest('.buy-click-container')` so it survives markup depth changes. - -**Noob Mode Buy button** (the big "Buy {token}" button): -```html -
- -
-``` -- Detection: match `button` elements whose class list includes `bg-increase` and whose text content matches `/^Buy\s/i`. Do not hardcode the token name ("pixelcat") — match the "Buy " prefix pattern instead, since it changes per token page. -- Fallback/secondary check: the button's own class `bg-increase` (their "increase/green" action color) combined with being inside a quick-buy/trade panel region, to avoid accidentally matching unrelated green buttons elsewhere on the page. - -**Note for Claude Code:** Axiom.trade is a React SPA — buy elements mount/unmount as the user navigates between tokens without full page reloads. Use a `MutationObserver` on a stable root (e.g. `document.body` or the main app root) rather than a one-time `querySelectorAll` on load, and re-apply/remove the lock treatment whenever matching elements appear. - -### 3.4 Lock Enforcement — Visual Treatment (v1: `blur-disable`) - -For each detected buy container: -- Apply a wrapper/overlay (don't mutate the site's own classes destructively — wrap or overlay so original markup can be restored exactly on unlock). -- Visual: `filter: blur(2-3px)`, reduced opacity (~0.5), cursor `not-allowed`. -- Overlay a small lock icon, centered, not blurred, so it reads clearly as "locked" rather than "broken." -- `pointer-events: none` on the original element or a transparent click-catcher div on top of it. -- Click-catcher (if used) triggers the same "show reason" notification on click/tap, so users who try to click get immediate feedback rather than nothing happening. - -### 3.5 Notification/Reason UI - -- Small, dismissible toast, injected into the page (top-right corner, non-blocking) or via the extension's own popup badge/notification. -- Content: short reason (e.g. threshold crossed, amount, token if relevant) + a "View details" affordance that opens the extension popup. -- Must not offer a one-click unlock from the toast itself. -- Extension popup (separate, already-scoped-elsewhere UI surface) shows: current lock status, reason, time locked, link to adjust thresholds (thresholds config itself is out of scope for this spec — just leave a hook/placeholder). - -### 3.6 State Source for This Milestone - -Since backend integration (WebSocket, RPC loss detection) is separate work: -- Build the content-script/background lock state as consuming a simple typed interface, e.g. `{ locked: boolean, reason?: string, scope: 'buy-only' | 'full-block' }`. -- For UI development purposes, wire this to a mock/dev toggle (e.g. via the extension popup, or a `chrome.storage`/`browser.storage` flag) so the lock/unlock visuals can be demoed and iterated on before the real backend WebSocket is wired in. -- The background script should have a clearly isolated function/module (`getLockState()`/`setLockState()`) that's the single seam where the future WebSocket client will plug in. - -## 4. Non-Goals for This Milestone - -- No real backend connection yet (mock state only). -- No cost-basis tracking logic (backend concern). -- No full-site-block implementation detail beyond a stub module (structure only, so it's easy to fill in later). -- No threshold-setting UI (placeholder link only). -- Only one site adapter (axiom.trade) needs to be functional; architecture just needs to make adding more trivial. - -## 5. Deliverable for This Pass - -A working browser extension (loadable unpacked in Zen/Firefox, and ideally Chromium) that: -1. On axiom.trade, detects quick-buy and noob-mode buy elements per section 3.3. -2. Applies the blur/disable visual treatment per 3.4 when a dev-mode "locked" toggle is on. -3. Shows a reason toast per 3.5 when lock activates, and on click of a locked element. -4. Cleanly reverses all changes when toggled back to unlocked. -5. Is structured per the modular architecture in 3.2 so a second site adapter and a second intervention type could be added without touching existing files beyond registration. - -## 6. Repo / Licensing Context - -- This frontend repo is the OSS component; description: "Your blockchain powered Agent to help with your trading emotions." -- License: Apache 2.0 (consistent with other OSS projects, and chosen partly to build trust given the extension interacts with a trading wallet). -- Backend stays private/closed-source. From 3b6054f2f0a8ad63504220f0424afe72816d3840 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 7 Sep 2026 09:10:18 +0200 Subject: [PATCH 05/14] Connect to the real Nexa backend, replacing the mock lock toggle Adds the wire-protocol WebSocket client (ws-client.ts) and the REST auth flow (backend-client.ts) against the now-live backend, using a locally-generated ed25519 keypair (identity.ts) as a stand-in wallet signer until real wallet-extension integration is built. lock-state.ts's setLockState is now applyLockState, called only by the WS client on an incoming lock_state message -- the backend is the sole source of truth for lock state, so there's no other writer anymore. The popup's dev mock controls are replaced with a live connection-status + lock-state display. Verified end-to-end against the real backend (nonce -> verify -> /me -> ws lock_state) using the same tweetnacl/bs58 libs shipped in the extension. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01YXiHuScXrjxBh7yFGAPq3B --- package-lock.json | 25 ++++++ package.json | 4 + src/background/backend-client.ts | 54 +++++++++++++ src/background/connection-status.ts | 18 +++++ src/background/identity.ts | 53 ++++++++++++ src/background/lock-state.ts | 11 +-- src/background/ws-client.ts | 121 ++++++++++++++++++++++++++++ src/entrypoints/background.ts | 17 +++- src/entrypoints/popup/main.ts | 76 ++++++----------- src/entrypoints/popup/style.css | 60 +++++++------- src/shared/config.ts | 8 ++ src/shared/messaging.ts | 13 ++- src/shared/types.ts | 5 ++ wxt.config.ts | 4 +- 14 files changed, 371 insertions(+), 98 deletions(-) create mode 100644 src/background/backend-client.ts create mode 100644 src/background/connection-status.ts create mode 100644 src/background/identity.ts create mode 100644 src/background/ws-client.ts create mode 100644 src/shared/config.ts diff --git a/package-lock.json b/package-lock.json index 9dd3283..7665b6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,10 @@ "name": "nexa-extension", "version": "0.1.0", "hasInstallScript": true, + "dependencies": { + "bs58": "^6.0.0", + "tweetnacl": "^1.0.3" + }, "devDependencies": { "typescript": "^7.0.2", "wxt": "^0.21.4" @@ -1556,6 +1560,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/base-x": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz", + "integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==", + "license": "MIT" + }, "node_modules/boolbase": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-2.0.0.tgz", @@ -1570,6 +1580,15 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/bs58": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz", + "integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==", + "license": "MIT", + "dependencies": { + "base-x": "^5.0.0" + } + }, "node_modules/c12": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz", @@ -2732,6 +2751,12 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "license": "Unlicense" + }, "node_modules/typescript": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", diff --git a/package.json b/package.json index 86a211f..225d05d 100644 --- a/package.json +++ b/package.json @@ -17,5 +17,9 @@ "devDependencies": { "typescript": "^7.0.2", "wxt": "^0.21.4" + }, + "dependencies": { + "bs58": "^6.0.0", + "tweetnacl": "^1.0.3" } } diff --git a/src/background/backend-client.ts b/src/background/backend-client.ts new file mode 100644 index 0000000..aaa9f49 --- /dev/null +++ b/src/background/backend-client.ts @@ -0,0 +1,54 @@ +import bs58 from 'bs58'; +import { browser } from 'wxt/browser'; +import { BACKEND_HTTP_URL } from '@/shared/config'; +import { getIdentity } from './identity'; + +const SESSION_TOKEN_STORAGE_KEY = 'nexa:sessionToken'; + +/** + * Wallet-signature auth against the backend's REST flow (see + * backend/CLAUDE.md, "Current milestone: authentication + user data"): + * request a nonce for our wallet_address, sign it, exchange for a session + * token. That token is what the WS client (ws-client.ts) authenticates with. + */ +export async function authenticate(): Promise { + const identity = await getIdentity(); + + const nonceRes = await fetch(`${BACKEND_HTTP_URL}/auth/nonce`, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ wallet_address: identity.walletAddress }), + }); + if (!nonceRes.ok) throw new Error(`nonce request failed: ${nonceRes.status}`); + const { nonce } = (await nonceRes.json()) as { nonce: string }; + + const signatureBytes = identity.sign(new TextEncoder().encode(nonce)); + + const verifyRes = await fetch(`${BACKEND_HTTP_URL}/auth/verify`, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ + wallet_address: identity.walletAddress, + signature: bs58.encode(signatureBytes), + }), + }); + if (!verifyRes.ok) throw new Error(`verify request failed: ${verifyRes.status}`); + const { session_token: sessionToken } = (await verifyRes.json()) as { session_token: string }; + + await browser.storage.local.set({ [SESSION_TOKEN_STORAGE_KEY]: sessionToken }); + return sessionToken; +} + +/** + * Returns a usable session token, authenticating from scratch if none is + * stored yet (or `forceRefresh` is set, e.g. after the backend told us via + * `auth_expired`/close-4001 that the old one is no longer valid). + */ +export async function getSessionToken(forceRefresh = false): Promise { + if (!forceRefresh) { + const stored = await browser.storage.local.get(SESSION_TOKEN_STORAGE_KEY); + const token = stored[SESSION_TOKEN_STORAGE_KEY] as string | undefined; + if (token) return token; + } + return authenticate(); +} diff --git a/src/background/connection-status.ts b/src/background/connection-status.ts new file mode 100644 index 0000000..acb6f40 --- /dev/null +++ b/src/background/connection-status.ts @@ -0,0 +1,18 @@ +import { browser } from 'wxt/browser'; +import { DEFAULT_CONNECTION_STATUS, type ConnectionStatus } from '@/shared/types'; +import type { NexaMessage } from '@/shared/messaging'; + +/** In-memory only (not persisted) — always reflects the live WS connection, not a stale guess. */ +let currentStatus: ConnectionStatus = DEFAULT_CONNECTION_STATUS; + +export function getConnectionStatus(): ConnectionStatus { + return currentStatus; +} + +export async function setConnectionStatus(next: ConnectionStatus): Promise { + if (next === currentStatus) return; + currentStatus = next; + + const message: NexaMessage = { type: 'nexa:connection-status-changed', status: next }; + await browser.runtime.sendMessage(message).catch(() => undefined); +} diff --git a/src/background/identity.ts b/src/background/identity.ts new file mode 100644 index 0000000..9f16aee --- /dev/null +++ b/src/background/identity.ts @@ -0,0 +1,53 @@ +import bs58 from 'bs58'; +import nacl from 'tweetnacl'; +import { browser } from 'wxt/browser'; + +const IDENTITY_STORAGE_KEY = 'nexa:identity'; + +/** + * Stand-in "wallet" identity for the backend's Sign-In-with-Solana-style + * auth (nonce + ed25519 signature) — a real Solana wallet keypair, just not + * one connected to an actual wallet extension yet. Generated once and + * persisted so the same wallet_address/session survives extension restarts. + * + * Deliberately temporary: real wallet integration (bridging into the page's + * injected window.solana provider on axiom.trade) is a follow-up iteration, + * not this one. Swap this module out for that without touching callers — + * `getIdentity()`/`sign()` is the seam. + */ +export interface Identity { + walletAddress: string; + sign(message: Uint8Array): Uint8Array; +} + +interface StoredKeypair { + publicKey: string; // base58 + secretKey: string; // base58 +} + +let cached: Identity | null = null; + +export async function getIdentity(): Promise { + if (cached) return cached; + + const stored = await browser.storage.local.get(IDENTITY_STORAGE_KEY); + const persisted = stored[IDENTITY_STORAGE_KEY] as StoredKeypair | undefined; + + const keyPair = persisted + ? { publicKey: bs58.decode(persisted.publicKey), secretKey: bs58.decode(persisted.secretKey) } + : nacl.sign.keyPair(); + + if (!persisted) { + const toStore: StoredKeypair = { + publicKey: bs58.encode(keyPair.publicKey), + secretKey: bs58.encode(keyPair.secretKey), + }; + await browser.storage.local.set({ [IDENTITY_STORAGE_KEY]: toStore }); + } + + cached = { + walletAddress: bs58.encode(keyPair.publicKey), + sign: (message) => nacl.sign.detached(message, keyPair.secretKey), + }; + return cached; +} diff --git a/src/background/lock-state.ts b/src/background/lock-state.ts index 668bc42..c0f24e6 100644 --- a/src/background/lock-state.ts +++ b/src/background/lock-state.ts @@ -3,10 +3,11 @@ import { DEFAULT_LOCK_STATE, type LockState } from '@/shared/types'; import { LOCK_STATE_STORAGE_KEY, type NexaMessage } from '@/shared/messaging'; /** - * Single isolated seam for lock state. Nothing else in the extension reads or - * writes lock state directly — this is where the real backend WebSocket - * client plugs in later (replacing the storage-backed mock below) without - * any other module needing to change. + * Single isolated seam for lock state. Nothing else in the extension reads + * or writes lock state directly. `applyLockState` is called exclusively by + * ws-client.ts when the backend sends a `lock_state` message — the backend + * is the sole source of truth (see the wire protocol's "Ownership" section), + * so there is no other writer. */ let currentLockState: LockState = DEFAULT_LOCK_STATE; @@ -29,7 +30,7 @@ export async function getLockState(): Promise { return currentLockState; } -export async function setLockState(next: LockState): Promise { +export async function applyLockState(next: LockState): Promise { await ensureInitialized(); currentLockState = next; await browser.storage.local.set({ [LOCK_STATE_STORAGE_KEY]: next }); diff --git a/src/background/ws-client.ts b/src/background/ws-client.ts new file mode 100644 index 0000000..c2866eb --- /dev/null +++ b/src/background/ws-client.ts @@ -0,0 +1,121 @@ +import { BACKEND_WS_URL } from '@/shared/config'; +import type { ConnectionStatus, LockScope, LockState } from '@/shared/types'; + +/** + * Client for the backend wire protocol. See ../../CLAUDE.md's "WebSocket + * protocol" section (source of truth, shared with backend/CLAUDE.md) for + * the full spec — this is the one place that protocol is translated into + * this extension's internal `LockState`/`LockScope` (kebab-case), per the + * note there that this translation belongs at the WS client, nowhere else. + */ + +type WireScope = 'buy_only' | 'full_block'; + +interface WireLockState { + type: 'lock_state'; + locked: boolean; + scope: WireScope; + reason?: string | null; + triggered_at?: string | null; +} + +interface WireAck { + type: 'ack'; + for: string; + success: boolean; + error: string | null; +} + +interface WireError { + type: 'error'; + code: string; + message: string; +} + +type WireMessage = WireLockState | WireAck | WireError; + +const WIRE_TO_INTERNAL_SCOPE: Record = { + buy_only: 'buy-only', + full_block: 'full-block', +}; + +// Suggested backoff schedule from the protocol's connection-lifecycle section. +const RECONNECT_DELAYS_MS = [1000, 2000, 5000, 10000, 30000]; + +export interface WsClientOptions { + /** Resolve a session token; pass `forceRefresh: true` to get a fresh one. */ + getToken(forceRefresh?: boolean): Promise; + onLockState(state: LockState): void; + onStatusChange(status: ConnectionStatus): void; +} + +/** Starts the connection and keeps it alive; never resolves. */ +export function connectWsClient(options: WsClientOptions): void { + let reconnectAttempt = 0; + + function scheduleReconnect(forceRefresh: boolean) { + const delay = RECONNECT_DELAYS_MS[Math.min(reconnectAttempt, RECONNECT_DELAYS_MS.length - 1)]; + reconnectAttempt += 1; + setTimeout(() => void connect(forceRefresh), delay); + } + + async function connect(forceRefresh = false): Promise { + options.onStatusChange('connecting'); + + let token: string; + try { + token = await options.getToken(forceRefresh); + } catch { + options.onStatusChange('auth-error'); + scheduleReconnect(true); + return; + } + + const socket = new WebSocket(`${BACKEND_WS_URL}?token=${encodeURIComponent(token)}`); + + socket.addEventListener('open', () => { + reconnectAttempt = 0; + options.onStatusChange('connected'); + }); + + socket.addEventListener('message', (event) => { + let message: WireMessage; + try { + message = JSON.parse(event.data as string); + } catch { + return; // malformed frame — ignore rather than crash the background script + } + + if (message.type === 'lock_state') { + options.onLockState({ + locked: message.locked, + scope: WIRE_TO_INTERNAL_SCOPE[message.scope], + reason: message.reason ?? undefined, + lockedAt: message.triggered_at ? Date.parse(message.triggered_at) : undefined, + }); + } else if (message.type === 'error' && message.code === 'auth_expired') { + // Server already closes the socket for us on auth_expired; the + // 'close' handler below does the reconnect-with-fresh-token. + } + // 'ack' has nothing to react to yet — the client doesn't send + // unlock_request/escalate_request until lock-decision logic exists + // server-side (see backend/CLAUDE.md non-goals for this milestone). + }); + + socket.addEventListener('close', (event) => { + options.onStatusChange(event.code === 4001 ? 'auth-error' : 'disconnected'); + // 4001 (auth failed) and session_revoked both mean the current token + // is bad — get a fresh one before retrying, per the protocol's + // connection-lifecycle notes on both cases. + const forceRefreshNext = event.code === 4001; + scheduleReconnect(forceRefreshNext); + }); + + socket.addEventListener('error', () => { + // The 'close' event still fires after 'error', so reconnect logic + // lives there only — this listener just prevents an unhandled error. + }); + } + + void connect(); +} diff --git a/src/entrypoints/background.ts b/src/entrypoints/background.ts index 3cd5259..00b63ee 100644 --- a/src/entrypoints/background.ts +++ b/src/entrypoints/background.ts @@ -1,16 +1,25 @@ -import { getLockState, setLockState } from '@/background/lock-state'; +import { getSessionToken } from '@/background/backend-client'; +import { getConnectionStatus, setConnectionStatus } from '@/background/connection-status'; +import { applyLockState, getLockState } from '@/background/lock-state'; +import { connectWsClient } from '@/background/ws-client'; import type { NexaMessage } from '@/shared/messaging'; export default defineBackground(() => { + connectWsClient({ + getToken: getSessionToken, + onLockState: (state) => void applyLockState(state), + onStatusChange: (status) => void setConnectionStatus(status), + }); + browser.runtime.onMessage.addListener((message: NexaMessage, _sender, sendResponse) => { switch (message?.type) { case 'nexa:get-lock-state': getLockState().then(sendResponse); return true; // keep the message channel open for the async response - case 'nexa:set-lock-state': - setLockState(message.state).then(() => sendResponse({ ok: true })); - return true; + case 'nexa:get-connection-status': + sendResponse(getConnectionStatus()); + return false; case 'nexa:open-popup': // Best-effort: not all browsers/contexts allow programmatic popup diff --git a/src/entrypoints/popup/main.ts b/src/entrypoints/popup/main.ts index 7832b83..46e15bf 100644 --- a/src/entrypoints/popup/main.ts +++ b/src/entrypoints/popup/main.ts @@ -1,5 +1,5 @@ import { browser } from 'wxt/browser'; -import { DEFAULT_LOCK_STATE, type LockScope, type LockState } from '@/shared/types'; +import { DEFAULT_CONNECTION_STATUS, DEFAULT_LOCK_STATE, type ConnectionStatus, type LockState } from '@/shared/types'; import type { NexaMessage } from '@/shared/messaging'; const app = document.getElementById('app'); @@ -11,40 +11,16 @@ app.innerHTML = `

Trading companion

+
-
-

Dev mock controls

-

No backend connected yet — use this to simulate a lock signal.

- - - - - - -
- Adjust loss thresholds → `; +const connectionEl = document.getElementById('connection') as HTMLElement; const statusEl = document.getElementById('status') as HTMLElement; -const lockedToggle = document.getElementById('locked-toggle') as HTMLInputElement; -const scopeSelect = document.getElementById('scope-select') as HTMLSelectElement; -const reasonInput = document.getElementById('reason-input') as HTMLInputElement; function escapeHtml(value: string): string { const div = document.createElement('div'); @@ -52,6 +28,17 @@ function escapeHtml(value: string): string { return div.innerHTML; } +const CONNECTION_LABELS: Record = { + connecting: 'Connecting…', + connected: 'Connected', + disconnected: 'Disconnected — retrying', + 'auth-error': 'Sign-in failed — retrying', +}; + +function renderConnection(status: ConnectionStatus): void { + connectionEl.innerHTML = ` ${CONNECTION_LABELS[status]}`; +} + function renderStatus(state: LockState): void { statusEl.innerHTML = state.locked ? `
🔒 Locked
@@ -60,42 +47,25 @@ function renderStatus(state: LockState): void { : `
Unlocked
`; } -function syncFormFromState(state: LockState): void { - lockedToggle.checked = state.locked; - scopeSelect.value = state.scope; - reasonInput.value = state.reason ?? ''; -} - -async function loadState(): Promise { +async function loadLockState(): Promise { const message: NexaMessage = { type: 'nexa:get-lock-state' }; const state = (await browser.runtime.sendMessage(message)) as LockState | undefined; return state ?? DEFAULT_LOCK_STATE; } -async function pushState(): Promise { - const next: LockState = { - locked: lockedToggle.checked, - scope: scopeSelect.value as LockScope, - reason: reasonInput.value.trim() || undefined, - lockedAt: lockedToggle.checked ? Date.now() : undefined, - }; - const message: NexaMessage = { type: 'nexa:set-lock-state', state: next }; - await browser.runtime.sendMessage(message); - renderStatus(next); +async function loadConnectionStatus(): Promise { + const message: NexaMessage = { type: 'nexa:get-connection-status' }; + const status = (await browser.runtime.sendMessage(message)) as ConnectionStatus | undefined; + return status ?? DEFAULT_CONNECTION_STATUS; } -for (const el of [lockedToggle, scopeSelect, reasonInput]) { - el.addEventListener('change', () => void pushState()); -} - -loadState().then((state) => { - syncFormFromState(state); - renderStatus(state); -}); +loadLockState().then(renderStatus); +loadConnectionStatus().then(renderConnection); browser.runtime.onMessage.addListener((message: NexaMessage) => { if (message?.type === 'nexa:lock-state-changed') { - syncFormFromState(message.state); renderStatus(message.state); + } else if (message?.type === 'nexa:connection-status-changed') { + renderConnection(message.status); } }); diff --git a/src/entrypoints/popup/style.css b/src/entrypoints/popup/style.css index d91d45f..ea92922 100644 --- a/src/entrypoints/popup/style.css +++ b/src/entrypoints/popup/style.css @@ -19,6 +19,35 @@ header .subtitle { opacity: 0.65; } +.connection { + margin-bottom: 10px; + opacity: 0.75; + display: flex; + align-items: center; + gap: 6px; +} + +.connection-dot { + width: 8px; + height: 8px; + border-radius: 50%; + display: inline-block; + background: currentColor; +} + +.connection-connecting { + color: #ca8a04; +} + +.connection-connected { + color: #16a34a; +} + +.connection-disconnected, +.connection-auth-error { + color: #dc2626; +} + .status { margin-bottom: 16px; } @@ -50,37 +79,6 @@ header .subtitle { opacity: 0.65; } -.dev-controls { - border-top: 1px solid rgba(128, 128, 128, 0.25); - padding-top: 12px; -} - -.dev-controls h2 { - margin: 0 0 4px; - font-size: 12px; - text-transform: uppercase; - letter-spacing: 0.04em; - opacity: 0.65; -} - -.dev-controls .hint { - margin: 0 0 10px; - opacity: 0.6; -} - -.field { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - margin-bottom: 8px; -} - -.field input[type='text'] { - flex: 1; - min-width: 0; -} - .thresholds-link { display: block; margin-top: 14px; diff --git a/src/shared/config.ts b/src/shared/config.ts new file mode 100644 index 0000000..21688bc --- /dev/null +++ b/src/shared/config.ts @@ -0,0 +1,8 @@ +/** + * Nexa backend location. Dev-only default — swap for the real deployed + * backend host before shipping (see backend/CLAUDE.md, "Relationship to the + * frontend"). `host_permissions` in wxt.config.ts must be kept in sync with + * this host. + */ +export const BACKEND_HTTP_URL = 'http://localhost:3000'; +export const BACKEND_WS_URL = 'ws://localhost:3000/ws'; diff --git a/src/shared/messaging.ts b/src/shared/messaging.ts index 7032d9c..bc468d9 100644 --- a/src/shared/messaging.ts +++ b/src/shared/messaging.ts @@ -1,15 +1,20 @@ -import type { LockState } from './types'; +import type { ConnectionStatus, LockState } from './types'; /** browser.storage.local key backing the lock state seam in the background script. */ export const LOCK_STATE_STORAGE_KEY = 'nexa:lockState'; /** * Typed message contracts between background <-> content scripts <-> popup. - * All lock-state reads/writes go through these so no module reaches into - * storage directly (see src/background/lock-state.ts). + * All lock-state reads go through these so no module reaches into storage + * directly (see src/background/lock-state.ts). There is deliberately no + * client-writable "set lock state" message: the backend is the sole source + * of truth for lock state (see the WebSocket protocol's "Ownership" section + * in ../../CLAUDE.md) — only the WS client (ws-client.ts) may apply a new + * lock state, and only because the server told it to. */ export type NexaMessage = | { type: 'nexa:get-lock-state' } - | { type: 'nexa:set-lock-state'; state: LockState } | { type: 'nexa:lock-state-changed'; state: LockState } + | { type: 'nexa:get-connection-status' } + | { type: 'nexa:connection-status-changed'; status: ConnectionStatus } | { type: 'nexa:open-popup' }; diff --git a/src/shared/types.ts b/src/shared/types.ts index 46480ba..6f6ca52 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -12,3 +12,8 @@ export const DEFAULT_LOCK_STATE: LockState = { locked: false, scope: 'buy-only', }; + +/** Status of the background script's WS connection to the backend — surfaced in the popup. */ +export type ConnectionStatus = 'connecting' | 'connected' | 'disconnected' | 'auth-error'; + +export const DEFAULT_CONNECTION_STATUS: ConnectionStatus = 'connecting'; diff --git a/wxt.config.ts b/wxt.config.ts index 62fd816..6f71ffd 100644 --- a/wxt.config.ts +++ b/wxt.config.ts @@ -9,7 +9,9 @@ export default defineConfig({ name: 'Nexa', description: 'Your blockchain powered agent to help with your trading emotions.', permissions: ['storage'], - host_permissions: ['https://axiom.trade/*'], + // axiom.trade: the site adapter target. localhost:3000: the Nexa backend + // (dev only — swap/extend for the real backend host before shipping). + host_permissions: ['https://axiom.trade/*', 'http://localhost:3000/*'], browser_specific_settings: { gecko: { // Placeholder id for local/dev builds; replace before publishing to AMO. From 3302a78536865c0ef0c2467ba2c8fd42ef70eb2a Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 7 Sep 2026 09:11:53 +0200 Subject: [PATCH 06/14] Update CLAUDE.md: real backend connection replaces the mock lock toggle Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01YXiHuScXrjxBh7yFGAPq3B --- CLAUDE.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 92e92b9..20274e7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -79,9 +79,17 @@ A site adapter's contract: `matches` (URL pattern), `findBuyElements()`, `getCon ## Lock state contract -The lock/unlock state is a simple typed interface: `{ locked: boolean, reason?: string, scope: 'buy-only' | 'full-block' }`. For v1 (no real backend yet), this is driven by a mock/dev toggle (e.g. via popup or `browser.storage`). The background script must expose a single isolated seam (`getLockState()` / `setLockState()`) where the real WebSocket client will later plug in — do not scatter lock-state reads/writes across modules. +The lock/unlock state is a simple typed interface: `{ locked: boolean, reason?: string, scope: 'buy-only' | 'full-block' }`. **Implemented**: the background script (`src/background/lock-state.ts`) exposes the single isolated seam (`getLockState()` / `applyLockState()`) — nothing else reads or writes lock state directly. `applyLockState()` is called exclusively by the real WS client (`src/background/ws-client.ts`) on an incoming `lock_state` message; there is no mock/dev toggle anymore since a real backend exists (the popup used to have one — removed once the backend connection landed). -Note the naming mismatch with the wire protocol below: the frontend's internal `LockScope` uses kebab-case (`'buy-only' | 'full-block'`), while the wire protocol (§ "WebSocket protocol") uses snake_case (`"buy_only" | "full_block"`). The future WebSocket client is exactly where that translation belongs — inside the `getLockState()`/`setLockState()` seam, not leaked into content scripts or the popup. +Note the naming mismatch with the wire protocol below: the frontend's internal `LockScope` uses kebab-case (`'buy-only' | 'full-block'`), while the wire protocol (§ "WebSocket protocol") uses snake_case (`"buy_only" | "full_block"`). `ws-client.ts` is exactly where that translation happens — not leaked into content scripts or the popup. + +## Backend connection (implemented) + +- `src/shared/config.ts` — `BACKEND_HTTP_URL`/`BACKEND_WS_URL`, currently hardcoded to `localhost:3000` (dev only; `host_permissions` in `wxt.config.ts` must stay in sync with whatever host is configured here). +- `src/background/identity.ts` — **stub wallet**: a locally-generated ed25519 keypair (via `tweetnacl`), persisted in `browser.storage.local`, used as a stand-in for a real Solana wallet signature. This is deliberately temporary — real wallet integration means bridging into the page's injected `window.solana` provider on axiom.trade (content script + page-context script), which hasn't been built yet. `getIdentity()`/`sign()` is the seam that swap plugs into. +- `src/background/backend-client.ts` — the REST auth flow (`POST /auth/nonce` → sign → `POST /auth/verify` → session token), persisted via `getSessionToken()`. +- `src/background/ws-client.ts` — the WS client described above: connects to `/ws?token=...`, reconnects with the protocol's suggested backoff, force-refreshes the session token on `4001`/`auth_expired` before retrying. +- `src/background/connection-status.ts` — separate from lock state; the popup surfaces this (connecting/connected/disconnected/auth-error) alongside the lock state so a broken connection isn't silently indistinguishable from "unlocked". ## WebSocket protocol (backend wire contract) @@ -153,7 +161,7 @@ Single source of truth for the wire protocol between the Nexa backend (Rust/Axum ## Non-goals for this milestone -- No real backend/WebSocket connection (mock state only) — see "WebSocket protocol" above for the wire contract to implement against when this milestone is picked up. +- Real wallet signing (Phantom et al. via axiom.trade's injected `window.solana`) — auth currently uses a stub local keypair, see "Backend connection" above. - No cost-basis tracking logic (backend concern). - No full-site-block implementation beyond a stub module. - No threshold-setting UI (placeholder link only). From 58e32caa5cd20aafd8e7e6a15d665c3376d73a83 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 7 Sep 2026 11:56:05 +0200 Subject: [PATCH 07/14] Port popup to React; fix backend port colliding with Vite dev server Popup is now a real React app (App.tsx/main.tsx via createRoot), wired through @wxt-dev/module-react. tsconfig.json needed an explicit jsx: "react-jsx" -- WXT's generated .wxt/tsconfig.json doesn't set it, so tsc --noEmit failed on JSX syntax even though the Vite build itself was fine. Root cause of the actually-reported bug (blank popup, predating the React port too): the backend defaulted to port 3000, the same port WXT's dev server uses for this extension. With both running, the popup's script tags pointed at the Vite dev server but the backend answered instead, so main.tsx never loaded -- "View Page Source" showed raw unbundled dev-mode HTML pointing at localhost:3000. Backend now binds :8080 (see backend commit), and BACKEND_HTTP_URL/BACKEND_WS_URL + host_permissions here follow it. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01YXiHuScXrjxBh7yFGAPq3B --- CLAUDE.md | 2 +- package-lock.json | 759 +++++++++++++++++++++++-------- package.json | 5 + src/entrypoints/popup/App.tsx | 71 +++ src/entrypoints/popup/index.html | 2 +- src/entrypoints/popup/main.ts | 71 --- src/entrypoints/popup/main.tsx | 12 + src/shared/config.ts | 7 +- tsconfig.json | 5 +- wxt.config.ts | 6 +- 10 files changed, 662 insertions(+), 278 deletions(-) create mode 100644 src/entrypoints/popup/App.tsx delete mode 100644 src/entrypoints/popup/main.ts create mode 100644 src/entrypoints/popup/main.tsx diff --git a/CLAUDE.md b/CLAUDE.md index 20274e7..ed13ba6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -85,7 +85,7 @@ Note the naming mismatch with the wire protocol below: the frontend's internal ` ## Backend connection (implemented) -- `src/shared/config.ts` — `BACKEND_HTTP_URL`/`BACKEND_WS_URL`, currently hardcoded to `localhost:3000` (dev only; `host_permissions` in `wxt.config.ts` must stay in sync with whatever host is configured here). +- `src/shared/config.ts` — `BACKEND_HTTP_URL`/`BACKEND_WS_URL`, currently hardcoded to `localhost:8080` (dev only; `host_permissions` in `wxt.config.ts` must stay in sync with whatever host is configured here). Deliberately not `:3000` — that's this extension's own Vite dev server port (`npm run dev`), and running the backend on the same port breaks the dev popup silently: its script tags point at Vite, but the backend answers instead, so nothing ever renders. If you see a blank popup with `http://localhost:3000/...` script tags in "View Page Source" that 404 or return something unexpected, this port collision is the first thing to check. - `src/background/identity.ts` — **stub wallet**: a locally-generated ed25519 keypair (via `tweetnacl`), persisted in `browser.storage.local`, used as a stand-in for a real Solana wallet signature. This is deliberately temporary — real wallet integration means bridging into the page's injected `window.solana` provider on axiom.trade (content script + page-context script), which hasn't been built yet. `getIdentity()`/`sign()` is the seam that swap plugs into. - `src/background/backend-client.ts` — the REST auth flow (`POST /auth/nonce` → sign → `POST /auth/verify` → session token), persisted via `getSessionToken()`. - `src/background/ws-client.ts` — the WS client described above: connects to `/ws?token=...`, reconnects with the protocol's suggested backoff, force-refreshes the session token on `4001`/`auth_expired` before retrying. diff --git a/package-lock.json b/package-lock.json index 7665b6c..d40dd85 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,15 @@ "version": "0.1.0", "hasInstallScript": true, "dependencies": { + "@wxt-dev/module-react": "^1.2.2", "bs58": "^6.0.0", + "react": "^19.2.8", + "react-dom": "^19.2.8", "tweetnacl": "^1.0.3" }, "devDependencies": { + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.7", "typescript": "^7.0.2", "wxt": "^0.21.4" } @@ -21,7 +26,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/@1natsu/wait-element/-/wait-element-4.2.0.tgz", "integrity": "sha512-Om0Q+WE9mNrpY4AwMTvkFiYHv8VM7TML3PvOqXy+w6kAjLjKhGYHYX+305+a6J8RVpds9s7IF2Z5aOPYwULFNw==", - "dev": true, "license": "MIT", "dependencies": { "defu": "^6.1.4", @@ -32,7 +36,6 @@ "version": "5.12.0", "resolved": "https://registry.npmjs.org/@aklinker1/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.12.0.tgz", "integrity": "sha512-X24LvEGw6UFmy0lpGJDmXsMyBD58XmX1bbwsaMLhNoM+UMQfQ3b2RtC+nz4b/NoRK5r6QJSKJHBNVeUdwqybaQ==", - "dev": true, "license": "MIT", "dependencies": { "open": "^8.4.0", @@ -59,7 +62,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -72,14 +74,151 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@aklinker1/zero-zip/-/zero-zip-1.0.1.tgz", "integrity": "sha512-07a596Bd1QO0bi3tIyt2xruq6vKk7hCUt9enHBwggvipB0iiycoJ9/CqzN6UFawaKbOi6NBfMlUxUXzIOZ7Dsg==", - "dev": true, "license": "MIT" }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-string-parser": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -89,17 +228,37 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/parser": { "version": "7.29.8", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.29.8" @@ -111,11 +270,72 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/types": { "version": "7.29.8", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.29.7", @@ -132,7 +352,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -150,7 +369,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -168,7 +386,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -186,7 +403,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -204,7 +420,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -222,7 +437,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -240,7 +454,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -258,7 +471,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -276,7 +488,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -294,7 +505,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -312,7 +522,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -330,7 +539,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -348,7 +556,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -366,7 +573,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -384,7 +590,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -402,7 +607,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -420,7 +624,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -438,7 +641,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -456,7 +658,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -474,7 +675,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -492,7 +692,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -510,7 +709,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -528,7 +726,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -546,7 +743,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -564,7 +760,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -582,7 +777,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -597,7 +791,6 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -608,7 +801,6 @@ "version": "2.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -619,7 +811,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -629,14 +820,12 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -650,7 +839,6 @@ "cpu": [ "x64" ], - "dev": true, "libc": [ "glibc" ], @@ -664,6 +852,12 @@ "node": "^22.20 || ^24.12 || >=25" } }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "license": "MIT" + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.63.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.1.tgz", @@ -671,7 +865,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -686,7 +879,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -701,7 +893,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -716,7 +907,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -731,7 +921,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -746,7 +935,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -761,7 +949,6 @@ "cpu": [ "arm" ], - "dev": true, "libc": [ "glibc" ], @@ -779,7 +966,6 @@ "cpu": [ "arm" ], - "dev": true, "libc": [ "musl" ], @@ -797,7 +983,6 @@ "cpu": [ "arm64" ], - "dev": true, "libc": [ "glibc" ], @@ -815,7 +1000,6 @@ "cpu": [ "arm64" ], - "dev": true, "libc": [ "musl" ], @@ -833,7 +1017,6 @@ "cpu": [ "loong64" ], - "dev": true, "libc": [ "glibc" ], @@ -851,7 +1034,6 @@ "cpu": [ "loong64" ], - "dev": true, "libc": [ "musl" ], @@ -869,7 +1051,6 @@ "cpu": [ "ppc64" ], - "dev": true, "libc": [ "glibc" ], @@ -887,7 +1068,6 @@ "cpu": [ "ppc64" ], - "dev": true, "libc": [ "musl" ], @@ -905,7 +1085,6 @@ "cpu": [ "riscv64" ], - "dev": true, "libc": [ "glibc" ], @@ -923,7 +1102,6 @@ "cpu": [ "riscv64" ], - "dev": true, "libc": [ "musl" ], @@ -941,7 +1119,6 @@ "cpu": [ "s390x" ], - "dev": true, "libc": [ "glibc" ], @@ -959,7 +1136,6 @@ "cpu": [ "x64" ], - "dev": true, "libc": [ "glibc" ], @@ -977,7 +1153,6 @@ "cpu": [ "x64" ], - "dev": true, "libc": [ "musl" ], @@ -995,7 +1170,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1010,7 +1184,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1025,7 +1198,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1040,7 +1212,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1055,7 +1226,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1070,7 +1240,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1082,24 +1251,62 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/@topcli/prompts/-/prompts-4.0.0.tgz", "integrity": "sha512-kkKYPb4k/6kRdnESt77B5vJrrYnYHIYczwv0P1c+tfN/IWEx3KZeKEHoUq7sImIyBEXWdA2uakIroD9pmRJziQ==", - "dev": true, "license": "ISC", "engines": { "node": ">=22.0.0" } }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, "license": "MIT" }, "node_modules/@types/filesystem": { "version": "0.0.36", "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.36.tgz", "integrity": "sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==", - "dev": true, "license": "MIT", "dependencies": { "@types/filewriter": "*" @@ -1109,16 +1316,34 @@ "version": "0.0.33", "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.33.tgz", "integrity": "sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==", - "dev": true, "license": "MIT" }, "node_modules/@types/har-format": { "version": "1.2.16", "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.16.tgz", "integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==", - "dev": true, "license": "MIT" }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-I8bPpDLcHBv1qiIiXDCy71Rt8eQDKJP0sMSWJphDdAcdqiJ1sGpZamavoEIRZmYzjia9LuEb2HlYdDpmoENpvQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, "node_modules/@typescript/typescript-aix-ppc64": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", @@ -1126,7 +1351,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1143,7 +1367,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1160,7 +1383,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1177,7 +1399,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1194,7 +1415,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1211,7 +1431,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1228,7 +1447,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1245,7 +1463,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1262,7 +1479,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1279,7 +1495,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1296,7 +1511,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1313,7 +1527,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1330,7 +1543,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1347,7 +1559,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1364,7 +1575,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1381,7 +1591,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1398,7 +1607,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1415,7 +1623,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1432,7 +1639,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1449,7 +1655,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -1459,11 +1664,30 @@ "node": ">=16.20.0" } }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, "node_modules/@webext-core/fake-browser": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@webext-core/fake-browser/-/fake-browser-2.0.1.tgz", "integrity": "sha512-4x5z1z8F0KU8ShF4ForXJ8qnA8oZTy7HYjI91Mbpdzp3H3hU9HR6TNPUxfWtSpFz2FwgEircuJKQg0qFIn6RLg==", - "dev": true, "license": "MIT", "dependencies": { "@wxt-dev/browser": "*", @@ -1477,7 +1701,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@webext-core/isolated-element/-/isolated-element-3.0.0.tgz", "integrity": "sha512-PmSIBMSe+rFw6eXzpJoV+glc1HPO40clcT9FJ7rSfCQzNewK8/nQZoBYWen2cMqdXdBNM9to/IbNNu5mdQLaPg==", - "dev": true, "license": "MIT", "dependencies": { "is-potential-custom-element-name": "^1.0.1" @@ -1490,7 +1713,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@webext-core/match-patterns/-/match-patterns-2.0.0.tgz", "integrity": "sha512-EsyMMKfQuSE4rWCjP/TifwvUWlhC/OhiGn5OK3p9F0d57UmtLib0nzguzPKyxM1/kZV7SkE+jYXkE8gsdPRLmw==", - "dev": true, "license": "MIT", "engines": { "node": "*" @@ -1500,18 +1722,32 @@ "version": "0.2.9", "resolved": "https://registry.npmjs.org/@wxt-dev/browser/-/browser-0.2.9.tgz", "integrity": "sha512-I//1sxt6sSd2Ouj48Q7NfXvGFEKbQAL0wf4I/7sxduMx6S0i5xhJqDUkPCCUj42QnnbTpzybI0TA6OIz9NfMgA==", - "dev": true, "license": "MIT", "dependencies": { "@types/filesystem": "*", "@types/har-format": "*" } }, + "node_modules/@wxt-dev/module-react": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@wxt-dev/module-react/-/module-react-1.2.2.tgz", + "integrity": "sha512-+lRLi1r9dAXpLySWSIWHLJ1h/nFzR20iQnx3RNrKyA6oJg4+ClOluVXozHjfPg9Okfy/umtffiOopGayASrg6w==", + "license": "MIT", + "dependencies": { + "@vitejs/plugin-react": "^4.4.1 || ^5.0.0 || ^6.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/wxt-dev" + }, + "peerDependencies": { + "vite": "^5.4.19 || ^6.3.4 || ^7.0.0 || ^8.0.0-0", + "wxt": ">=0.19.16" + } + }, "node_modules/@wxt-dev/storage": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/@wxt-dev/storage/-/storage-1.2.9.tgz", "integrity": "sha512-dh9TJwvLBM2x4wyy9nE7eYE3wA8pgz1TXSyz7RbdjkJxLfFfWkbeIqiU7VkGy7Vx8sJBmQjoSpk9zSUb3Bjy0Q==", - "dev": true, "license": "MIT", "dependencies": { "@wxt-dev/browser": ">=0.1", @@ -1525,7 +1761,6 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -1538,7 +1773,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -1548,7 +1782,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -1566,11 +1799,22 @@ "integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==", "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.21", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.21.tgz", + "integrity": "sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/boolbase": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-2.0.0.tgz", "integrity": "sha512-DkVaaQHymRhpYEYo9x1oo7Q7B0Y6KJUsjm3c9eTyFDby4MHLBTwZ6ZDWBel5zrYxj1WsZgC5oLpiz+93MluXeA==", - "dev": true, "license": "ISC", "engines": { "node": ">=20.19.0" @@ -1580,6 +1824,39 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/browserslist": { + "version": "4.28.9", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.9.tgz", + "integrity": "sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.20", + "caniuse-lite": "^1.0.30001810", + "electron-to-chromium": "^1.5.420", + "node-releases": "^2.0.54", + "update-browserslist-db": "^1.3.2" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/bs58": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz", @@ -1593,7 +1870,6 @@ "version": "3.3.4", "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz", "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==", - "dev": true, "license": "MIT", "dependencies": { "chokidar": "^5.0.0", @@ -1622,17 +1898,35 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/cac/-/cac-7.0.0.tgz", "integrity": "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=20.19.0" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, "node_modules/chokidar": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", - "dev": true, "license": "MIT", "dependencies": { "readdirp": "^5.0.0" @@ -1648,14 +1942,12 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.2.tgz", "integrity": "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==", - "dev": true, "license": "MIT" }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -1670,7 +1962,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -1683,31 +1974,33 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, "license": "MIT" }, "node_modules/confbox": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", - "dev": true, "license": "MIT" }, "node_modules/consola": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "dev": true, "license": "MIT", "engines": { "node": "^14.18.0 || >=16.10.0" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, "node_modules/css-select": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-7.0.0.tgz", "integrity": "sha512-snmjEVXy+1LnwXdxhYvTMj1d9tOh4HxkA1YmoayVBeeyR2C14Pum7fcxJIm4SswYspVy866eYNwlH6xC3/VH5g==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "boolbase": "^2.0.0", @@ -1728,7 +2021,6 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-8.0.0.tgz", "integrity": "sha512-DH0Bqq3DNp5tdOReuNyAA+Ev4Y2GS5FMbZpeTLP6C4CDi0h5nL0BmUPChXw3o/qbHLDWHl49sbNqQVY7bMSDdw==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=20.19.0" @@ -1742,14 +2034,36 @@ "version": "0.5.0", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "dev": true, "license": "MIT" }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/define-lazy-prop": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -1759,21 +2073,18 @@ "version": "6.1.7", "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", - "dev": true, "license": "MIT" }, "node_modules/destr": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", - "dev": true, "license": "MIT" }, "node_modules/dom-serializer": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-3.1.1.tgz", "integrity": "sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw==", - "dev": true, "license": "MIT", "dependencies": { "domelementtype": "^3.0.0", @@ -1792,7 +2103,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-3.0.0.tgz", "integrity": "sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg==", - "dev": true, "funding": [ { "type": "github", @@ -1808,7 +2118,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-6.0.1.tgz", "integrity": "sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^3.0.0" @@ -1825,7 +2134,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/domutils/-/domutils-4.0.2.tgz", "integrity": "sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^3.0.0", @@ -1844,7 +2152,6 @@ "version": "17.4.2", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -1857,7 +2164,6 @@ "version": "13.0.0", "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-13.0.0.tgz", "integrity": "sha512-aBfBS8eYIeXmpHI9ThIlA7/WLq+SLt18iXUZhb52rW89QLKQFoIpPG1bPeewoPZsTyjSSO3T7234FBVUM1V2rA==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "dotenv": "^17.4.2" @@ -1869,18 +2175,22 @@ "url": "https://dotenvx.com" } }, + "node_modules/electron-to-chromium": { + "version": "1.5.422", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.422.tgz", + "integrity": "sha512-UvA/32XqrLDdZSn7Jllo1AYNcWji/G0d5M0GTViE7KoGBiMunw3a34Sb2KO4ZZyrSEhqsxFoVhWWJshdyfKqJA==", + "license": "ISC" + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/entities": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=20.19.0" @@ -1893,7 +2203,6 @@ "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, "hasInstallScript": true, "license": "MIT", "peer": true, @@ -1936,7 +2245,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -1946,7 +2254,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -1959,7 +2266,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" @@ -1969,14 +2275,12 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.1.tgz", "integrity": "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==", - "dev": true, "license": "MIT" }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -1994,7 +2298,6 @@ "version": "11.0.23", "resolved": "https://registry.npmjs.org/filesize/-/filesize-11.0.23.tgz", "integrity": "sha512-EzB9Km94xm3V7szupSlcGVJpkvXWuNtSZmr7qBoj229q7lEJEEYGMk8gwGnA4ZTAGQmHZigTDEIuOfCQXec1fQ==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">= 10.8.0" @@ -2004,7 +2307,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -2016,11 +2318,19 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -2030,7 +2340,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/giget/-/giget-3.3.1.tgz", "integrity": "sha512-r+mvuDjrjMpsdw46Kmeydb8bdHm7wOKw8wNBtTndkjbPjgAp5oUJUxRE76wZFknxIPokfWvep2qSXK37aXE6zg==", - "dev": true, "license": "MIT", "bin": { "giget": "dist/cli.mjs" @@ -2040,21 +2349,18 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/hookable/-/hookable-6.1.1.tgz", "integrity": "sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==", - "dev": true, "license": "MIT" }, "node_modules/html-escaper": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", - "dev": true, "license": "MIT" }, "node_modules/htmlparser2": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", - "dev": true, "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { @@ -2074,7 +2380,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dev": true, "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", @@ -2089,7 +2394,6 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -2102,7 +2406,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, "funding": [ { "type": "github", @@ -2115,7 +2418,6 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.3.0" @@ -2131,7 +2433,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^2.0.0", @@ -2146,7 +2447,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -2159,7 +2459,6 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, "license": "MIT", "bin": { "is-docker": "cli.js" @@ -2175,7 +2474,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -2185,14 +2483,12 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true, "license": "MIT" }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, "license": "MIT", "dependencies": { "is-docker": "^2.0.0" @@ -2205,7 +2501,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", - "dev": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -2215,14 +2510,24 @@ "version": "10.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", - "dev": true, "license": "MIT" }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -2235,7 +2540,6 @@ "version": "0.18.13", "resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.18.13.tgz", "integrity": "sha512-ES/o9qotMpzpN2MHs+Iq/JcVoOj8Fa5wiQYrTdFpvAnwXL0g66XHHUc9WUMk6nAlBtGsFQ24ne+SYnvnaQ2FSw==", - "dev": true, "license": "ISC", "dependencies": { "css-select": "^7.0.0", @@ -2260,7 +2564,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.2.1.tgz", "integrity": "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==", - "dev": true, "license": "MIT", "dependencies": { "mlly": "^1.7.4", @@ -2278,14 +2581,21 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, "license": "MIT" }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, "node_modules/magic-string": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.2.3.tgz", "integrity": "sha512-Bpb0W2TbLKOZ7vJnOUnVRGq3WL2p+ISV29M6hYPL1AFCpyKZpdr5ytiXoTSSxRVhg8YW7f65+6gbG8WG6PCa/g==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" @@ -2295,7 +2605,6 @@ "version": "0.5.4", "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.4.tgz", "integrity": "sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==", - "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.29.7", @@ -2307,7 +2616,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/many-keys-map/-/many-keys-map-3.0.3.tgz", "integrity": "sha512-1DiZmDHPXMBgMRjeUtHy1q1VYmeJscHxhIAexX9z/zjRMP80+0ETuPfssi8z+kMY4DwUgsKuHqpjxgmeA9gBNA==", - "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -2320,7 +2628,6 @@ "version": "1.8.2", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", - "dev": true, "license": "MIT", "dependencies": { "acorn": "^8.16.0", @@ -2333,14 +2640,12 @@ "version": "0.1.8", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "dev": true, "license": "MIT" }, "node_modules/mlly/node_modules/pkg-types": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "dev": true, "license": "MIT", "dependencies": { "confbox": "^0.1.8", @@ -2348,11 +2653,16 @@ "pathe": "^2.0.1" } }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.18", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", - "dev": true, "funding": [ { "type": "github", @@ -2368,11 +2678,19 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/node-releases": { + "version": "2.0.54", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.54.tgz", + "integrity": "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/nth-check": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-3.0.1.tgz", "integrity": "sha512-GX0gsdbGVCgnRgbeGaubfjpBXyYRWOOCVeYh08bSQvDZqxz5ndXs1OTfAt/h36G1xvI94YIspsI0sVFqAV9+RQ==", - "dev": true, "license": "BSD-2-Clause", "dependencies": { "boolbase": "^2.0.0" @@ -2389,7 +2707,6 @@ "version": "0.6.9", "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.9.tgz", "integrity": "sha512-zxlE2yvSWZWmHcNdT3+5zV2lrCogeE9YOklHrR3dFjqutq5wO7GFDYLFDRXLsYnJzwvy/im9fYoxePvS0VTW0w==", - "dev": true, "license": "MIT", "dependencies": { "citty": "^0.2.2", @@ -2407,14 +2724,12 @@ "version": "2.0.12", "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.12.tgz", "integrity": "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==", - "dev": true, "license": "MIT" }, "node_modules/open": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", - "dev": true, "license": "MIT", "dependencies": { "define-lazy-prop": "^2.0.0", @@ -2432,29 +2747,24 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, "license": "MIT" }, "node_modules/perfect-debounce": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", - "dev": true, "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -2467,7 +2777,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.2.tgz", "integrity": "sha512-v0sVXzj7oPGysr543YYZLYbcJNJsKikSsp/fFzoxQ12ewY3ZZr7oCPC8y7OlmxfYB3QPvriXmuPD8KZggE1vqg==", - "dev": true, "license": "MIT", "dependencies": { "confbox": "^0.3.0", @@ -2479,14 +2788,12 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.3.1.tgz", "integrity": "sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA==", - "dev": true, "license": "MIT" }, "node_modules/postcss": { "version": "8.5.28", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2516,7 +2823,6 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/publish-browser-extension/-/publish-browser-extension-6.1.1.tgz", "integrity": "sha512-ovfBOz+cZIOBHZ+oxTfpv1kSGU7ruzy8CXoFGvhXsUbJuty45srGzf1ufEePs69virOWXfFbhZRoNQUdgPvwXA==", - "dev": true, "license": "MIT", "dependencies": { "@topcli/prompts": "^4.0.0", @@ -2534,7 +2840,6 @@ "version": "0.2.11", "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", - "dev": true, "funding": [ { "type": "individual", @@ -2551,18 +2856,46 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.1.0.tgz", "integrity": "sha512-ufjkNVzbRHKcCOmTahZkmVsyc3W+MSk3jY03m+a7tGHkIsdVMG9l10/3HvFbWkkKzY5VFp3pkRsIo/UYgmFL7Q==", - "dev": true, "license": "MIT", "dependencies": { "defu": "^6.1.7", "destr": "^2.0.5" } }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/readdirp": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 20.19.0" @@ -2576,7 +2909,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -2586,7 +2918,6 @@ "version": "4.63.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.1.tgz", "integrity": "sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==", - "dev": true, "license": "MIT", "peer": true, "dependencies": { @@ -2629,18 +2960,31 @@ "fsevents": "~2.3.2" } }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, "node_modules/scule": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", - "dev": true, "license": "MIT" }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">= 12" @@ -2650,7 +2994,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -2660,7 +3003,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -2675,7 +3017,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -2688,7 +3029,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-4.0.0.tgz", "integrity": "sha512-PaqAvfUZKBwc/SLmNZtHmzK+v19Z4O4eS3cKPeGvbIv/U3pnyEq4Tuw3/4v/FwfM8VQaEawsyCcOQ0P+kpwWWw==", - "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^10.0.0" @@ -2701,7 +3041,6 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/superlock/-/superlock-1.3.5.tgz", "integrity": "sha512-XpWNthvezZnWp0u7/UL8rBbBOnq2Qx39fw+0RNMC/+eotOd81glzsmIWVH0ejarhTeDQihxOKSbjm2XXFo5a8w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 14" @@ -2711,7 +3050,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/tasuku/-/tasuku-2.3.0.tgz", "integrity": "sha512-9Jtk+XAnttslCw4i9RceSZGr2PT5TLn0vUyWnoP2SdlSYzkiYRY6kB5qnPi5IQ/Em8e4oBow1rpaA39iijTIrA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/privatenumber/tasuku?sponsor=1" @@ -2721,14 +3059,12 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/tiny-open/-/tiny-open-1.3.0.tgz", "integrity": "sha512-GUFS8yjJZq0oWqCKCJVHcBgMpmi2WEGXY1le3E5ncR0DsgTII5uUyxtfk8/vGyDDGBE42UYHR6Ocvlk8mkXSRg==", - "dev": true, "license": "MIT" }, "node_modules/tinyexec": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", - "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -2738,7 +3074,6 @@ "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", - "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -2761,7 +3096,7 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc" @@ -2796,21 +3131,18 @@ "version": "1.6.4", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", - "dev": true, "license": "MIT" }, "node_modules/uhyphen": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/uhyphen/-/uhyphen-0.2.0.tgz", "integrity": "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==", - "dev": true, "license": "ISC" }, "node_modules/unimport": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/unimport/-/unimport-6.4.0.tgz", "integrity": "sha512-JJOOuNMFq8b4ZPBKwQUxEcba4MplskDzYI1Lvrf8rJfWphZTWvPNXWa493qsPngHUmub89w6C7j+SeLWTE/UIQ==", - "dev": true, "license": "MIT", "dependencies": { "acorn": "^8.18.0", @@ -2848,7 +3180,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.3.0.tgz", "integrity": "sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.5", @@ -2903,7 +3234,6 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.2.tgz", "integrity": "sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA==", - "dev": true, "license": "MIT", "dependencies": { "pathe": "^2.0.3", @@ -2916,11 +3246,40 @@ "url": "https://github.com/sponsors/sxzz" } }, + "node_modules/update-browserslist-db": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", + "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/vite": { "version": "6.4.3", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", - "dev": true, "license": "MIT", "peer": true, "dependencies": { @@ -2996,14 +3355,12 @@ "version": "0.6.2", "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", - "dev": true, "license": "MIT" }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -3021,7 +3378,6 @@ "version": "0.21.4", "resolved": "https://registry.npmjs.org/wxt/-/wxt-0.21.4.tgz", "integrity": "sha512-iyVMTXb37BQAbsPHCuPzrK3ZxnO9pZmvbKl7VJWIHe3RG40On2NyP2FOjArtOITO96plEYnh3o+iUlRp9qE90w==", - "dev": true, "license": "MIT", "dependencies": { "@1natsu/wait-element": "^4.1.2", @@ -3087,17 +3443,21 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "license": "ISC", "engines": { "node": ">=10" } }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, "node_modules/yargs": { "version": "17.7.3", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", - "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -3116,7 +3476,6 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, "license": "ISC", "engines": { "node": ">=12" diff --git a/package.json b/package.json index 225d05d..14e19d8 100644 --- a/package.json +++ b/package.json @@ -15,11 +15,16 @@ "postinstall": "wxt prepare" }, "devDependencies": { + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.7", "typescript": "^7.0.2", "wxt": "^0.21.4" }, "dependencies": { + "@wxt-dev/module-react": "^1.2.2", "bs58": "^6.0.0", + "react": "^19.2.8", + "react-dom": "^19.2.8", "tweetnacl": "^1.0.3" } } diff --git a/src/entrypoints/popup/App.tsx b/src/entrypoints/popup/App.tsx new file mode 100644 index 0000000..f8d0d73 --- /dev/null +++ b/src/entrypoints/popup/App.tsx @@ -0,0 +1,71 @@ +import { useEffect, useState } from 'react'; +import { browser } from 'wxt/browser'; +import { DEFAULT_CONNECTION_STATUS, DEFAULT_LOCK_STATE, type ConnectionStatus, type LockState } from '@/shared/types'; +import type { NexaMessage } from '@/shared/messaging'; + +const CONNECTION_LABELS: Record = { + connecting: 'Connecting…', + connected: 'Connected', + disconnected: 'Disconnected — retrying', + 'auth-error': 'Sign-in failed — retrying', +}; + +export function App() { + const [lockState, setLockState] = useState(DEFAULT_LOCK_STATE); + const [connectionStatus, setConnectionStatus] = useState(DEFAULT_CONNECTION_STATUS); + + useEffect(() => { + browser.runtime + .sendMessage({ type: 'nexa:get-lock-state' } satisfies NexaMessage) + .then((state: LockState | undefined) => setLockState(state ?? DEFAULT_LOCK_STATE)); + + browser.runtime + .sendMessage({ type: 'nexa:get-connection-status' } satisfies NexaMessage) + .then((status: ConnectionStatus | undefined) => setConnectionStatus(status ?? DEFAULT_CONNECTION_STATUS)); + + const listener = (message: NexaMessage) => { + if (message?.type === 'nexa:lock-state-changed') { + setLockState(message.state); + } else if (message?.type === 'nexa:connection-status-changed') { + setConnectionStatus(message.status); + } + }; + browser.runtime.onMessage.addListener(listener); + return () => browser.runtime.onMessage.removeListener(listener); + }, []); + + return ( + <> +
+

Nexa

+

Trading companion

+
+ +
+ + {' '} + {CONNECTION_LABELS[connectionStatus]} +
+ +
+ {lockState.locked ? ( + <> +
🔒 Locked
+

{lockState.reason ?? 'No reason provided.'}

+

Scope: {lockState.scope}

+ + ) : ( +
Unlocked
+ )} +
+ + + Adjust loss thresholds → + + + ); +} diff --git a/src/entrypoints/popup/index.html b/src/entrypoints/popup/index.html index d31b2c9..04964a9 100644 --- a/src/entrypoints/popup/index.html +++ b/src/entrypoints/popup/index.html @@ -7,6 +7,6 @@
- + diff --git a/src/entrypoints/popup/main.ts b/src/entrypoints/popup/main.ts deleted file mode 100644 index 46e15bf..0000000 --- a/src/entrypoints/popup/main.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { browser } from 'wxt/browser'; -import { DEFAULT_CONNECTION_STATUS, DEFAULT_LOCK_STATE, type ConnectionStatus, type LockState } from '@/shared/types'; -import type { NexaMessage } from '@/shared/messaging'; - -const app = document.getElementById('app'); -if (!app) throw new Error('Popup root element #app not found.'); - -app.innerHTML = ` -
-

Nexa

-

Trading companion

-
- -
-
- - - Adjust loss thresholds → - -`; - -const connectionEl = document.getElementById('connection') as HTMLElement; -const statusEl = document.getElementById('status') as HTMLElement; - -function escapeHtml(value: string): string { - const div = document.createElement('div'); - div.textContent = value; - return div.innerHTML; -} - -const CONNECTION_LABELS: Record = { - connecting: 'Connecting…', - connected: 'Connected', - disconnected: 'Disconnected — retrying', - 'auth-error': 'Sign-in failed — retrying', -}; - -function renderConnection(status: ConnectionStatus): void { - connectionEl.innerHTML = ` ${CONNECTION_LABELS[status]}`; -} - -function renderStatus(state: LockState): void { - statusEl.innerHTML = state.locked - ? `
🔒 Locked
-

${escapeHtml(state.reason ?? 'No reason provided.')}

-

Scope: ${escapeHtml(state.scope)}

` - : `
Unlocked
`; -} - -async function loadLockState(): Promise { - const message: NexaMessage = { type: 'nexa:get-lock-state' }; - const state = (await browser.runtime.sendMessage(message)) as LockState | undefined; - return state ?? DEFAULT_LOCK_STATE; -} - -async function loadConnectionStatus(): Promise { - const message: NexaMessage = { type: 'nexa:get-connection-status' }; - const status = (await browser.runtime.sendMessage(message)) as ConnectionStatus | undefined; - return status ?? DEFAULT_CONNECTION_STATUS; -} - -loadLockState().then(renderStatus); -loadConnectionStatus().then(renderConnection); - -browser.runtime.onMessage.addListener((message: NexaMessage) => { - if (message?.type === 'nexa:lock-state-changed') { - renderStatus(message.state); - } else if (message?.type === 'nexa:connection-status-changed') { - renderConnection(message.status); - } -}); diff --git a/src/entrypoints/popup/main.tsx b/src/entrypoints/popup/main.tsx new file mode 100644 index 0000000..4b5ed3b --- /dev/null +++ b/src/entrypoints/popup/main.tsx @@ -0,0 +1,12 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import { App } from './App'; + +const root = document.getElementById('app'); +if (!root) throw new Error('Popup root element #app not found.'); + +createRoot(root).render( + + + , +); diff --git a/src/shared/config.ts b/src/shared/config.ts index 21688bc..426f95b 100644 --- a/src/shared/config.ts +++ b/src/shared/config.ts @@ -4,5 +4,8 @@ * frontend"). `host_permissions` in wxt.config.ts must be kept in sync with * this host. */ -export const BACKEND_HTTP_URL = 'http://localhost:3000'; -export const BACKEND_WS_URL = 'ws://localhost:3000/ws'; +// Port 8080, deliberately not 3000 — that's WXT/Vite's dev server port for +// this extension, and colliding with it breaks the dev popup silently (its +// script tags point at Vite, but the backend answers instead). +export const BACKEND_HTTP_URL = 'http://localhost:8080'; +export const BACKEND_WS_URL = 'ws://localhost:8080/ws'; diff --git a/tsconfig.json b/tsconfig.json index 008bc3c..6ad10a0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,3 +1,6 @@ { - "extends": "./.wxt/tsconfig.json" + "extends": "./.wxt/tsconfig.json", + "compilerOptions": { + "jsx": "react-jsx" + } } diff --git a/wxt.config.ts b/wxt.config.ts index 6f71ffd..6251196 100644 --- a/wxt.config.ts +++ b/wxt.config.ts @@ -3,15 +3,17 @@ import { defineConfig } from 'wxt'; // See https://wxt.dev/api/config.html export default defineConfig({ srcDir: 'src', + modules: ['@wxt-dev/module-react'], // Target Manifest V3 on both Chromium and Firefox (modern Firefox / Zen support it). manifestVersion: 3, manifest: { name: 'Nexa', description: 'Your blockchain powered agent to help with your trading emotions.', permissions: ['storage'], - // axiom.trade: the site adapter target. localhost:3000: the Nexa backend + // axiom.trade: the site adapter target. localhost:8080: the Nexa backend // (dev only — swap/extend for the real backend host before shipping). - host_permissions: ['https://axiom.trade/*', 'http://localhost:3000/*'], + // Not 3000 — that's this extension's own Vite dev server port. + host_permissions: ['https://axiom.trade/*', 'http://localhost:8080/*'], browser_specific_settings: { gecko: { // Placeholder id for local/dev builds; replace before publishing to AMO. From 56a2ff993032866c3c40e7cfa12d726332f493f2 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 7 Sep 2026 12:20:17 +0200 Subject: [PATCH 08/14] Replace stub wallet keypair with real Phantom sign-in Auth now goes through an actual connected Solana wallet instead of a locally-generated ed25519 keypair. Phantom's window.solana provider is only reachable from a page's own JS world, not an isolated-world content script, so this adds a second world:'MAIN' content script (wallet-bridge.content.ts + wallet-bridge/inject.ts) that talks to window.solana directly and relays to the isolated world via window CustomEvents (wallet-bridge/relay.ts), matched by request id. wallet-connect.ts orchestrates: try a silent onlyIfTrusted connect on load; if that fails, show an on-page banner (wallet-bridge/banner.ts) whose click handler is what actually calls connect() -- Phantom requires a real user gesture for the approval popup on a first-ever connect, which a click relayed from the extension popup wouldn't count as by the time it reaches the wallet. background/wallet-auth.ts runs the REST auth flow (nonce -> ask the tab's content script to sign it -> verify -> store session token) once a wallet reports connected. ws-client.ts no longer force-retries with a known-bad token on auth failure; it calls onAuthExpired instead (which clears the token and prompts a silent wallet reconnect) and exposes reconnectNow() so background.ts can short-circuit the backoff wait once a fresh token exists. identity.ts and its tweetnacl dependency are gone -- no more stub signer. Untested against real Phantom (no browser automation available this session) -- flagged in CLAUDE.md as needing manual verification, along with a note that world:'MAIN' needs Firefox 128+. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01YXiHuScXrjxBh7yFGAPq3B --- CLAUDE.md | 18 +++-- package-lock.json | 9 +-- package.json | 3 +- src/background/backend-client.ts | 60 ++++------------ src/background/identity.ts | 53 -------------- src/background/wallet-auth.ts | 55 ++++++++++++++ src/background/ws-client.ts | 79 +++++++++++++++------ src/content-scripts/content-index.ts | 5 ++ src/content-scripts/wallet-bridge/banner.ts | 56 +++++++++++++++ src/content-scripts/wallet-bridge/inject.ts | 65 +++++++++++++++++ src/content-scripts/wallet-bridge/relay.ts | 42 +++++++++++ src/content-scripts/wallet-connect.ts | 64 +++++++++++++++++ src/entrypoints/background.ts | 16 ++++- src/entrypoints/wallet-bridge.content.ts | 14 ++++ src/shared/messaging.ts | 10 ++- 15 files changed, 413 insertions(+), 136 deletions(-) delete mode 100644 src/background/identity.ts create mode 100644 src/background/wallet-auth.ts create mode 100644 src/content-scripts/wallet-bridge/banner.ts create mode 100644 src/content-scripts/wallet-bridge/inject.ts create mode 100644 src/content-scripts/wallet-bridge/relay.ts create mode 100644 src/content-scripts/wallet-connect.ts create mode 100644 src/entrypoints/wallet-bridge.content.ts diff --git a/CLAUDE.md b/CLAUDE.md index ed13ba6..6ae69e0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -86,11 +86,22 @@ Note the naming mismatch with the wire protocol below: the frontend's internal ` ## Backend connection (implemented) - `src/shared/config.ts` — `BACKEND_HTTP_URL`/`BACKEND_WS_URL`, currently hardcoded to `localhost:8080` (dev only; `host_permissions` in `wxt.config.ts` must stay in sync with whatever host is configured here). Deliberately not `:3000` — that's this extension's own Vite dev server port (`npm run dev`), and running the backend on the same port breaks the dev popup silently: its script tags point at Vite, but the backend answers instead, so nothing ever renders. If you see a blank popup with `http://localhost:3000/...` script tags in "View Page Source" that 404 or return something unexpected, this port collision is the first thing to check. -- `src/background/identity.ts` — **stub wallet**: a locally-generated ed25519 keypair (via `tweetnacl`), persisted in `browser.storage.local`, used as a stand-in for a real Solana wallet signature. This is deliberately temporary — real wallet integration means bridging into the page's injected `window.solana` provider on axiom.trade (content script + page-context script), which hasn't been built yet. `getIdentity()`/`sign()` is the seam that swap plugs into. -- `src/background/backend-client.ts` — the REST auth flow (`POST /auth/nonce` → sign → `POST /auth/verify` → session token), persisted via `getSessionToken()`. -- `src/background/ws-client.ts` — the WS client described above: connects to `/ws?token=...`, reconnects with the protocol's suggested backoff, force-refreshes the session token on `4001`/`auth_expired` before retrying. +- `src/background/backend-client.ts` — session token storage only (`getSessionToken()`/`storeSessionToken()`/`clearSessionToken()`). Getting a token in the first place is `wallet-auth.ts`'s job. +- `src/background/wallet-auth.ts` — runs the REST auth flow (`POST /auth/nonce` → Phantom signature → `POST /auth/verify` → session token) once a content script reports a connected wallet; also `requestWalletReconnect()`, used after the backend invalidates a session. +- `src/background/ws-client.ts` — the WS client described above: connects to `/ws?token=...`. `connectWsClient()` returns a controller with `reconnectNow()` so the background script can short-circuit the backoff wait right after a fresh token arrives. Auth failures (`4001` close, `auth_expired`/`session_revoked` errors) do **not** auto-retry with backoff — they call `onAuthExpired()` instead, since retrying with a known-bad token can't succeed; only real disconnects (network drop, backgrounded browser) use the protocol's suggested backoff schedule. - `src/background/connection-status.ts` — separate from lock state; the popup surfaces this (connecting/connected/disconnected/auth-error) alongside the lock state so a broken connection isn't silently indistinguishable from "unlocked". +### Wallet auth (Phantom) — implemented + +Real wallet signing, not a stub keypair. Phantom (and any wallet injecting a compatible `window.solana`) is only reachable from a **page's own JS world** — a normal (isolated-world) content script cannot call into it directly, hence the two-content-script bridge below. This is the standard pattern for extensions that need to talk to page-injected wallet providers. + +- `src/entrypoints/wallet-bridge.content.ts` + `src/content-scripts/wallet-bridge/inject.ts` — a **second, `world: 'MAIN'`** content script on axiom.trade (Manifest V3 native main-world injection — no `web_accessible_resources`/dynamic `