From 47f3b44d921d518ef797301df004f12096559c9d Mon Sep 17 00:00:00 2001 From: jianf <2198972886@qq.com> Date: Sun, 16 Aug 2026 09:53:05 +0800 Subject: [PATCH] feat(gui): Electron desktop with embedded core, tray, autostart, win/linux packaging - cmd/gui: Electron shell (Clash-Verge style) embedding the full WebUI 1:1 - embedded llmsproxy core (luajit) with auto-generated profile - key stored in keys[] (non-seed) so no replace-the-key warning - gw_key cookie injection: web UI works without login - side-rail toggles for autostart / silent start - system tray with status + controls, silent start (--silent) - win cross-build (mingw luajit exe + dll) / deb / AppImage via electron-builder - Makefile: build / gui / gui-dist / gui-deb / gui-win targets - README: desktop GUI section - lua(adapter): opencode normalizes non-whitelisted roles to system --- .gitignore | 9 + Makefile | 57 + README.md | 39 +- cmd/gui/build/icon.ico | Bin 0 -> 108461 bytes cmd/gui/build/icon.png | Bin 0 -> 7825 bytes cmd/gui/icon.svg | 11 + cmd/gui/main.js | 663 +++++ cmd/gui/package-lock.json | 4048 ++++++++++++++++++++++++++++ cmd/gui/package.json | 73 + cmd/gui/preload.js | 27 + cmd/gui/renderer/app.js | 198 ++ cmd/gui/renderer/index.html | 225 ++ cmd/gui/renderer/style.css | 598 ++++ cmd/gui/scripts/prepare-core.js | 74 + internal/lua/adapters/opencode.lua | 14 + internal/lua/vm_test.go | 68 + 16 files changed, 6102 insertions(+), 2 deletions(-) create mode 100644 Makefile create mode 100644 cmd/gui/build/icon.ico create mode 100644 cmd/gui/build/icon.png create mode 100644 cmd/gui/icon.svg create mode 100644 cmd/gui/main.js create mode 100644 cmd/gui/package-lock.json create mode 100644 cmd/gui/package.json create mode 100644 cmd/gui/preload.js create mode 100644 cmd/gui/renderer/app.js create mode 100644 cmd/gui/renderer/index.html create mode 100644 cmd/gui/renderer/style.css create mode 100644 cmd/gui/scripts/prepare-core.js diff --git a/.gitignore b/.gitignore index d15f037..2d0ef03 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,12 @@ /runtime.json /master.key *.logmaster.key +# GUI (Electron) +cmd/gui/node_modules/ +cmd/gui/bin/ +cmd/gui/build/gui-dist/ +cmd/build/ +cmd/gui/*.log +# workspace +.pi/ +/build/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6d30256 --- /dev/null +++ b/Makefile @@ -0,0 +1,57 @@ +.PHONY: all build test gui gui-dev gui-dist gui-deb gui-win help clean + +# ModelRouter - single-binary gateway + optional Electron desktop GUI (embedded core) +export BINARY +BINARY=llmsproxy +export BUILD_DIR +BUILD_DIR=build +export GUI_DIR +GUI_DIR=cmd/gui +VERSION ?= $(shell git describe --tags --dirty 2>/dev/null || echo "0.1.0") + +all: build + +# ---- core binary (server users / embedded core) ---- +build: + @mkdir -p $(BUILD_DIR) + CGO_ENABLED=1 go build -tags luajit -trimpath -o $(BUILD_DIR)/$(BINARY) ./cmd/llmsproxy + @echo "Built: $(BUILD_DIR)/$(BINARY) ($(VERSION))" + +# ---- GUI (Electron) ---- +gui: + @cd $(GUI_DIR) && npm start + +gui-dev: + @cd $(GUI_DIR) && npm run dev + +# build the embedded core into the GUI, then run electron-builder +# targets: deb + AppImage (linux), rpm needs system rpmbuild +gui-dist: + @cd $(GUI_DIR) && npm run dist:linux + +# deb only (for local sharing), rpm needs rpmbuild +gui-deb: + @cd $(GUI_DIR) && npm run dist:debian + +# windows nsis installer (needs apt install gcc-mingw-w64-x86-64 + wine for electron-builder) +gui-win: + @cd $(GUI_DIR) && npm run dist:win + +gui-dist-dir: + @cd $(GUI_DIR) && npm run dist:dir + +test: + go test ./... + +clean: + rm -rf $(BUILD_DIR) cmd/gui/build/gui-dist cmd/gui/bin + +help: + @echo "Targets:" + @echo " make build build core binary (luajit)" + @echo " make gui run desktop GUI (dev)" + @echo " make gui-dev run GUI with devtools" + @echo " make gui-dist build deb + AppImage (linux)" + @echo " make gui-deb build deb only" + @echo " make gui-win build Windows nsis (needs mingw + wine)" + @echo " make test run go tests" diff --git a/README.md b/README.md index d5a4f10..6030218 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,14 @@ ## 核心优势 ### 极致轻量 + - **单二进制**:编译后约 10MB,零运行时依赖(仅依赖系统 libc),部署即用 - **极低内存占用**:空闲状态仅 ~15MB RSS,满载并发 100+ 请求时峰值 < 100MB - **零运行时依赖**:纯 Go + LuaJIT 静态链接,无需安装 Python/Node/Java 等运行时 - **启动极快**:冷启动 < 200ms,热重载配置 < 10ms ### 强大的多租户调度能力 + - **多密钥多租户**:支持无限密钥,每个密钥独立角色、模型范围、Token 配额、重置周期 - **AUTO 智能调度**:基于优先级档位的分级调度,同优先级源自动轮询负载均衡,故障自动毫秒级故障转移 - **Token 配额管理**:精确到模型级别的 Token 配额控制,支持小时/周/月/自定义小时周期自动重置 @@ -29,6 +31,7 @@ - **Source-Model 前缀路由**:支持 `source-model`/`source:model`/`source/model` 精确指定上游源 ### 生产级可靠性 + - **热加载配置**:WebUI 修改源/密钥/适配器/AUTO链即时生效,无需重启 - **密钥加密落盘**:AES-256-GCM 加密存储 `api_key`、网关密钥、请求头,master.key 0600 权限保护 - **实时源探测**:`GET {base}/models` 定期探测,毫秒级感知上游状态,不污染调度退避状态 @@ -36,6 +39,7 @@ - **AES-256-GCM 加密存储**:运行时文件敏感字段加密落盘,master.key 0600 权限,支持环境变量注入主密钥 ### 灵活的协议适配 + - **LuaJIT VM**:每个适配器独立 VM + worker 池,安全并发,Lua 脚本热加载无需重启 - **Lua 适配器协议**:`transform_request` / `transform_response` / `transform_stream_chunk` 双向转换 - **动态请求头钩子**:`build_headers(meta)` 支持 HMAC 签名、动态 Header 注入 @@ -297,10 +301,41 @@ zen 按 User-Agent 指纹识别官方客户端并分流——非官方 UA 的请 `meta.app_secret` 对时间戳+URL+请求体哈希做 HMAC 签名并附 `X-App-Sign` 等头。 配好 `sources[].meta.{app_id, app_secret, app_agent}` 即可。 +## 桌面 GUI(Electron) + +可选独立产物,面向内网桌面端用户:**内嵌 ModelRouter 核心**(Clash Verge 形态), +一键启停、系统托盘常驻、开机自启、静默启动,内容区一比一内嵌完整 WebUI +(状态/对话/密钥/优先级/源/适配器 六个页面,免登录)。服务器用户继续使用纯 Go 二进制。 + +### 构建与运行 + +```bash +cd cmd/gui +npm install +make gui # 或 npm run dev —— 开发运行(需先 make build 生成 bin/llmsproxy) +make gui-deb # 本机构建 deb(分享给其它 Linux 用户) +make gui-dist # 构建 deb + AppImage +make gui-win # 构建 Windows nsis 安装包(需先 apt install gcc-mingw-w64-x86-64;electron-builder 用 wine) +``` + +产物在 `cmd/build/gui-dist/`:`ModelRouter-1.0.0.AppImage`、`modelrouter-gui_1.0.0_amd64.deb`、 +`modelrouter-gui Setup 1.0.0.exe`(win,需 mingw 交叉编译核心后)。rpm 需系统 `rpmbuild`。 + +### 特性 + +- **内嵌核心**:自动拉起捆绑的 `llmsproxy`(LuaJIT 版),配置/密钥/adapter 存于 + 用户数据目录 `/profile/`,首次运行自动生成随机 admin key 写入 `keys` + (非 seed)并自注入,WebUI 免登录、不会提示"更换初始密钥"。端口可在设置中更改(默认 8787)。 +- **系统托盘**:内核状态点、开机自启/静默启动开关、启停/重启内核、退出。 +- **静默启动**:`--silent` 参数或设置项,启动仅驻托盘不弹窗;首次运行始终显示窗口。 +- **开机自启**:Windows/macOS 用 `setLoginItemSettings`;Linux 写 + `~/.config/autostart/modelrouter-gui.desktop`,静默开启时自动附加 `--silent`。 + ## 目录 ``` -cmd/llmsproxy # 入口 +cmd/llmsproxy # 入口(纯 Go 服务端) +cmd/gui # Electron 桌面 GUI(内嵌核心 + 托盘 + 打包) internal/config # YAML 配置加载/校验 internal/lua # LuaJIT VM + worker 池 + AdapterCache + 内置适配器 (embed) internal/provider # Provider(HTTP) + Registry(路由) @@ -315,4 +350,4 @@ go test -tags luajit ./... ``` 覆盖:配置校验、适配器加载/变换、签名钩子、Gateway 鉴权、SDK 结算、SSE 流式、 -模型路由、多模态透传与 disable_thinking。 \ No newline at end of file +模型路由、多模态透传与 disable_thinking。 diff --git a/cmd/gui/build/icon.ico b/cmd/gui/build/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..1353e34ef57169317244076e11476b32926f3382 GIT binary patch literal 108461 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 literal 0 HcmV?d00001 diff --git a/cmd/gui/build/icon.png b/cmd/gui/build/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1cfc70fe941e521109ebd25ea8e9f4219c2208fb GIT binary patch literal 7825 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 Nj+ + + + + + + + + + M + diff --git a/cmd/gui/main.js b/cmd/gui/main.js new file mode 100644 index 0000000..9fb3721 --- /dev/null +++ b/cmd/gui/main.js @@ -0,0 +1,663 @@ +// ModelRouter Desktop GUI — main process +// Clash-Verge-style: bundles and manages an embedded llmsproxy core instance +// in a per-user profile dir (userData), auto-starts it, keeps a tray icon, +// supports launch-at-login. Server-side users keep using the pure Go binary. +const { + app, + BrowserWindow, + ipcMain, + Menu, + Tray, + shell, + nativeImage, +} = require("electron"); +const path = require("path"); +const fs = require("fs"); +const http = require("http"); +const os = require("os"); +const crypto = require("crypto"); +const { spawn } = require("child_process"); + +const LOG_FILE = path.join(app.getPath("userData"), "gui.log"); +function log(msg) { + try { + fs.appendFileSync(LOG_FILE, new Date().toISOString() + " " + msg + "\n"); + } catch (e) {} +} +const _consoleLog = console.log, + _consoleErr = console.error; +console.log = function () { + log(Array.prototype.slice.call(arguments).join(" ")); + _consoleLog.apply(null, arguments); +}; +console.error = function () { + log("ERR " + Array.prototype.slice.call(arguments).join(" ")); + _consoleErr.apply(null, arguments); +}; + +// ---------- embedded core profile ---------- +// Per-user profile dir: /profile +const PROFILE_DIR = path.join(app.getPath("userData"), "profile"); +const CONFIG_FILE = path.join(PROFILE_DIR, "config.yaml"); +// Packaged: extraResource at process.resourcesPath/bin (outside asar, spawnable). +// Dev: cmd/gui/bin, populated by prepare-core.js. +const CORE_EXE = path.join( + app.isPackaged ? process.resourcesPath : __dirname, + "bin", + process.platform === "win32" ? "llmsproxy.exe" : "llmsproxy", +); +// default port for the embedded core (configurable via settings) +const DEFAULT_PORT = 8787; + +let mainWindow = null; +let tray = null; +let coreProc = null; +let coreStartedAt = 0; +let settings = {}; // { port, autoStart, minimizeToTray } +let authRules = []; + +const isDev = process.argv.includes("--dev"); +const silentFlag = process.argv.includes("--silent"); + +function loadSettings() { + try { + const p = path.join(app.getPath("userData"), "settings.json"); + if (fs.existsSync(p)) settings = JSON.parse(fs.readFileSync(p, "utf-8")); + } catch (e) {} + if (typeof settings.port !== "number") settings.port = DEFAULT_PORT; + if (typeof settings.silentStart !== "boolean") settings.silentStart = false; + if (typeof settings.minimizeToTray !== "boolean") + settings.minimizeToTray = true; + return settings; +} +function saveSettings() { + try { + fs.writeFileSync( + path.join(app.getPath("userData"), "settings.json"), + JSON.stringify(settings, null, 2), + "utf-8", + ); + } catch (e) {} +} + +// read admin key from embedded config.yaml (generated by core first run) +function readAdminKey() { + // The embedded profile stores its admin key in `keys` (NOT gateway_keys): + // gateway_keys entries get marked seed:true by the core (-> "replace the + // initial key" warning in the web UI), while a plain keys entry authenticates + // without any seed warning. + try { + if (!fs.existsSync(CONFIG_FILE)) return ""; + const raw = fs.readFileSync(CONFIG_FILE, "utf-8"); + // keys: + // - key: sk-gw-... + const k = /^\s*keys:\s*\n(?:\s*-\s*key:\s*"?([^\s"#]+)"?[^\n]*\n)+/m.exec( + raw, + ); + if (k && k[1]) return k[1]; + // legacy: gateway_keys list (pre-migration configs) + const g = /^\s*gateway_keys:\s*\n(?:\s*-\s*"?([^\s"#]+)"?\s*\n)+/m.exec( + raw, + ); + if (g && g[1]) return g[1]; + const g2 = /gateway_keys:[^[\n]*\[\s*"?([^\s"\]]+)"?/m.exec(raw); + if (g2 && g2[1]) return g2[1]; + } catch (e) {} + return ""; +} + +const embeddedBaseUrl = () => + "http://127.0.0.1:" + (settings.port || DEFAULT_PORT); + +// ---------- embedded core lifecycle ---------- +function isCoreRunning() { + return coreProc !== null && coreProc.exitCode === null; +} + +function coreStarted() { + return coreStartedAt > 0 && isCoreRunning(); +} + +function writeDefaultEmbeddedConfig() { + // Generate a self-contained embedded profile with a fresh random admin key. + // (Like Clash Verge generates its profile; the core's EnsureDefault default + // is 127.0.0.1:8080 — we want our own port + key on first run.) If a config + // already exists we do NOT overwrite it (keep user's edits). + if (fs.existsSync(CONFIG_FILE)) return; + const key = "sk-gw-" + crypto.randomBytes(16).toString("hex"); + const yaml = [ + "# ModelRouter embedded profile (auto-generated)", + "listen: 127.0.0.1:" + (settings.port || DEFAULT_PORT), + "default_model: AUTO", + "adapter_dir: " + JSON.stringify(path.join(PROFILE_DIR, "adapters")), + "runtime_file: " + JSON.stringify(path.join(PROFILE_DIR, "runtime.json")), + "", + "sources:", + " - name: zen", + " base_url: https://opencode.ai/zen/v1", + " api_key: public", + " adapter: opencode", + " models:", + " - id: deepseek-v4-flash-free", + " priority: 100", + " kind: chat", + "", + // Admin key as a plain `keys` entry (no seed flag) so the web UI never + // shows the replace-the-initial-key warning. gateway_keys entries are + // marked seed:true by the core. + "keys:", + " - key: " + key, + " role: admin", + " name: embedded", + ].join("\n"); + try { + fs.mkdirSync(PROFILE_DIR, { recursive: true }); + fs.writeFileSync(CONFIG_FILE, yaml, "utf-8"); + } catch (e) { + console.error("write embedded config failed:", e); + } +} + +// does the embedded core answer already? returns its admin key if so +function probeEmbedded() { + // Use the admin key from the embedded profile so an up core that requires + // auth (401 without key) is still detected as healthy. Any HTTP response + // (even 401/200) proves the listener is up; network errors mean it is not. + return new Promise((resolve) => { + const u = embeddedBaseUrl(); + const key = readAdminKey(); + const headers = key ? { Authorization: "Bearer " + key } : {}; + const req = http.get(u + "/v1/models", { headers }, (res) => { + let body = ""; + res.on("data", (c) => (body += c)); + res.on("end", () => resolve({ ok: true, status: res.statusCode, body })); + }); + req.on("error", () => resolve({ ok: false })); + req.setTimeout(1200, () => { + req.destroy(); + resolve({ ok: false }); + }); + }); +} + +function startCore() { + if (coreProc && coreProc.exitCode === null) { + console.log("core already running"); + return; + } + if (!fs.existsSync(CORE_EXE)) { + console.error("embedded core binary missing:", CORE_EXE); + return; + } + // ensure profile dir + config + local adapters exist + fs.mkdirSync(PROFILE_DIR, { recursive: true }); + if (!fs.existsSync(CONFIG_FILE)) writeDefaultEmbeddedConfig(); + + coreProc = spawn(CORE_EXE, ["-config", CONFIG_FILE], { + cwd: PROFILE_DIR, + env: Object.assign({}, process.env, { + LLMS_PROXY_GUI: "1", + }), + stdio: ["ignore", "pipe", "pipe"], + }); + coreStartedAt = Date.now(); + // capture core logs -> gui log + coreProc.stdout.on("data", (d) => log("[core] " + String(d).trim())); + coreProc.stderr.on("data", (d) => log("[core-err] " + String(d).trim())); + coreProc.on("error", (err) => { + console.error("core spawn error:", err.message); + coreProc = null; + }); + coreProc.on("exit", (code) => { + const wasManaged = coreStartedAt > 0; + coreStartedAt = 0; + coreProc = null; + console.log("core exited:", code); + // auto-restart if still running GUI (crash recovery, max every ~5s) + if (wasManaged && !app.isQuitting) { + const since = Date.now() - (coreLastExit || 0); + if (since < 2000) return; // avoid tight crash loop + coreLastExit = Date.now(); + setTimeout(() => { + if (!app.isQuitting) startCore(); + }, 1500); + } + }); + notifyCoreState(); + // pump until the core answers, then inject the admin key and tell the + // renderer the web UI is ready (the iframe must load only after auth is set) + waitForCoreReady() + .then((ok) => { + if (ok) { + setEmbeddedAuth(); + notifyCoreState(); + } + }) + .catch(() => {}); +} + +// waitForCoreReady polls probeEmbedded until the listener is up (or timeout). +function waitForCoreReady(timeoutMs = 10000) { + const start = Date.now(); + return new Promise((resolve) => { + const poll = () => { + probeEmbedded().then((p) => { + if (p.ok) return resolve(true); + if (Date.now() - start > timeoutMs) return resolve(false); + setTimeout(poll, 250); + }); + }; + poll(); + }); +} + +let coreLastExit = 0; + +function stopCore() { + app.isQuitting = true; + if (coreProc && coreProc.exitCode === null) { + try { + coreProc.kill(); + } catch (e) {} + } + coreProc = null; + coreStartedAt = 0; + notifyCoreState(); +} + +function restartCore() { + app.isQuitting = true; + if (coreProc && coreProc.exitCode === null) { + try { + coreProc.kill(); + } catch (e) {} + } + coreProc = null; + coreStartedAt = 0; + app.isQuitting = false; + setTimeout(() => startCore(), 400); +} + +// ---------- auth rule injection (embedded only) ---------- +function installAuthRule() { + // Read the admin key live on every request so injection never depends on + // ordering: as soon as the embedded core has written its config (key exists) + // the web UI gets authenticated, even if the renderer loaded early. + const apply = (details, callback) => { + const url = details.url || ""; + const rule = authRules.find((r) => r.url && url.indexOf(r.url) === 0); + if (!rule) return callback({ requestHeaders: details.requestHeaders }); + const h = Object.assign({}, details.requestHeaders); + const key = rule.apiKey || readAdminKey() || ""; + // ModelRouter auth() checks gw_key cookie first; Chromium lets us inject + // Cookie on top-level nav AND cross-origin XHR/fetch, which the CORS- + // restricted Authorization header cannot do inside a sandboxed iframe. + if (key && !/(^|;)\s*gw_key=/.test(h.Cookie || "")) + h["Cookie"] = (h["Cookie"] ? h["Cookie"] + "; " : "") + "gw_key=" + key; + if (rule.cookie) + h["Cookie"] = (h["Cookie"] ? h["Cookie"] + "; " : "") + rule.cookie; + if (rule.headers) + Object.keys(rule.headers).forEach((k) => (h[k] = rule.headers[k])); + callback({ requestHeaders: h }); + }; + require("electron").session.defaultSession.webRequest.onBeforeSendHeaders( + (details, callback) => { + try { + apply(details, callback); + } catch (e) { + callback({ requestHeaders: details.requestHeaders }); + } + }, + ); + require("electron").session.defaultSession.webRequest.onHeadersReceived( + (details, callback) => { + const h = Object.assign({}, details.responseHeaders || {}); + const url = details.url || ""; + if (authRules.some((r) => r.url && url.indexOf(r.url) === 0)) { + h["Access-Control-Allow-Origin"] = ["*"]; + h["Access-Control-Allow-Methods"] = ["GET,POST,PUT,DELETE,OPTIONS"]; + h["Access-Control-Allow-Headers"] = [ + "Content-Type, X-API-Key, Authorization, Cookie, Range", + ]; + } + callback({ responseHeaders: h }); + }, + ); +} + +function setEmbeddedAuth() { + authRules = []; + authRules.push({ url: embeddedBaseUrl(), apiKey: readAdminKey() || "" }); +} + +// ---------- tray ---------- +function createTray() { + // Tray icon: prefer the packaged asset (inside asar) or the dev build dir. + // GNOME renders tray icons via AppIndicator: a fully transparent PNG with + // only colored pixels draws as an odd box, so ship an opaque rounded-blue + // M icon with a subtle white ring for dark/light trays alike. + const candidates = [ + path.join(__dirname, "build", "icon.png"), + path.join(process.resourcesPath || "", "build", "icon.png"), + ]; + let img = null; + for (const c of candidates) { + try { + const t = nativeImage.createFromPath(c); + if (!t.isEmpty()) { + img = t; + break; + } + } catch (e) {} + } + if (!img) { + // clear blue-M fallback (never the transparent fabric pixel) + img = nativeImage.createFromDataURL( + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAWCAYAAADEtGw7AAAACXBIWXMAAAsTAAALEwEAmpwYAAABkUlEQVQ4jb2Vz0sCQRTH/S+yP8Ydkg7RKRD6FwpzxiWoQyL+Awohab8g6RD04yBeIuhQt7pVkCeFLv4Hzdtf7vhiZnHTXVzUtR485u3j7Wdn38x8J5H4L0vpfFnLASOU1wiD02jnR4QCle9EQgkFnVBuEAY4m3PQGOQnQ9mswIBToKHfJ3PNNDxzkuVJH6x6Ghvqe+4XzHg9WJCvWli68HyjYPh5GQ/zsibcDl4bAUMjWPDWcXFo9abj52tN28+/d0UILFlTgcUA8bXt+vmXtqtyscHdnkDTRlzdBUzrgGANsNMT8cF3z301sqqJuUNTxbdP/fjgwrmlxssHBxv3jooPzqz44M2Sgdwc4OeXwI+uUK3IFI344EzRUAvmCkTH9RZSbrmFgE9aXgukHbecWcC8HgXerniLJm2rbEaCxw6IFI9gQfnaxqtHB9f3DVzRQcXS5ZZb2/OeKzd2CJyisDMuQoxDXJ3QKOdp9r00pnAag/xCBSigyVR+dZ6ZToT68CxPyiJ17UxzNcnaUQ3+a/sBfMAWejRz3/oAAAAASUVORK5CYII=", + ); + } + tray = new Tray(img); + const rebuild = () => { + const running = coreStarted(); + const menu = Menu.buildFromTemplate([ + { + label: "ModelRouter Desktop", + enabled: false, + }, + { + label: running ? "● 内核运行中" : "○ 内核已停止", + enabled: false, + }, + { type: "separator" }, + { + label: "显示主窗口", + click: () => { + if (mainWindow) { + mainWindow.show(); + mainWindow.focus(); + } + }, + }, + { type: "separator" }, + { + label: "开启内核 (核心网关启动)", + enabled: !running, + click: () => startCore(), + }, + { + label: "重启内核", + enabled: running, + click: () => restartCore(), + }, + { + label: "停止内核", + enabled: running, + click: () => stopCore(), + }, + { type: "separator" }, + { + label: "开机自启", + type: "checkbox", + checked: !!settings.autoStart, + click: () => { + settings.autoStart = !settings.autoStart; + applyAutoStart(); + saveSettings(); + rebuild(); + }, + }, + { + label: "静默启动", + type: "checkbox", + checked: !!settings.silentStart, + click: () => { + settings.silentStart = !settings.silentStart; + saveSettings(); + rebuild(); + }, + }, + { type: "separator" }, + { + label: "退出", + click: () => app.quit(), + }, + ]); + tray.setContextMenu(menu); + }; + tray.setToolTip("ModelRouter"); + tray.on("click", () => { + if (mainWindow) { + mainWindow.isVisible() ? mainWindow.focus() : mainWindow.show(); + } + }); + rebuild(); + global.__rebuildTray = rebuild; + // keep a weak ref; main process keeps tray alive +} + +function notifyCoreState() { + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send("core:state", { + running: coreStarted(), + startedAt: coreStartedAt, + }); + } + try { + if (global.__rebuildTray) global.__rebuildTray(); + } catch (e) {} +} + +// ---------- auto start / launch at login ---------- +function setupAutoStart() { + if (settings.autoStart === undefined) settings.autoStart = true; + applyAutoStart(); +} +function applyAutoStart() { + const on = !!settings.autoStart; + if (process.platform === "linux") { + // Linux: explicit XDG autostart desktop entry (app.setLoginItemSettings + // is a no-op on Linux). + const d = path.join( + os.homedir(), + ".config", + "autostart", + "modelrouter-gui.desktop", + ); + try { + if (on) { + const exe = process.execPath; + fs.mkdirSync(path.dirname(d), { recursive: true }); + fs.writeFileSync( + d, + '[Desktop Entry]\nType=Application\nName=ModelRouter\nComment=Unified LLM gateway\nExec="' + + exe + + '" --no-sandbox' + + (settings.silentStart ? " --silent" : "") + + "\nTerminal=false\nX-GNOME-Autostart-enabled=true\n", + "utf-8", + ); + } else if (fs.existsSync(d)) { + fs.unlinkSync(d); + } + } catch (e) { + console.warn("linux autostart failed:", e.message); + } + return; + } + try { + app.setLoginItemSettings({ openAtLogin: on }); + } catch (e) { + console.warn("auto-start not supported:", e.message); + } +} + +// ---------- window ---------- +function createWindow() { + const menu = Menu.buildFromTemplate([]); + Menu.setApplicationMenu(menu); + mainWindow = new BrowserWindow({ + width: 1280, + height: 860, + minWidth: 900, + minHeight: 600, + title: "ModelRouter", + frame: false, + show: false, + webPreferences: { + preload: path.join(__dirname, "preload.js"), + contextIsolation: true, + nodeIntegration: false, + }, + }); + mainWindow.loadFile(path.join(__dirname, "renderer", "index.html")); + mainWindow.webContents.setWindowOpenHandler(({ url }) => { + if (url && url.startsWith("http")) shell.openExternal(url); + return { action: "deny" }; + }); + // Silent start: `--silent` CLI flag or settings.silentStart keeps the app + // in the tray without showing the window (Clash-Verge style). First run + // always shows the window so the user sees the dashboard. + mainWindow.once("ready-to-show", () => { + const firstRun = !fs.existsSync( + path.join(app.getPath("userData"), "settings.json"), + ); + if (firstRun || !(silentFlag || settings.silentStart)) { + mainWindow.show(); + } + }); + mainWindow.on("close", (e) => { + if (settings.minimizeToTray && !app.isQuitting) { + e.preventDefault(); + mainWindow.hide(); + tray && + tray.displayBalloon && + tray.displayBalloon({ + title: "ModelRouter", + content: "已最小化到托盘,内核继续运行", + }); + } + }); + mainWindow.on("closed", () => (mainWindow = null)); + if (isDev) mainWindow.webContents.openDevTools(); +} + +// ---------- IPC ---------- +ipcMain.handle("window:minimize", (e) => + BrowserWindow.fromWebContents(e.sender)?.minimize(), +); +ipcMain.handle("window:toggleMaximize", (e) => { + const w = BrowserWindow.fromWebContents(e.sender); + if (!w) return; + if (w.isMaximized()) w.unmaximize(); + else w.maximize(); +}); +ipcMain.handle("window:close", (e) => + BrowserWindow.fromWebContents(e.sender)?.close(), +); +ipcMain.handle( + "window:isMaximized", + (e) => !!BrowserWindow.fromWebContents(e.sender)?.isMaximized(), +); + +ipcMain.handle("core:state", () => ({ + running: coreStarted(), + startedAt: coreStartedAt, + baseUrl: embeddedBaseUrl(), + port: settings.port || DEFAULT_PORT, + profileDir: PROFILE_DIR, + configFile: CONFIG_FILE, + coreExe: CORE_EXE, + coreExists: fs.existsSync(CORE_EXE), +})); +ipcMain.handle("core:start", () => { + app.isQuitting = false; + startCore(); + setTimeout(() => setEmbeddedAuth(), 400); + return coreStarted(); +}); +ipcMain.handle("core:stop", () => { + stopCore(); + return true; +}); +ipcMain.handle("core:restart", () => { + restartCore(); + setTimeout(() => setEmbeddedAuth(), 500); + return true; +}); +ipcMain.handle("core:probe", async () => { + const p = await probeEmbedded(); + return p; +}); +ipcMain.handle("core:key", () => readAdminKey()); + +ipcMain.handle("settings:get", () => loadSettings()); +ipcMain.handle("settings:set", (_, patch) => { + Object.assign(settings, patch); + // port change -> rewrite ONLY the listen line, preserving sources/keys/etc. + if (typeof patch.port === "number" && patch.port !== DEFAULT_PORT) { + try { + if (!fs.existsSync(CONFIG_FILE)) writeDefaultEmbeddedConfig(); + let raw = fs.readFileSync(CONFIG_FILE, "utf-8"); + raw = raw.replace(/^listen:.*$/m, "listen: 127.0.0.1:" + patch.port); + fs.writeFileSync(CONFIG_FILE, raw, "utf-8"); + } catch (e) { + console.error("port rewrite failed:", e); + } + if (coreStarted()) restartCore(); + } + if (typeof patch.autoStart === "boolean") applyAutoStart(); + saveSettings(); + setTimeout(() => setEmbeddedAuth(), 300); + return settings; +}); +ipcMain.handle("settings:logFile", () => LOG_FILE); + +// open paths in file manager +ipcMain.handle("shell:openPath", (_, p) => { + shell.openPath(p); + return true; +}); + +// window control from renderer uses preload -> ipcMain above + +// ---------- bootstrap ---------- +const gotLock = app.requestSingleInstanceLock(); +if (gotLock) { + app.on("second-instance", () => { + if (mainWindow) { + if (mainWindow.isMinimized()) mainWindow.restore(); + mainWindow.show(); + mainWindow.focus(); + } + }); + + app.whenReady().then(() => { + installAuthRule(); + loadSettings(); + setupAutoStart(); + setEmbeddedAuth(); + createWindow(); + createTray(); + + // start embedded core if not already running (another instance of llmsproxy) + setTimeout(async () => { + const p = await probeEmbedded(); + if (!p.ok) { + startCore(); + } + }, 600); + }); + + app.on("activate", () => { + if (mainWindow === null) createWindow(); + }); + app.on("before-quit", () => { + app.isQuitting = true; + stopCore(); + try { + if (tray) tray.destroy(); + } catch (e) {} + }); +} else { + app.quit(); +} diff --git a/cmd/gui/package-lock.json b/cmd/gui/package-lock.json new file mode 100644 index 0000000..ab1aced --- /dev/null +++ b/cmd/gui/package-lock.json @@ -0,0 +1,4048 @@ +{ + "name": "modelrouter-gui", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "modelrouter-gui", + "version": "1.0.0", + "license": "Proprietary", + "devDependencies": { + "electron": "^33.0.0", + "electron-builder": "^26.15.3" + } + }, + "node_modules/@electron/asar": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", + "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "asar": "bin/asar.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@electron/asar/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@electron/asar/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@electron/asar/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@electron/fuses": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@electron/fuses/-/fuses-1.8.0.tgz", + "integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.1", + "fs-extra": "^9.0.1", + "minimist": "^1.2.5" + }, + "bin": { + "electron-fuses": "dist/bin.js" + } + }, + "node_modules/@electron/fuses/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/fuses/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/fuses/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/get": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", + "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/@electron/notarize": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz", + "integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.1", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/notarize/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/notarize/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/notarize/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/osx-sign": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.3.tgz", + "integrity": "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "compare-version": "^0.1.2", + "debug": "^4.3.4", + "fs-extra": "^10.0.0", + "isbinaryfile": "^4.0.8", + "minimist": "^1.2.6", + "plist": "^3.0.5" + }, + "bin": { + "electron-osx-flat": "bin/electron-osx-flat.js", + "electron-osx-sign": "bin/electron-osx-sign.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@electron/osx-sign/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/@electron/osx-sign/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/osx-sign/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/rebuild": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.2.0.tgz", + "integrity": "sha512-RKL/O+jGoXJMxrx/5771y1n0xTKmFuOYGO3gMmwypBM6rsH0kou0mswwdXA2JrhIkE4xyC7v9vGk0n6NPzgOxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.1.1", + "node-abi": "^4.2.0", + "node-api-version": "^0.2.1", + "node-gyp": "^12.2.0", + "read-binary-file-arch": "^1.0.6" + }, + "bin": { + "electron-rebuild": "lib/cli.js" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@electron/universal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.3.tgz", + "integrity": "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "^3.3.1", + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.3.1", + "dir-compare": "^4.2.0", + "fs-extra": "^11.1.1", + "minimatch": "^9.0.3", + "plist": "^3.1.0" + }, + "engines": { + "node": ">=16.4" + } + }, + "node_modules/@electron/universal/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@electron/universal/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@electron/universal/node_modules/fs-extra": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", + "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/universal/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/universal/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@electron/universal/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/windows-sign": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz", + "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "dependencies": { + "cross-dirname": "^0.1.0", + "debug": "^4.3.4", + "fs-extra": "^11.1.1", + "minimist": "^1.2.8", + "postject": "^1.0.0-alpha.6" + }, + "bin": { + "electron-windows-sign": "bin/electron-windows-sign.js" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/windows-sign/node_modules/fs-extra": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", + "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/windows-sign/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/windows-sign/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@malept/cross-spawn-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", + "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/@malept/flatpak-bundler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", + "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "tmp-promise": "^3.0.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@noble/hashes": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.3.0.tgz", + "integrity": "sha512-oN+QwyX7VSHotibwubG3kpzbwKrfnyR6OOO+3Nk/53ADL7FmgHHz4TgrbaYKvvOw09u6QTx0oiH1cNCIOuN0CQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.9.0.tgz", + "integrity": "sha512-AKvPMOM7LfK0uFe1m7o7+veOa8xQGPqsqOrKi3QKgCElzwjGp39mbhr2g7mt/v/mXQHiIvJmDj5cJS173x8Q9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/json-schema": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@peculiar/utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/webcrypto": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.7.1.tgz", + "integrity": "sha512-ODOov0sGMJMf3jPonOkgGqPknTsu+DdQ7kD++gz8aI+aFMOMHFbWAA2taqXXVTdP+OTOQR/znGvSpmkeI0WTYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/json-schema": "^1.1.12", + "@peculiar/utils": "^2.0.2", + "tslib": "^2.8.1", + "webcrypto-core": "^1.9.2" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.14", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.14.tgz", + "integrity": "sha512-T4EDRUBVZYRldYApjEJiU0e1stYWaRAX7CuSnKzrpwdZKo53zGV8/pqfzV6FfwNl9YThD2OumQYvqtvjvgG7aQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "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/app-builder-lib": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.15.3.tgz", + "integrity": "sha512-2VnyWkqsP5v5XbBhL3tD5Syx8iNPBYsoU7kY4S2fz7wg8Rj/nztWKCUzGKaFRTv0Xwf3/H058CR1Kvtd/3lRow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "3.4.1", + "@electron/fuses": "^1.8.0", + "@electron/get": "^3.0.0", + "@electron/notarize": "2.5.0", + "@electron/osx-sign": "1.3.3", + "@electron/rebuild": "^4.0.4", + "@electron/universal": "2.0.3", + "@malept/flatpak-bundler": "^0.4.0", + "@noble/hashes": "^2.2.0", + "@peculiar/webcrypto": "^1.7.1", + "@types/fs-extra": "9.0.13", + "ajv": "^8.18.0", + "asn1js": "^3.0.10", + "async-exit-hook": "^2.0.1", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chromium-pickle-js": "^0.2.0", + "ci-info": "4.3.1", + "debug": "^4.3.4", + "dotenv": "^16.4.5", + "dotenv-expand": "^11.0.6", + "ejs": "^3.1.8", + "electron-publish": "26.15.3", + "fs-extra": "^10.1.0", + "hosted-git-info": "^4.1.0", + "isbinaryfile": "^5.0.0", + "jiti": "^2.4.2", + "js-yaml": "^4.1.0", + "json5": "^2.2.3", + "lazy-val": "^1.0.5", + "minimatch": "^10.2.5", + "pkijs": "^3.4.0", + "plist": "3.1.0", + "proper-lockfile": "^4.1.2", + "resedit": "^1.7.0", + "semver": "~7.7.3", + "tar": "^7.5.7", + "temp-file": "^3.4.0", + "tiny-async-pool": "1.3.0", + "unzipper": "^0.12.3", + "which": "^5.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "dmg-builder": "26.15.3", + "electron-builder-squirrel-windows": "26.15.3" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-3.1.0.tgz", + "integrity": "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/app-builder-lib/node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/app-builder-lib/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/app-builder-lib/node_modules/fs-extra/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/app-builder-lib/node_modules/fs-extra/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/asn1js": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.5", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/builder-util": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-26.15.3.tgz", + "integrity": "sha512-q2hn7Mbo2nFNkVekPiHFx6Nfo3hURmES3tfBn+k5Pqxl2RkmP3QGqZUhH/q9Pch/4G05NRhPjDlVj1O8q4Txvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.6", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.6", + "debug": "^4.3.4", + "fs-extra": "^10.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "js-yaml": "^4.1.0", + "sanitize-filename": "^1.6.3", + "source-map-support": "^0.5.19", + "stat-mode": "^1.0.0", + "temp-file": "^3.4.0", + "tiny-async-pool": "1.3.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/builder-util-runtime": { + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.7.0.tgz", + "integrity": "sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/builder-util/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/builder-util/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/builder-util/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/bytestreamjs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/chromium-pickle-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-dirname": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz", + "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/dir-compare": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", + "integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimatch": "^3.0.5", + "p-limit": "^3.1.0 " + } + }, + "node_modules/dir-compare/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/dir-compare/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/dir-compare/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/dmg-builder": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.15.3.tgz", + "integrity": "sha512-O3zJUFUYHJKgzPqioHxfxzBzlSC1eXCSr79gMSBKBP5AgjjpmrydMsMLotEg9fAJF36vdUncb+4ndRNxoPdlSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "fs-extra": "^10.1.0", + "js-yaml": "^4.1.0" + } + }, + "node_modules/dmg-builder/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dmg-builder/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/dmg-builder/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron": { + "version": "33.4.11", + "resolved": "https://registry.npmjs.org/electron/-/electron-33.4.11.tgz", + "integrity": "sha512-xmdAs5QWRkInC7TpXGNvzo/7exojubk+72jn1oJL7keNeIlw7xNglf8TGtJtkR4rWC5FJq0oXiIXPS9BcK2Irg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@electron/get": "^2.0.0", + "@types/node": "^20.9.0", + "extract-zip": "^2.0.1" + }, + "bin": { + "electron": "cli.js" + }, + "engines": { + "node": ">= 12.20.55" + } + }, + "node_modules/electron-builder": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.15.3.tgz", + "integrity": "sha512-a1KM5heqS3gQCZzizXEI8RjJy3QVogULPdeSknt76uLDpBIW/HDGsMg/XgP0riP6PI9COsRvFITKKGDqA8fJxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "dmg-builder": "26.15.3", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "simple-update-notifier": "2.0.0", + "yargs": "^17.6.2" + }, + "bin": { + "electron-builder": "cli.js", + "install-app-deps": "install-app-deps.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/electron-builder-squirrel-windows": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.15.3.tgz", + "integrity": "sha512-Jc19XPV9y9+2bAdZPkXuVNGNIEFBq9poHC61l8Kv6FdK7DRG3+Ic0rerC0DXOaeHNz8yW0fg/JnF8GQROOF5MA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "electron-winstaller": "5.4.0" + } + }, + "node_modules/electron-builder/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-builder/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-builder/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-publish": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.15.3.tgz", + "integrity": "sha512-g/2bn8YTavY4cuS5F+jOS7zmZbXXBV8KZ8yHKfJjFPoKtzBqrpCdNPxBd3tqdBwP7BVd0lGzf7Bk2s0KesWZ4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^9.0.11", + "aws4": "^1.13.2", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "form-data": "^4.0.5", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "mime": "^2.5.2" + } + }, + "node_modules/electron-publish/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-publish/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-publish/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-winstaller": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz", + "integrity": "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@electron/asar": "^3.2.1", + "debug": "^4.1.1", + "fs-extra": "^7.0.1", + "lodash": "^4.17.21", + "temp": "^0.9.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "@electron/windows-sign": "^1.1.2" + } + }, + "node_modules/electron-winstaller/node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "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/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "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": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "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/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/global-agent/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "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/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isbinaryfile": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", + "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "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-yaml": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC", + "optional": true + }, + "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/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lazy-val": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-abi": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.33.0.tgz", + "integrity": "sha512-vLBWCKb+7LWsX+TbfzWOkw0W81m377tyx3hOweBTjO43CXZnRGS1/JPWs20fr0PgZyDXk6ROYrylsEycK8raDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.6.3" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-api-version": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", + "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + } + }, + "node_modules/node-api-version/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.4.0.tgz", + "integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "undici": "^6.25.0", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/node-gyp/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pe-library": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz", + "integrity": "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "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" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkijs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.0.tgz", + "integrity": "sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@noble/hashes": "1.4.0", + "asn1js": "^3.0.6", + "bytestreamjs": "^2.0.1", + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/pkijs/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/plist": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", + "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/postject": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz", + "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "commander": "^9.4.0" + }, + "bin": { + "postject": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/postject/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.2.0.tgz", + "integrity": "sha512-BbubeCEyTuQjVMakvJQ/Sxbc93F2pwmbsxONT/ZRrwU7Ua38d8unYTwXpTVLAKJ4BDuH9IGztCjQcd/N/39Dvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-binary-file-arch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", + "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "bin": { + "read-binary-file-arch": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/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/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resedit": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz", + "integrity": "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pe-library": "^0.4.1" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/sanitize-filename": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.4.tgz", + "integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==", + "dev": true, + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sax": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/stat-mode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", + "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.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/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "7.5.22", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", + "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/temp": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", + "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "mkdirp": "^0.5.1", + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/temp-file": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", + "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-exit-hook": "^2.0.1", + "fs-extra": "^10.0.0" + } + }, + "node_modules/temp-file/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/temp-file/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/temp-file/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/tiny-async-pool": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz", + "integrity": "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^5.5.0" + } + }, + "node_modules/tiny-async-pool/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "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/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici": { + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz", + "integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unzipper": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.5.tgz", + "integrity": "sha512-tXYOi9R57Uj/2Z25SOs5RRSzq886MBQj2gY8dPL+xl/kv6s6SvByoKfAtvfVeEuhntWDgjd2o9p2lb4TVPAz0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "~3.7.2", + "duplexer2": "~0.1.4", + "fs-extra": "11.3.1", + "graceful-fs": "^4.2.2", + "node-int64": "^0.4.0" + } + }, + "node_modules/unzipper/node_modules/fs-extra": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz", + "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/unzipper/node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/unzipper/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/webcrypto-core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.9.2.tgz", + "integrity": "sha512-gsXecm82UQNlTBURJGuqOWy1Ww08S3kZUcr3aOJS02Pk0xLtkfeUAVC0u0xhgdonFme80edSJUIJyuvL/7250Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/json-schema": "^1.1.12", + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "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/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "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", + "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" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/cmd/gui/package.json b/cmd/gui/package.json new file mode 100644 index 0000000..68afe84 --- /dev/null +++ b/cmd/gui/package.json @@ -0,0 +1,73 @@ +{ + "name": "modelrouter-gui", + "version": "1.0.0", + "description": "ModelRouter Desktop — embedded ModelRouter core with tray", + "author": "ModelRouter", + "main": "main.js", + "license": "Proprietary", + "scripts": { + "start": "electron . --no-sandbox", + "dev": "electron . --no-sandbox --dev", + "core:linux": "node scripts/prepare-core.js linux", + "core:win": "node scripts/prepare-core.js win", + "dist:linux": "npm run core:linux && electron-builder --linux deb AppImage", + "dist:debian": "npm run core:linux && electron-builder --linux deb", + "dist:win": "npm run core:win && electron-builder --win nsis", + "dist:dir": "npm run core:linux && electron-builder --dir" + }, + "devDependencies": { + "electron": "^33.0.0", + "electron-builder": "^26.15.3" + }, + "build": { + "appId": "com.modelrouter.gui", + "productName": "ModelRouter", + "linux": { + "target": [ + "deb", + "rpm", + "AppImage" + ], + "category": "Network", + "icon": "build/icon.png", + "maintainer": "ModelRouter", + "synopsis": "ModelRouter Desktop", + "extraResources": [ + { + "from": "bin/llmsproxy", + "to": "bin/llmsproxy" + } + ] + }, + "directories": { + "output": "../build/gui-dist" + }, + "mac": { + "target": "dmg", + "icon": "build/icon.png" + }, + "win": { + "target": "nsis", + "icon": "build/icon.ico", + "extraResources": [ + { + "from": "bin/llmsproxy.exe", + "to": "bin/llmsproxy.exe" + }, + { + "from": "bin/lua51.dll", + "to": "bin/lua51.dll" + } + ] + }, + "files": [ + "main.js", + "preload.js", + "renderer/**/*", + "build/**/*", + "!node_modules/**/*" + ], + "asar": true + }, + "homepage": "https://gitcode.com/JianFeeeee/ModelRouter" +} diff --git a/cmd/gui/preload.js b/cmd/gui/preload.js new file mode 100644 index 0000000..c3cfca8 --- /dev/null +++ b/cmd/gui/preload.js @@ -0,0 +1,27 @@ +const { contextBridge, ipcRenderer } = require("electron"); + +contextBridge.exposeInMainWorld("modelrouter", { + win: { + minimize: () => ipcRenderer.invoke("window:minimize"), + toggleMaximize: () => ipcRenderer.invoke("window:toggleMaximize"), + close: () => ipcRenderer.invoke("window:close"), + isMaximized: () => ipcRenderer.invoke("window:isMaximized"), + }, + core: { + state: () => ipcRenderer.invoke("core:state"), + start: () => ipcRenderer.invoke("core:start"), + stop: () => ipcRenderer.invoke("core:stop"), + restart: () => ipcRenderer.invoke("core:restart"), + probe: () => ipcRenderer.invoke("core:probe"), + key: () => ipcRenderer.invoke("core:key"), + onState: (cb) => ipcRenderer.on("core:state", (_e, d) => cb(d)), + }, + settings: { + get: () => ipcRenderer.invoke("settings:get"), + set: (patch) => ipcRenderer.invoke("settings:set", patch), + logFile: () => ipcRenderer.invoke("settings:logFile"), + }, + shell: { + openPath: (p) => ipcRenderer.invoke("shell:openPath", p), + }, +}); diff --git a/cmd/gui/renderer/app.js b/cmd/gui/renderer/app.js new file mode 100644 index 0000000..8f66873 --- /dev/null +++ b/cmd/gui/renderer/app.js @@ -0,0 +1,198 @@ +// ModelRouter Desktop GUI — renderer +// Clash-Verge-style: the content area is a full-screen iframe loading the +// real WebUI 1:1 (status / chat / keys / priority / sources / adapters). +// The shell only adds the titlebar, settings overlay and tray integration. +const state = { + core: { + running: false, + baseUrl: "", + port: 8787, + profileDir: "", + configFile: "", + coreExists: true, + }, + settings: { + port: 8787, + autoStart: true, + silentStart: false, + minimizeToTray: true, + }, + theme: localStorage.getItem("mr-theme") || "light", + loadedOnce: false, +}; + +const $ = (s) => document.querySelector(s); + +function esc(s) { + return String(s == null ? "" : s).replace( + /[&<>"']/g, + (c) => + ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[ + c + ], + ); +} + +function toast(msg, isErr, ms) { + const t = $("#toast"); + t.textContent = msg; + t.className = "show" + (isErr ? " err" : ""); + clearTimeout(t._tm); + t._tm = setTimeout(() => { + t.className = ""; + }, ms || 2600); +} + +const frame = () => $("#webui-frame"); + +// ===== core status ===== +async function refreshCore() { + state.core = await window.modelrouter.core.state(); + renderCoreStatus(); +} + +function renderCoreStatus() { + const core = state.core; + const dot = $("#conn-dot"); + dot.className = "status-dot" + (core.running ? " ok" : " bad"); + dot.title = core.running ? "内核运行中" : "内核未运行"; +} + +function showOffline() { + frame().style.display = "none"; + $("#offline").style.display = "flex"; + $("#console-loading").style.display = "none"; +} + +function showLoading() { + frame().style.display = "none"; + $("#offline").style.display = "none"; + $("#console-loading").style.display = "flex"; +} + +function loadFrame(url) { + const f = frame(); + f.style.display = "block"; + $("#offline").style.display = "none"; + $("#console-loading").style.display = "none"; + f.src = url; + state.loadedOnce = true; +} + +async function ensureCore() { + await refreshCore(); + if (state.core.running) { + loadFrame(state.core.baseUrl); + } else { + showOffline(); + } +} + +// ===== side rail toggles ===== +function renderRail() { + const auto = $("#rail-autostart-dot"); + const sil = $("#rail-silent-dot"); + if (auto) auto.classList.toggle("on", !!state.settings.autoStart); + if (sil) sil.classList.toggle("on", !!state.settings.silentStart); +} + +async function toggleAutoStart() { + const next = !state.settings.autoStart; + state.settings.autoStart = next; + await window.modelrouter.settings.set({ autoStart: next }); + renderRail(); + toast(next ? "开机自启已开启" : "开机自启已关闭"); +} + +async function toggleSilent() { + const next = !state.settings.silentStart; + state.settings.silentStart = next; + await window.modelrouter.settings.set({ silentStart: next }); + renderRail(); + toast(next ? "静默启动已开启" : "静默启动已关闭"); +} + +// ===== settings overlay ===== +async function openSettings() { + state.settings = await window.modelrouter.settings.get(); + $("#set-port").value = state.settings.port ?? 8787; + $("#set-autostart").checked = !!state.settings.autoStart; + $("#set-silent").checked = !!state.settings.silentStart; + $("#set-tray").checked = !!state.settings.minimizeToTray; + $("#settings-overlay").style.display = "flex"; + renderRail(); +} +function closeSettings() { + $("#settings-overlay").style.display = "none"; +} + +async function saveSettings() { + const port = parseInt($("#set-port").value, 10); + const autoStart = $("#set-autostart").checked; + const silentStart = $("#set-silent").checked; + const minimizeToTray = $("#set-tray").checked; + await window.modelrouter.settings.set({ + port, + autoStart, + silentStart, + minimizeToTray, + }); + closeSettings(); + toast("设置已保存"); + await refreshCore(); + if (state.core.running) { + loadFrame(state.core.baseUrl); + } +} + +// ===== init ===== +function init() { + $("#tb-min").onclick = () => window.modelrouter.win.minimize(); + $("#tb-max").onclick = () => window.modelrouter.win.toggleMaximize(); + $("#tb-close").onclick = () => window.modelrouter.win.close(); + $("#tb-settings").onclick = openSettings; + $("#rail-settings").onclick = openSettings; + $("#rail-autostart").onclick = toggleAutoStart; + $("#rail-silent").onclick = toggleSilent; + $("#rail-theme").onclick = () => { + state.theme = state.theme === "dark" ? "light" : "dark"; + applyTheme(); + }; + $("#set-close").onclick = closeSettings; + $("#set-cancel").onclick = closeSettings; + $("#set-save").onclick = saveSettings; + $("#set-dir").onclick = () => + window.modelrouter.shell.openPath(state.core.profileDir); + $("#set-log").onclick = async () => { + const p = await window.modelrouter.settings.logFile(); + window.modelrouter.shell.openPath(p); + }; + $("#off-start").onclick = async () => { + showLoading(); + await window.modelrouter.core.start(); + await refreshCore(); + if (state.core.running) loadFrame(state.core.baseUrl); + else showOffline(); + }; + frame().onload = () => refreshCore(); + window.modelrouter.core.onState((d) => { + state.core = Object.assign({}, state.core, d); + renderCoreStatus(); + if (d.running && !state.loadedOnce) loadFrame(state.core.baseUrl); + if (!d.running) showOffline(); + }); + // Escape closes overlay + document.addEventListener("keydown", (e) => { + if (e.key === "Escape" && $("#settings-overlay").style.display === "flex") + closeSettings(); + }); +} + +async function boot() { + init(); + state.settings = await window.modelrouter.settings.get(); + renderRail(); + await ensureCore(); +} + +boot(); diff --git a/cmd/gui/renderer/index.html b/cmd/gui/renderer/index.html new file mode 100644 index 0000000..f56c1fe --- /dev/null +++ b/cmd/gui/renderer/index.html @@ -0,0 +1,225 @@ + + + + + + + ModelRouter + + + + + + + +
+
+ + ModelRouter + +
+
+ + + + +
+
+ + + + + +
+
+ + + +
+
+ + + + +
+ + + diff --git a/cmd/gui/renderer/style.css b/cmd/gui/renderer/style.css new file mode 100644 index 0000000..30c6418 --- /dev/null +++ b/cmd/gui/renderer/style.css @@ -0,0 +1,598 @@ +/* ===================================================================== + ModelRouter Desktop GUI — Clash-Verge-style shell + Content area = full-screen iframe embedding the real WebUI 1:1. + Palette mirrors the WebUI (NapCat sakura/frost/glassmorphism). + ===================================================================== */ +:root { + --primary: #ff7fac; + --primary-h: #f33b7c; + --primary-50: #fff0f5; + --primary-100: #ffe4e9; + --primary-200: #ffcdd9; + --primary-300: #ff9eb5; + --primary-400: #ff7fac; + --primary-500: #f33b7c; + --secondary: #88c0d0; + --secondary-h: #4c8dae; + --danger: #db3694; + --ok: #17a964; + --warn: #b7791f; + --bg-s1: #eef2ff; + --bg-s2: #ffffff; + --bg-s3: #ffe9f0; + --card: rgba(255, 255, 255, 0.62); + --card-solid: #fff; + --line: rgba(255, 127, 172, 0.18); + --line-strong: rgba(120, 90, 150, 0.22); + --fg: #3b3350; + --muted: #7c7a95; + --blob1: rgba(255, 127, 172, 0.45); + --blob2: rgba(136, 192, 208, 0.42); + --blob3: rgba(244, 114, 182, 0.3); + --sh-sm: 0 1px 2px rgba(70, 50, 110, 0.06); + --sh-md: 0 6px 20px rgba(120, 90, 160, 0.13); + --sh-lg: 0 18px 46px rgba(120, 90, 160, 0.22); + --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); +} +html[data-theme="dark"] { + --primary: #f54180; + --primary-h: #f871a0; + --primary-50: #310413; + --primary-100: #610726; + --primary-200: #920b3a; + --primary-300: #c20e4d; + --primary-400: #f31260; + --primary-500: #f54180; + --secondary: #5e9fbf; + --secondary-h: #88c0d0; + --danger: #db3694; + --ok: #4f7cff; + --warn: #e0b25c; + --bg-s1: #14121c; + --bg-s2: #1a1824; + --bg-s3: #221a28; + --card: rgba(34, 31, 50, 0.55); + --card-solid: #241f38; + --line: rgba(255, 127, 172, 0.16); + --line-strong: rgba(200, 180, 235, 0.18); + --fg: #ece6f5; + --muted: #a09db8; + --blob1: rgba(255, 79, 160, 0.32); + --blob2: rgba(100, 180, 215, 0.26); + --blob3: rgba(219, 54, 148, 0.22); + --sh-sm: 0 1px 2px rgba(0, 0, 0, 0.35); + --sh-md: 0 8px 26px rgba(0, 0, 0, 0.45); + --sh-lg: 0 20px 52px rgba(0, 0, 0, 0.55); +} +* { + box-sizing: border-box; +} +html, +body { + height: 100%; +} +body { + margin: 0; + color: var(--fg); + overflow: hidden; + font: + 13px / 1.5 "Quicksand", + "Nunito", + "Inter", + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + Roboto, + "PingFang SC", + "Microsoft YaHei", + sans-serif; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + background: var(--bg-s1); +} +button { + font: inherit; + cursor: pointer; + border: none; + background: none; + color: inherit; +} +input { + font: inherit; + color: var(--fg); +} +::selection { + background: #ffcdba; + color: #fff; +} +::-webkit-scrollbar { + width: 8px; + height: 8px; +} +::-webkit-scrollbar-thumb { + background: rgba(255, 182, 193, 0.45); + border-radius: 4px; +} + +/* animated gradient-mesh background (NapCat PageBackground) */ +#bgfx { + position: fixed; + inset: 0; + z-index: -10; + overflow: hidden; + background: linear-gradient(135deg, var(--bg-s1), var(--bg-s2), var(--bg-s3)); +} +#bgfx i { + position: absolute; + border-radius: 50%; + filter: blur(64px); + will-change: transform; + opacity: 0.6; +} +#bgfx .b1 { + top: -10%; + left: -8%; + width: 560px; + height: 560px; + background: var(--blob1); + animation: drift1 30s ease-in-out infinite alternate; +} +#bgfx .b2 { + top: 16%; + right: -10%; + width: 440px; + height: 440px; + background: var(--blob2); + animation: drift2 36s ease-in-out infinite alternate; +} +#bgfx .b3 { + bottom: -12%; + left: 24%; + width: 620px; + height: 620px; + background: var(--blob3); + animation: drift3 42s ease-in-out infinite alternate; +} +@keyframes drift1 { + from { + transform: translate(0, 0) scale(1); + } + to { + transform: translate(30px, -20px) scale(1.05); + } +} +@keyframes drift2 { + from { + transform: translate(0, 0) scale(1); + } + to { + transform: translate(-25px, 18px) scale(1.03); + } +} +@keyframes drift3 { + from { + transform: translate(0, 0) scale(1); + } + to { + transform: translate(18px, 25px) scale(1.04); + } +} + +/* ---- frameless titlebar ---- */ +.titlebar { + height: 36px; + display: flex; + align-items: center; + padding: 0 8px 0 14px; + -webkit-app-region: drag; + user-select: none; + background: linear-gradient( + 180deg, + rgba(255, 255, 255, 0.5), + rgba(255, 255, 255, 0.16) + ); + backdrop-filter: blur(14px) saturate(1.4); + -webkit-backdrop-filter: blur(14px) saturate(1.4); + border-bottom: 1px solid var(--line); + position: relative; + z-index: 30; +} +html[data-theme="dark"] .titlebar { + background: linear-gradient( + 180deg, + rgba(26, 23, 38, 0.7), + rgba(26, 23, 38, 0.42) + ); +} +.titlebar-brand { + display: flex; + align-items: center; + gap: 9px; +} +.tb-logo { + width: 20px; + height: 20px; + border-radius: 6px; + background: linear-gradient( + 135deg, + #3f6ef5, + #6a8ffb + ); /* matches site favicon */ + color: #fff; + font-size: 12px; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; +} +.tb-title { + font-size: 12.5px; + font-weight: 600; + letter-spacing: 0.03em; + color: var(--fg); +} +.status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: #c8c6d2; +} +.status-dot.ok { + background: var(--ok); + box-shadow: 0 0 8px rgba(23, 169, 100, 0.55); +} +.status-dot.bad { + background: var(--danger); +} +.titlebar-right { + margin-left: auto; + display: flex; + align-items: center; + height: 36px; + -webkit-app-region: no-drag; + gap: 2px; +} +.tb-btn { + width: 34px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 7px; + color: var(--muted); + transition: + background 0.15s, + color 0.15s; +} +.tb-btn:hover { + background: var(--primary-100); + color: var(--fg); +} +.tb-btn.tb-close:hover { + background: var(--danger); + color: #fff; +} + +/* ---- content area ---- */ +#app { + position: absolute; + inset: 36px 0 0 0; + display: flex; +} +#rail { + flex: 0 0 56px; + width: 56px; + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + padding: 10px 0; + background: var(--card2, rgba(255, 255, 255, 0.45)); + border-right: 1px solid var(--line); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); +} +.rail-gap { + flex: 1; +} +.rail-btn { + position: relative; + width: 38px; + height: 38px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 11px; + color: var(--muted); + transition: all 0.15s; +} +.rail-btn svg { + width: 18px; + height: 18px; +} +.rail-btn:hover { + color: var(--fg); + background: var(--primary-100); +} +.rail-btn.active { + color: var(--primary-h); + background: var(--primary-100); +} +/* toggle status dot (top-right corner of the button) */ +.rail-dot { + position: absolute; + top: 5px; + right: 5px; + width: 7px; + height: 7px; + border-radius: 50%; + background: #c8c6d2; + border: 1.5px solid var(--card-solid, #fff); + box-sizing: content-box; + transition: background 0.15s; +} +.rail-dot.on { + background: var(--ok, #17a964); + box-shadow: 0 0 6px rgba(23, 169, 100, 0.6); +} +.theme-ic { + display: flex; + align-items: center; + justify-content: center; +} +.theme-ic svg { + width: 16px; + height: 16px; +} +/* content column: iframe sits to the right of the rail */ +.app-main { + flex: 1; + min-width: 0; + position: relative; +} +.webui-frame { + width: 100%; + height: 100%; + border: none; + background: #fff; +} +html[data-theme="dark"] .webui-frame { + background: #1a1824; +} + +/* ---- offline placeholder ---- */ +.offline { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + z-index: 5; +} +.off-card { + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + padding: 38px 46px; + background: var(--card); + border: 1px solid var(--line-strong); + border-radius: 20px; + box-shadow: var(--sh-lg); + backdrop-filter: blur(18px); + -webkit-backdrop-filter: blur(18px); +} +.off-logo { + width: 52px; + height: 52px; + border-radius: 15px; + background: linear-gradient( + 135deg, + #3f6ef5, + #6a8ffb + ); /* matches site favicon */ + color: #fff; + font-size: 26px; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + box-shadow: var(--sh-md); +} +.off-title { + font-size: 17px; + font-weight: 700; +} +.off-sub { + font-size: 12.5px; + color: var(--muted); + max-width: 340px; + text-align: center; +} +.off-card button { + padding: 10px 26px; + border-radius: 10px; + font-weight: 600; + margin-top: 6px; +} +.off-card .primary { + background: var(--primary); + color: #fff; +} +.off-card .primary:hover { + background: var(--primary-h); +} + +/* loading spinner */ +.console-loading { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 14px; + color: var(--muted); + font-size: 13px; + z-index: 5; +} +.spinner { + width: 32px; + height: 32px; + border-radius: 50%; + border: 3px solid var(--primary-100); + border-top-color: var(--primary-h); + animation: spin 0.8s linear infinite; +} +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +/* ---- settings overlay ---- */ +.overlay { + position: absolute; + inset: 36px 0 0 0; + display: flex; + align-items: center; + justify-content: center; + z-index: 40; + background: rgba(30, 24, 48, 0.18); + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); +} +html[data-theme="dark"] .overlay { + background: rgba(10, 8, 18, 0.5); +} +.overlay-card { + width: 400px; + max-width: 92%; + background: var(--card-solid); + border: 1px solid var(--line-strong); + border-radius: 18px; + box-shadow: var(--sh-lg); + padding: 22px 24px; + animation: popIn 0.18s var(--ease-spring); +} +@keyframes popIn { + from { + opacity: 0; + transform: translateY(8px) scale(0.98); + } + to { + opacity: 1; + transform: none; + } +} +.overlay-head { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 10px; +} +.overlay-head h2 { + margin: 0; + font-size: 17px; +} +.form .row { + display: flex; + align-items: center; + gap: 10px; + padding: 9px 0; + border-bottom: 1px solid var(--line); +} +.form .row label { + flex: 0 0 110px; + font-size: 13px; + color: var(--muted); +} +.form .row .toggle { + flex: 1; + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + font-size: 13px; + color: var(--fg); + font-weight: 500; +} +.form .row .toggle input { + accent-color: var(--primary); + width: 15px; + height: 15px; +} +.form .row .hint { + font-size: 11px; + color: var(--muted); +} +.form input[type="number"] { + width: 120px; + padding: 7px 10px; + border: 1px solid var(--line-strong); + border-radius: 8px; + background: var(--bg-s2); + outline: none; +} +.form input[type="number"]:focus { + border-color: var(--primary); + box-shadow: 0 0 0 3px var(--primary-100); +} +.form .actions { + display: flex; + justify-content: flex-start; + gap: 9px; + border-bottom: none; + padding-top: 12px; +} +.form .actions.right { + justify-content: flex-end; +} +.form .actions button { + padding: 9px 18px; + border-radius: 9px; + font-weight: 600; + transition: all 0.15s; +} +.form .actions .primary { + background: var(--primary); + color: #fff; +} +.form .actions .primary:hover { + background: var(--primary-h); +} +.form .actions .ghost { + background: var(--bg-s2); + border: 1px solid var(--line-strong); + color: var(--muted); +} +.form .actions .ghost:hover { + background: var(--primary-50); + color: var(--fg); +} + +/* ---- toast ---- */ +#toast { + position: fixed; + right: 20px; + bottom: 20px; + z-index: 200; + padding: 11px 18px; + border-radius: 12px; + background: var(--card-solid); + color: var(--fg); + font-size: 13px; + box-shadow: var(--sh-lg); + border: 1px solid var(--line); + opacity: 0; + transform: translateY(10px); + pointer-events: none; + transition: all 0.25s var(--ease-spring); +} +#toast.show { + opacity: 1; + transform: none; +} +#toast.err { + border-color: var(--danger); +} diff --git a/cmd/gui/scripts/prepare-core.js b/cmd/gui/scripts/prepare-core.js new file mode 100644 index 0000000..fbba678 --- /dev/null +++ b/cmd/gui/scripts/prepare-core.js @@ -0,0 +1,74 @@ +#!/usr/bin/env node +// Build the embedded llmsproxy core(s) for the GUI package. +// node prepare-core.js -> linux (native, luajit) + win (cross, best effort) +// node prepare-core.js linux -> linux only +// node prepare-core.js win -> win only +const { execFileSync } = require("child_process"); +const fs = require("fs"); +const path = require("path"); + +const guiDir = __dirname.replace(/scripts$/, ""); +const binDir = path.join(guiDir, "bin"); +const projectRoot = path.join(guiDir, "..", ".."); + +function buildLinux() { + const out = path.join(binDir, "llmsproxy"); + console.log("[prepare-core] building llmsproxy (linux/luajit)..."); + fs.mkdirSync(binDir, { recursive: true }); + execFileSync( + "go", + ["build", "-tags", "luajit", "-trimpath", "-o", out, "./cmd/llmsproxy"], + { cwd: projectRoot, stdio: "inherit" }, + ); + return out; +} + +function buildWin() { + const out = path.join(binDir, "llmsproxy.exe"); + console.log( + "[prepare-core] cross-building llmsproxy.exe (windows/amd64, luajit)...", + ); + fs.mkdirSync(binDir, { recursive: true }); + const cc = process.env.MINGW_CC || "x86_64-w64-mingw32-gcc"; + // friendly hint when the mingw cross-compiler is missing + try { + execFileSync(cc + " --version", { shell: true, stdio: "ignore" }); + } catch (e) { + console.error( + "[prepare-core] MISSING mingw-w64 cross-compiler (" + cc + ").", + ); + console.error(" Install it: sudo apt install gcc-mingw-w64-x86-64"); + console.error(" Then re-run: npm run core:win (or make gui-win)"); + process.exit(1); + } + execFileSync( + "go", + ["build", "-tags", "luajit", "-trimpath", "-o", out, "./cmd/llmsproxy"], + { + cwd: projectRoot, + stdio: "inherit", + env: Object.assign({}, process.env, { + GOOS: "windows", + GOARCH: "amd64", + CGO_ENABLED: "1", + CC: cc, + }), + }, + ); + return out; +} + +const which = process.argv[2] || "both"; +const results = []; +try { + if (which === "linux" || which === "both") results.push(buildLinux()); +} catch (e) { + console.error("[prepare-core] linux build failed:", e.message); +} +try { + if (which === "win" || which === "both") results.push(buildWin()); +} catch (e) { + console.error("[prepare-core] win build failed:", e.message); +} +if (results.length === 0) process.exit(1); +results.forEach((p) => console.log("[prepare-core] core ready: " + p)); diff --git a/internal/lua/adapters/opencode.lua b/internal/lua/adapters/opencode.lua index db588d9..7082b0b 100644 --- a/internal/lua/adapters/opencode.lua +++ b/internal/lua/adapters/opencode.lua @@ -16,6 +16,17 @@ adapter.headers = { -- zen 上游 schema 只接受 text content part(无视觉/音频能力):多模态 part -- (image_url / input_audio / file 等)一律剥离;因此失去全部 content 的 -- 消息整条丢弃,避免上游 "unknown variant `image_url`, expected `text`"。 +-- zen 上游角色白名单只有 system / user / assistant / tool / latest_reminder: +-- OpenAI 的 developer(及 function 等)不在其中,直接透传会触发上游 +-- "unknown variant `developer`, expected one of ..." 错误;统一归一化为 system。 +local ROLE_WHITELIST = { + system = true, + user = true, + assistant = true, + tool = true, + latest_reminder = true, +} + function adapter.transform_request(raw_body) local ok, req = pcall(json.decode, raw_body) if not ok then return raw_body end @@ -24,6 +35,9 @@ function adapter.transform_request(raw_body) if req.messages then local kept = {} for _, msg in ipairs(req.messages) do + if type(msg.role) == "string" and not ROLE_WHITELIST[msg.role] then + msg.role = "system" + end msg.reasoning_content = nil local drop = false if type(msg.content) == "table" then diff --git a/internal/lua/vm_test.go b/internal/lua/vm_test.go index 67e8f6c..5d56486 100644 --- a/internal/lua/vm_test.go +++ b/internal/lua/vm_test.go @@ -302,3 +302,71 @@ func TestMultimodalTransform(t *testing.T) { t.Fatalf("openai passthrough lost multimodal content: %s", po) } } + +func TestOpenCodeAdapterNormalizesDeveloperRole(t *testing.T) { + vm := NewVM(freshAdapterDir(t)) + if err := vm.Start(); err != nil { + t.Fatalf("start: %v", err) + } + defer vm.Stop() + + raw := `{"model":"zen","messages":[` + + `{"role":"developer","content":"be concise"},` + + `{"role":"user","content":"hi"},` + + `{"role":"assistant","content":"hello"},` + + `{"role":"function","content":"{\"a\":1}"},` + + `{"role":"latest_reminder","content":"remind"}]}` + out, err := vm.Transform("opencode", "transform_request", raw) + if err != nil { + t.Fatalf("transform_request: %v", err) + } + var req struct { + Messages []struct { + Role string `json:"role"` + } `json:"messages"` + } + if err := json.Unmarshal([]byte(out), &req); err != nil { + t.Fatalf("output not JSON: %v\n%s", err, out) + } + want := []string{"system", "user", "assistant", "system", "latest_reminder"} + if len(req.Messages) != len(want) { + t.Fatalf("got %d messages, want %d: %s", len(req.Messages), len(want), out) + } + for i, w := range want { + if req.Messages[i].Role != w { + t.Errorf("message %d role = %q, want %q", i, req.Messages[i].Role, w) + } + } +} + +func TestOpenCodeAdapterKeepsWhitelistedRolesAndDropsMultimodal(t *testing.T) { + vm := NewVM(freshAdapterDir(t)) + if err := vm.Start(); err != nil { + t.Fatalf("start: %v", err) + } + defer vm.Stop() + + raw := `{"messages":[` + + `{"role":"system","content":"sys"},` + + `{"role":"user","content":[{"type":"text","text":"keep"},{"type":"image_url","url":"x"}]},` + + `{"role":"user","content":[{"type":"image_url","url":"x"}]}]}` + out, err := vm.Transform("opencode", "transform_request", raw) + if err != nil { + t.Fatalf("transform_request: %v", err) + } + var req struct { + Messages []struct { + Role string `json:"role"` + Content any `json:"content"` + } `json:"messages"` + } + if err := json.Unmarshal([]byte(out), &req); err != nil { + t.Fatalf("output not JSON: %v\n%s", err, out) + } + if len(req.Messages) != 2 { + t.Fatalf("got %d messages, want 2 (multimodal-only dropped): %s", len(req.Messages), out) + } + if req.Messages[0].Role != "system" || req.Messages[1].Role != "user" { + t.Fatalf("unexpected roles: %s", out) + } +}