baa-conductor

git clone 

commit
7d4f292
parent
daf1574
author
im_wower
date
2026-03-22 11:41:27 +0800 CST
Vendor baa-firefox into plugin subdirectory
19 files changed,  +7710, -4
M DESIGN.md
+1, -1
1@@ -2604,7 +2604,7 @@ Scope:
2 - Firefox 插件集成文档
3 - 浏览器状态协议
4 
5-这部分可以落在独立仓库里,但协议应该在这里先定义清楚。
6+这部分现在统一收口在本仓库的 Firefox 插件子目录里,但协议仍然在这里定义清楚。
7 
8 ## 35.10 Task J: Status API 与基础 UI
9 
M README.md
+2, -0
1@@ -27,6 +27,8 @@ apps/
2   conductor-daemon/
3   status-api/
4   worker-runner/
5+plugins/
6+  baa-firefox/
7 packages/
8   auth/
9   checkpointing/
M coordination/FIFTH_WAVE_START.md
+1, -1
1@@ -40,4 +40,4 @@ npx --yes pnpm install
2 - 当前仍然采用“内网走 Tailscale `100.x`,外网走二级域名”
3 - 当前仍不依赖 MagicDNS 名称
4 - `README.md`、`DESIGN.md`、根配置文件仍视为热点文件,非任务必要不要碰
5-- `T-026` 是跨仓库任务,目标仓库为 `/Users/george/code/baa/baa-firefox`
6+- `T-026` 在当时是跨仓库任务;当前 Firefox 插件代码已经收口到本仓库子目录 `plugins/baa-firefox/`
M coordination/STATUS_SUMMARY.md
+1, -1
1@@ -62,7 +62,7 @@
2 - `README.md`、`DESIGN.md`、根配置文件应避免多人同时修改
3 - `ops/launchd/**` 已合入,当前主线已经产出 app 级 `dist/index.js`
4 - `apps/conductor-daemon/package.json` 与 `apps/status-api/package.json` 的构建脚本已在第三波整合时收口
5-- `baa-firefox` 的 `T-026` 已单独合入其仓库 `main`
6+- Firefox 插件代码现已收口到本仓库子目录 `plugins/baa-firefox/`
7 
8 ## 后续汇总规则
9 
M docs/firefox/README.md
+6, -1
 1@@ -1,5 +1,11 @@
 2 # Firefox Control Protocol
 3 
 4+当前 Firefox 插件实现代码已经直接收口到本仓库子目录:
 5+
 6+- [`../../plugins/baa-firefox/`](../../plugins/baa-firefox/)
 7+
 8+这里保留协议和控制面约定文档;具体实现以 `plugins/baa-firefox/` 为准。
 9+
10 本文档定义 `baa-firefox` 与 `baa-conductor` control API 之间的最小协议。
11 
12 目标:
13@@ -10,7 +16,6 @@
14 
15 非目标:
16 
17-- 不在本仓库实现 Firefox 插件代码
18 - 不定义完整 UI 视觉稿
19 - 不让浏览器成为调度真相来源
20 
A plugins/baa-firefox/.gitignore
+2, -0
1@@ -0,0 +1,2 @@
2+node_modules/
3+.DS_Store
A plugins/baa-firefox/PLAN.md
+436, -0
  1@@ -0,0 +1,436 @@
  2+# BAA Firefox MVP Plan
  3+
  4+## Goal
  5+
  6+Build the smallest Firefox extension MVP that proves this model works:
  7+
  8+- one always-open controller page inside the extension
  9+- one real top-level AI tab
 10+- manual login only
 11+- automatic discovery of API endpoints, auth headers, and streaming events
 12+- a long-lived WebSocket from the controller page to `baa-server`
 13+- no DOM parsing as the primary path
 14+
 15+The first platform should be `claude.ai` only. After it works, generalize.
 16+
 17+## Non-Goals
 18+
 19+Do not solve these in MVP:
 20+
 21+- multi-platform support
 22+- multiple tabs per platform
 23+- multiple accounts
 24+- multi-window coordination
 25+- iframe embedding
 26+- full request execution from server into the AI tab
 27+- automatic login
 28+- deep DOM automation
 29+- generic worker/websocket interception
 30+- polished UI
 31+
 32+## Core Decision
 33+
 34+Do not keep the main WebSocket in `background.js`.
 35+
 36+Instead:
 37+
 38+- `controller.html` stays open in a normal browser tab
 39+- `controller.js` owns the WebSocket and the runtime state
 40+- `background.js` becomes thin bootstrap and helper logic only
 41+
 42+Reason:
 43+
 44+- Firefox MV3 background lifetime is the main risk
 45+- a visible extension page is simpler than trying to keep a background context alive
 46+- the controller page can directly use WebExtension APIs needed for MVP
 47+
 48+## MVP Architecture
 49+
 50+### 1. Controller Page
 51+
 52+One extension page acts as the control tower.
 53+
 54+Responsibilities:
 55+
 56+- open and keep track of the single AI tab
 57+- connect to `baa-server` over WS
 58+- receive browser-side events from content scripts
 59+- persist minimal state to `storage.local`
 60+- surface simple status: connected, tab exists, credentials seen, endpoints count
 61+
 62+The controller page should be opened manually at first.
 63+
 64+Later, background can auto-open it on startup if needed.
 65+
 66+### 2. Real AI Tab
 67+
 68+Use one real top-level tab:
 69+
 70+- `https://claude.ai`
 71+
 72+Requirements:
 73+
 74+- user logs in manually
 75+- no specific chat URL required
 76+- the root app page is enough
 77+
 78+The tab is the real browser surface that carries:
 79+
 80+- cookies
 81+- session
 82+- same-origin rules
 83+- anti-bot browser context
 84+
 85+### 3. Content Script Bridge
 86+
 87+Inject a lightweight content script into the AI tab.
 88+
 89+Responsibilities:
 90+
 91+- receive `CustomEvent`s from the page context
 92+- forward them to the controller page or background
 93+
 94+This is the bridge between:
 95+
 96+- privileged extension code
 97+- page MAIN world instrumentation
 98+
 99+### 4. MAIN World Interceptor
100+
101+Inject a MAIN world script into the AI tab.
102+
103+Responsibilities:
104+
105+- patch `window.fetch`
106+- observe request URL, method, headers, body
107+- observe response status, headers, body
108+- observe SSE chunks if present
109+- emit normalized events through `CustomEvent`
110+
111+This is the primary discovery layer for:
112+
113+- API endpoints
114+- request/response shapes
115+- streaming behavior
116+
117+### 5. Browser-Level Request Observer
118+
119+Use `webRequest` in parallel.
120+
121+Responsibilities:
122+
123+- capture outgoing request headers at browser level
124+- recover auth-related headers not visible from page JS
125+- build a credential snapshot
126+- provide a second signal for endpoint discovery
127+
128+This is the primary source for:
129+
130+- cookies
131+- CSRF-style headers
132+- browser-visible request metadata
133+
134+## Why This MVP Should Work
135+
136+This design avoids the fragile path:
137+
138+- no DOM scraping as the primary integration
139+- no button clicking or selector dependency
140+- no iframe
141+
142+This design keeps the stable path:
143+
144+- real browser tab
145+- real cookies
146+- real request flow
147+- extension-side network observation
148+
149+The working proof for MVP is simple:
150+
151+- the user logs into Claude manually
152+- the extension sees live endpoint traffic
153+- the extension captures auth material
154+- the extension sends these to `baa-server`
155+
156+## Files To Create
157+
158+The repo should start with these files:
159+
160+- `manifest.json`
161+- `controller.html`
162+- `controller.js`
163+- `background.js`
164+- `content-script.js`
165+- `page-interceptor.js`
166+- `README.md`
167+
168+Optional:
169+
170+- `controller.css`
171+
172+## File Responsibilities
173+
174+### `manifest.json`
175+
176+Firefox-targeted MV3 manifest.
177+
178+Include only what MVP needs:
179+
180+- `tabs`
181+- `storage`
182+- `webRequest`
183+- `cookies`
184+- `scripting`
185+- host permissions for `https://claude.ai/*`
186+
187+Register:
188+
189+- `background.js`
190+- `content-script.js`
191+- `page-interceptor.js`
192+- browser action or direct tab page for `controller.html`
193+
194+### `controller.html`
195+
196+Minimal UI only.
197+
198+Must show:
199+
200+- WS connected / disconnected
201+- AI tab exists / missing
202+- credentials seen / missing
203+- number of discovered endpoints
204+- recent log lines
205+
206+One button is enough:
207+
208+- `Open Claude Tab`
209+
210+### `controller.js`
211+
212+Main runtime owner.
213+
214+Responsibilities:
215+
216+- connect WS to `baa-server`
217+- create or recover the Claude tab
218+- maintain `platformTabId`
219+- receive forwarded events
220+- normalize and send them to server
221+- persist last known state
222+
223+State shape can be minimal:
224+
225+```js
226+{
227+  platform: "claude",
228+  tabId: 123,
229+  wsConnected: true,
230+  lastCredentialAt: 0,
231+  endpoints: {},
232+  lastHeaders: {},
233+  lastEvents: []
234+}
235+```
236+
237+### `background.js`
238+
239+Thin only.
240+
241+Responsibilities:
242+
243+- open/focus `controller.html` when extension icon is clicked
244+- optionally relay messages if direct page-to-tab wiring is awkward
245+- nothing long-lived beyond bootstrap
246+
247+No main WS should live here in MVP.
248+
249+### `content-script.js`
250+
251+Bridge only.
252+
253+Responsibilities:
254+
255+- listen for `CustomEvent`s from page context
256+- forward to extension runtime
257+
258+Keep it small.
259+
260+### `page-interceptor.js`
261+
262+MAIN world network observer.
263+
264+Responsibilities:
265+
266+- wrap `fetch`
267+- capture requests
268+- capture error responses
269+- capture streaming events where possible
270+
271+For MVP, patching `fetch` is enough.
272+
273+If Claude moves important traffic elsewhere later, expand after proof.
274+
275+## Message Flow
276+
277+### Browser Startup
278+
279+1. User opens `controller.html`
280+2. `controller.js` connects WS to `baa-server`
281+3. `controller.js` checks for existing Claude tab
282+4. If missing, it opens `https://claude.ai`
283+5. Content script and MAIN world interceptor are active on that tab
284+
285+### Manual Login
286+
287+1. User logs into Claude in the real tab
288+2. Claude app bootstraps and starts making network requests
289+3. `webRequest` captures browser-visible auth headers and cookies
290+4. MAIN world interceptor captures request and response data
291+5. Controller receives both and sends normalized events to `baa-server`
292+
293+### Endpoint Discovery
294+
295+1. MAIN world interceptor sees a request
296+2. URL path is normalized
297+3. Method + normalized path are added to endpoint registry
298+4. Controller sends discovered endpoints to `baa-server`
299+
300+### Credential Snapshot
301+
302+1. `webRequest` sees request headers
303+2. Controller extracts a reduced auth snapshot
304+3. Snapshot is timestamped and stored
305+4. Snapshot is sent to `baa-server`
306+
307+## Data Sent To Server
308+
309+MVP should send only these message types:
310+
311+- `hello`
312+- `endpoint_discovered`
313+- `credential_snapshot`
314+- `network_event`
315+- `sse_event`
316+- `status`
317+
318+Example normalized messages:
319+
320+```json
321+{
322+  "type": "hello",
323+  "clientId": "ff-xxxxxx",
324+  "nodeType": "browser",
325+  "nodeCategory": "proxy",
326+  "nodePlatform": "firefox"
327+}
328+```
329+
330+```json
331+{
332+  "type": "endpoint_discovered",
333+  "platform": "claude",
334+  "method": "POST",
335+  "path": "/api/organizations/{id}/messages"
336+}
337+```
338+
339+```json
340+{
341+  "type": "credential_snapshot",
342+  "platform": "claude",
343+  "headers": {
344+    "cookie": "...",
345+    "x-csrf-token": "...",
346+    "anthropic-client-sha": "..."
347+  },
348+  "ts": 0
349+}
350+```
351+
352+## Minimal Acceptance Criteria
353+
354+MVP is done when all of these are true:
355+
356+- opening `controller.html` establishes a WS connection to `baa-server`
357+- the controller can open or recover exactly one Claude tab
358+- manual login in that tab leads to captured credentials
359+- at least one live Claude API endpoint is discovered automatically
360+- request/response metadata is visible in server logs
361+- SSE chunks are visible if Claude uses streaming on the observed path
362+- closing the Claude tab and reopening it still restores the flow
363+
364+## Build Order
365+
366+Implement in this order:
367+
368+1. `manifest.json`
369+2. `controller.html` + `controller.js`
370+3. thin `background.js`
371+4. `content-script.js`
372+5. `page-interceptor.js`
373+6. `webRequest` credential capture
374+7. WS message normalization to server
375+8. minimal status UI
376+
377+## Scope Cuts If Time Is Tight
378+
379+If the first pass needs to be even smaller, cut these first:
380+
381+- pretty UI
382+- SSE chunk parsing details
383+- background auto-open behavior
384+- storage persistence beyond `tabId` and last headers
385+- endpoint normalization beyond very basic ID replacement
386+
387+Do not cut these:
388+
389+- controller-owned WS
390+- real top-level Claude tab
391+- MAIN world fetch interception
392+- `webRequest` credential capture
393+
394+## Deferred Work After MVP
395+
396+Only after the MVP works:
397+
398+- add active proxy execution from server into the live AI tab
399+- support ChatGPT and Gemini
400+- support multiple windows
401+- support multi-account contexts
402+- add stronger recovery logic
403+- add XHR and worker interception
404+- make the controller page optional by moving some logic back into a more robust runtime model
405+
406+## TODO: SSE 抓取
407+
408+Observed in the current Firefox MVP:
409+
410+- request / credential / endpoint capture works
411+- `POST /api/organizations/{id}/chat_conversations/{id}/completion` is discovered
412+- the current SSE bridge can still report `The operation was aborted` with `0` chunks
413+- this happened in the anonymous `https://claude.ai/new?incognito` flow after a real prompt/response round trip
414+
415+Next work should focus on these items:
416+
417+- reproduce the failure deterministically on both anonymous and logged-in organization chats
418+- verify whether `response.clone().body.getReader()` is racing with Claude's own stream consumption in Firefox
419+- test replacing the current clone-reader approach with a `ReadableStream.tee()` based interception path
420+- keep partial chunk state across boundaries so incomplete `data:` frames are not dropped
421+- include stable conversation identifiers in `sse_event` payloads when the completion URL contains them
422+- distinguish clean stream completion from abort / navigation / page refresh in emitted events
423+- add controller-side logs for first chunk, last chunk, abort reason, and total chunk count
424+- only mark SSE capture as done after a verified chunked response is observed in `baa-server` logs
425+
426+## Final Recommendation
427+
428+Do not over-design beyond this.
429+
430+The first goal is only to prove:
431+
432+- Firefox can keep a controller page open
433+- the controller page can keep a WS open
434+- a real Claude tab can be observed without DOM parsing
435+- endpoints and credentials can be learned and sent to `baa-server`
436+
437+Once that proof exists, the rest of the system can be evolved safely.
A plugins/baa-firefox/README.md
+114, -0
  1@@ -0,0 +1,114 @@
  2+# BAA Firefox
  3+
  4+这个目录现在作为 `baa-conductor` 内部插件子目录维护。
  5+
  6+来源说明:
  7+
  8+- 初始代码来自原独立仓库 `baa-firefox`
  9+- 已迁入当前仓库,后续以这里为主写入面
 10+
 11+Firefox MVP for the BAA browser-proxy path.
 12+
 13+## What This Repo Does
 14+
 15+This extension keeps an always-open controller page and one real browser tab per platform.
 16+
 17+It does four things:
 18+
 19+- keeps a WebSocket connection to `baa-server`
 20+- opens or reuses real tabs for `claude.ai`, `chatgpt.com`, and `gemini.google.com`
 21+- discovers live API endpoints from real browser traffic
 22+- captures auth-related request headers and forwards them to `baa-server`
 23+
 24+This MVP is intentionally narrow:
 25+
 26+- manual login only
 27+- one tracked tab per platform
 28+- no iframe
 29+- no DOM automation
 30+- no full remote execution path yet
 31+
 32+## Files
 33+
 34+- `manifest.json` - Firefox MV3 manifest
 35+- `background.js` - opens and keeps the controller tab around
 36+- `controller.html` - always-open management page
 37+- `controller.js` - WS owner, multi-platform tab manager, webRequest capture, message relay
 38+- `content-script.js` - bridge from page events into the extension runtime
 39+- `page-interceptor.js` - MAIN world `fetch` interceptor
 40+- `scripts/run-persistent.sh` - starts Firefox with the persistent `baa-firefox-persistent` profile
 41+- `PLAN.md` - MVP design notes
 42+
 43+## Persistent Startup
 44+
 45+If you already created and logged into the persistent Firefox profile once, start the extension with:
 46+
 47+```bash
 48+./scripts/run-persistent.sh
 49+```
 50+
 51+This launches Firefox with the `baa-firefox-persistent` profile, keeps profile changes, reuses the saved browser login state on this machine, and reloads already-open platform pages so the interceptor is injected immediately.
 52+
 53+Useful overrides:
 54+
 55+- `BAA_FIREFOX_PROFILE` - change the Firefox profile name
 56+- `BAA_FIREFOX_BIN` - change the Firefox binary path
 57+- `BAA_WEB_EXT_BIN` - change the `web-ext` executable
 58+
 59+Example:
 60+
 61+```bash
 62+BAA_FIREFOX_PROFILE=baa-firefox-persistent ./scripts/run-persistent.sh
 63+```
 64+
 65+## How To Load
 66+
 67+1. Open Firefox.
 68+2. Visit `about:debugging#/runtime/this-firefox`.
 69+3. Click `Load Temporary Add-on...`.
 70+4. Choose [manifest.json](/Users/george/code/baa-conductor-main-merge/plugins/baa-firefox/manifest.json).
 71+5. Firefox should open `controller.html` automatically.
 72+
 73+## How To Run
 74+
 75+1. Start `baa-server` so `ws://localhost:9800` is available.
 76+2. Load this extension temporarily.
 77+3. In the controller page, keep the default WS URL or change it.
 78+4. Let the extension open `https://claude.ai/`, `https://chatgpt.com/`, and `https://gemini.google.com/`.
 79+5. Log in manually in the tabs you want to intercept.
 80+6. Use Claude, ChatGPT, or Gemini normally.
 81+7. Watch the controller page and `baa-server` logs for:
 82+   - endpoint discovery
 83+   - credential snapshots
 84+   - network events
 85+   - SSE events
 86+
 87+## Expected MVP Result
 88+
 89+Once the platform tabs start making real requests, the extension should forward:
 90+
 91+- `hello`
 92+- `api_endpoints`
 93+- `credentials`
 94+- `network_log`
 95+- `sse_event`
 96+- `client_log`
 97+
 98+These message shapes match the current `baa-server` browser-side WS handling.
 99+
100+## Limitations
101+
102+- only one tab per platform is tracked
103+- only `fetch` is patched in the page
104+- there is no remote request execution path yet
105+- if Firefox unloads the extension, the session must be re-established
106+- Gemini interception is heuristic because its web app mixes private RPC paths under `/_/`
107+
108+## Next Step
109+
110+After this works end to end, add:
111+
112+- request execution into the live platform tabs
113+- stronger tab recovery
114+- broader network interception
115+- more robust Gemini and SSE coverage
A plugins/baa-firefox/background.js
+130, -0
  1@@ -0,0 +1,130 @@
  2+const CONTROLLER_URL = browser.runtime.getURL("controller.html");
  3+const STORAGE_KEY = "baaFirefox.controllerTabId";
  4+const CONTROL_STATE_KEY = "baaFirefox.controlState";
  5+const MODE_BADGES = {
  6+  running: { text: "运行", color: "#1f6f5f" },
  7+  paused: { text: "暂停", color: "#a6512f" },
  8+  draining: { text: "排空", color: "#8b5e34" },
  9+  unknown: { text: "--", color: "#7f7a70" }
 10+};
 11+
 12+async function setControllerTabId(tabId) {
 13+  await browser.storage.local.set({ [STORAGE_KEY]: tabId });
 14+}
 15+
 16+async function getStoredControllerTabId() {
 17+  const data = await browser.storage.local.get(STORAGE_KEY);
 18+  return data[STORAGE_KEY] || null;
 19+}
 20+
 21+async function queryControllerTab() {
 22+  const tabs = await browser.tabs.query({ url: CONTROLLER_URL });
 23+  return tabs[0] || null;
 24+}
 25+
 26+async function ensureControllerTab(options = {}) {
 27+  const { activate = false } = options;
 28+
 29+  let tab = await queryControllerTab();
 30+  if (!tab) {
 31+    const storedId = await getStoredControllerTabId();
 32+    if (storedId) {
 33+      try {
 34+        tab = await browser.tabs.get(storedId);
 35+      } catch (_) {
 36+        tab = null;
 37+      }
 38+    }
 39+  }
 40+
 41+  if (tab) {
 42+    await setControllerTabId(tab.id);
 43+    if (activate) {
 44+      await browser.tabs.update(tab.id, { active: true });
 45+      if (tab.windowId != null) {
 46+        await browser.windows.update(tab.windowId, { focused: true });
 47+      }
 48+    }
 49+    return tab;
 50+  }
 51+
 52+  const created = await browser.tabs.create({
 53+    url: CONTROLLER_URL,
 54+    active: activate
 55+  });
 56+  await setControllerTabId(created.id);
 57+  return created;
 58+}
 59+
 60+function normalizeMode(value) {
 61+  const lower = String(value || "").trim().toLowerCase();
 62+  if (lower === "running" || lower === "paused" || lower === "draining") {
 63+    return lower;
 64+  }
 65+  return "unknown";
 66+}
 67+
 68+async function updateActionBadge(snapshot) {
 69+  const mode = normalizeMode(snapshot?.mode);
 70+  const badge = snapshot?.error ? { text: "ERR", color: "#a6512f" } : MODE_BADGES[mode];
 71+  const modeLabel = mode === "running" ? "运行中" : mode === "paused" ? "已暂停" : mode === "draining" ? "排空中" : "未知";
 72+  const leader = snapshot?.leader ? `\n主控: ${snapshot.leader}` : "";
 73+
 74+  await browser.action.setBadgeText({ text: badge.text });
 75+  await browser.action.setBadgeBackgroundColor({ color: badge.color });
 76+  await browser.action.setTitle({
 77+    title: `BAA Firefox 管理页\n模式: ${modeLabel}${leader}`
 78+  });
 79+}
 80+
 81+async function refreshActionBadgeFromStorage() {
 82+  const data = await browser.storage.local.get(CONTROL_STATE_KEY);
 83+  await updateActionBadge(data[CONTROL_STATE_KEY] || null);
 84+}
 85+
 86+browser.runtime.onInstalled.addListener(() => {
 87+  ensureControllerTab({ activate: true }).catch(() => {});
 88+  refreshActionBadgeFromStorage().catch(() => {});
 89+});
 90+
 91+browser.runtime.onStartup.addListener(() => {
 92+  ensureControllerTab({ activate: false }).catch(() => {});
 93+  refreshActionBadgeFromStorage().catch(() => {});
 94+});
 95+
 96+browser.action.onClicked.addListener(() => {
 97+  ensureControllerTab({ activate: true }).catch(() => {});
 98+});
 99+
100+browser.runtime.onMessage.addListener((message) => {
101+  if (!message || typeof message !== "object") return undefined;
102+
103+  if (message.type === "controller_ready" && Number.isInteger(message.tabId)) {
104+    return setControllerTabId(message.tabId).then(() => ({ ok: true }));
105+  }
106+
107+  if (message.type === "focus_controller") {
108+    return ensureControllerTab({ activate: true }).then((tab) => ({
109+      ok: true,
110+      tabId: tab.id
111+    }));
112+  }
113+
114+  return undefined;
115+});
116+
117+browser.storage.onChanged.addListener((changes, areaName) => {
118+  if (areaName !== "local" || !changes[CONTROL_STATE_KEY]) return;
119+  updateActionBadge(changes[CONTROL_STATE_KEY].newValue || null).catch(() => {});
120+});
121+
122+browser.tabs.onRemoved.addListener((tabId) => {
123+  getStoredControllerTabId().then((storedId) => {
124+    if (storedId !== tabId) return;
125+    setTimeout(() => {
126+      ensureControllerTab({ activate: false }).catch(() => {});
127+    }, 300);
128+  }).catch(() => {});
129+});
130+
131+refreshActionBadgeFromStorage().catch(() => {});
A plugins/baa-firefox/content-script.js
+358, -0
  1@@ -0,0 +1,358 @@
  2+const CONTROL_STATE_KEY = "baaFirefox.controlState";
  3+const CLAUDE_ENTRY_HOSTS = ["claude.ai"];
  4+
  5+const claudeEntryState = {
  6+  host: null,
  7+  shadow: null,
  8+  snapshot: null,
  9+  expanded: false,
 10+  busyAction: null,
 11+  error: ""
 12+};
 13+
 14+function sendBridgeMessage(type, data) {
 15+  browser.runtime.sendMessage({
 16+    type,
 17+    data
 18+  }).catch(() => {});
 19+}
 20+
 21+function isRecord(value) {
 22+  return !!value && typeof value === "object" && !Array.isArray(value);
 23+}
 24+
 25+function isClaudePage() {
 26+  return CLAUDE_ENTRY_HOSTS.some((host) => location.hostname === host || location.hostname.endsWith(`.${host}`));
 27+}
 28+
 29+function normalizeMode(value) {
 30+  const lower = String(value || "").trim().toLowerCase();
 31+  if (lower === "running" || lower === "paused" || lower === "draining") {
 32+    return lower;
 33+  }
 34+  return "unknown";
 35+}
 36+
 37+function formatModeLabel(mode) {
 38+  switch (normalizeMode(mode)) {
 39+    case "running":
 40+      return "运行中";
 41+    case "paused":
 42+      return "已暂停";
 43+    case "draining":
 44+      return "排空中";
 45+    default:
 46+      return "未知";
 47+  }
 48+}
 49+
 50+function normalizeEntrySnapshot(value) {
 51+  if (!isRecord(value)) {
 52+    return {
 53+      mode: "unknown",
 54+      leader: null,
 55+      queueDepth: null,
 56+      activeRuns: null,
 57+      error: "未同步"
 58+    };
 59+  }
 60+
 61+  return {
 62+    mode: normalizeMode(value.mode),
 63+    leader: typeof value.leader === "string" && value.leader.trim() ? value.leader.trim() : null,
 64+    queueDepth: Number.isFinite(value.queueDepth) ? Number(value.queueDepth) : null,
 65+    activeRuns: Number.isFinite(value.activeRuns) ? Number(value.activeRuns) : null,
 66+    error: typeof value.error === "string" && value.error ? value.error : ""
 67+  };
 68+}
 69+
 70+function ensureClaudeEntry() {
 71+  if (!isClaudePage() || claudeEntryState.host || !document.documentElement) return;
 72+
 73+  const host = document.createElement("div");
 74+  host.style.position = "fixed";
 75+  host.style.right = "16px";
 76+  host.style.bottom = "16px";
 77+  host.style.zIndex = "2147483647";
 78+
 79+  const shadow = host.attachShadow({ mode: "open" });
 80+  shadow.innerHTML = `
 81+    <style>
 82+      :host {
 83+        all: initial;
 84+      }
 85+
 86+      .wrap {
 87+        font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
 88+        color: #f5efe2;
 89+      }
 90+
 91+      button {
 92+        font: inherit;
 93+        border: 0;
 94+        cursor: pointer;
 95+      }
 96+
 97+      .toggle {
 98+        display: inline-flex;
 99+        align-items: center;
100+        gap: 8px;
101+        padding: 9px 12px;
102+        border-radius: 999px;
103+        background: rgba(19, 26, 26, 0.92);
104+        color: inherit;
105+        box-shadow: 0 10px 28px rgba(0, 0, 0, 0.22);
106+      }
107+
108+      .toggle strong {
109+        font-size: 12px;
110+        letter-spacing: 0.08em;
111+        text-transform: uppercase;
112+      }
113+
114+      .badge {
115+        padding: 4px 8px;
116+        border-radius: 999px;
117+        font-size: 11px;
118+        line-height: 1;
119+      }
120+
121+      .badge.running {
122+        background: rgba(31, 111, 95, 0.28);
123+        color: #91dbc7;
124+      }
125+
126+      .badge.paused,
127+      .badge.draining {
128+        background: rgba(166, 81, 47, 0.28);
129+        color: #ffd3bf;
130+      }
131+
132+      .badge.unknown {
133+        background: rgba(127, 122, 112, 0.28);
134+        color: #e5dccd;
135+      }
136+
137+      .panel {
138+        margin-top: 10px;
139+        width: 250px;
140+        padding: 12px;
141+        border-radius: 18px;
142+        background: rgba(19, 26, 26, 0.94);
143+        box-shadow: 0 16px 40px rgba(0, 0, 0, 0.24);
144+      }
145+
146+      .hidden {
147+        display: none;
148+      }
149+
150+      .topline,
151+      .meta,
152+      .actions {
153+        display: flex;
154+        align-items: center;
155+        gap: 8px;
156+      }
157+
158+      .topline,
159+      .meta {
160+        justify-content: space-between;
161+      }
162+
163+      .meta {
164+        margin-top: 10px;
165+        color: rgba(245, 239, 226, 0.78);
166+        font-size: 12px;
167+      }
168+
169+      .actions {
170+        margin-top: 12px;
171+        flex-wrap: wrap;
172+      }
173+
174+      .panel-btn {
175+        padding: 8px 10px;
176+        border-radius: 999px;
177+        background: rgba(255, 255, 255, 0.08);
178+        color: inherit;
179+      }
180+
181+      .panel-btn:hover {
182+        background: rgba(255, 255, 255, 0.14);
183+      }
184+
185+      .panel-btn[disabled] {
186+        opacity: 0.6;
187+        cursor: default;
188+      }
189+
190+      .error {
191+        margin-top: 10px;
192+        color: #ffd3bf;
193+        font-size: 12px;
194+        line-height: 1.45;
195+      }
196+    </style>
197+    <div class="wrap">
198+      <button id="baa-toggle" class="toggle" type="button">
199+        <strong>BAA</strong>
200+        <span id="baa-mode-badge" class="badge unknown">未知</span>
201+      </button>
202+      <div id="baa-panel" class="panel hidden">
203+        <div class="topline">
204+          <span id="baa-leader">主控 -</span>
205+          <button id="baa-open-panel" class="panel-btn" type="button">管理页</button>
206+        </div>
207+        <div class="meta">
208+          <span id="baa-queue">队列 -</span>
209+          <span id="baa-runs">运行 -</span>
210+        </div>
211+        <div class="actions">
212+          <button data-action="pause" class="panel-btn" type="button">暂停</button>
213+          <button data-action="resume" class="panel-btn" type="button">恢复</button>
214+          <button data-action="drain" class="panel-btn" type="button">排空</button>
215+        </div>
216+        <div id="baa-error" class="error hidden"></div>
217+      </div>
218+    </div>
219+  `;
220+
221+  claudeEntryState.host = host;
222+  claudeEntryState.shadow = shadow;
223+
224+  shadow.getElementById("baa-toggle").addEventListener("click", () => {
225+    claudeEntryState.expanded = !claudeEntryState.expanded;
226+    renderClaudeEntry();
227+  });
228+
229+  shadow.getElementById("baa-open-panel").addEventListener("click", () => {
230+    browser.runtime.sendMessage({ type: "focus_controller" }).catch(() => {});
231+    claudeEntryState.expanded = false;
232+    renderClaudeEntry();
233+  });
234+
235+  for (const actionButton of shadow.querySelectorAll("[data-action]")) {
236+    actionButton.addEventListener("click", () => {
237+      runClaudeControlAction(actionButton.dataset.action || "").catch(() => {});
238+    });
239+  }
240+
241+  document.documentElement.append(host);
242+  renderClaudeEntry();
243+}
244+
245+function renderClaudeEntry() {
246+  if (!claudeEntryState.shadow) return;
247+
248+  const snapshot = normalizeEntrySnapshot(claudeEntryState.snapshot);
249+  const badge = claudeEntryState.shadow.getElementById("baa-mode-badge");
250+  const panel = claudeEntryState.shadow.getElementById("baa-panel");
251+  const leader = claudeEntryState.shadow.getElementById("baa-leader");
252+  const queue = claudeEntryState.shadow.getElementById("baa-queue");
253+  const runs = claudeEntryState.shadow.getElementById("baa-runs");
254+  const error = claudeEntryState.shadow.getElementById("baa-error");
255+
256+  badge.textContent = formatModeLabel(snapshot.mode);
257+  badge.className = `badge ${snapshot.mode}`;
258+  leader.textContent = `主控 ${snapshot.leader || "-"}`;
259+  queue.textContent = `队列 ${snapshot.queueDepth == null ? "-" : snapshot.queueDepth}`;
260+  runs.textContent = `运行 ${snapshot.activeRuns == null ? "-" : snapshot.activeRuns}`;
261+  panel.classList.toggle("hidden", !claudeEntryState.expanded);
262+
263+  const busy = !!claudeEntryState.busyAction;
264+  for (const button of claudeEntryState.shadow.querySelectorAll(".panel-btn")) {
265+    if (button.id === "baa-open-panel") continue;
266+    button.disabled = busy;
267+  }
268+
269+  const errorText = claudeEntryState.error || snapshot.error;
270+  error.textContent = errorText || "";
271+  error.classList.toggle("hidden", !errorText);
272+}
273+
274+async function loadClaudeEntrySnapshot() {
275+  const data = await browser.storage.local.get(CONTROL_STATE_KEY);
276+  claudeEntryState.snapshot = normalizeEntrySnapshot(data[CONTROL_STATE_KEY]);
277+  renderClaudeEntry();
278+}
279+
280+async function runClaudeControlAction(action) {
281+  if (!action) return;
282+
283+  claudeEntryState.busyAction = action;
284+  claudeEntryState.error = "";
285+  renderClaudeEntry();
286+
287+  try {
288+    const response = await browser.runtime.sendMessage({
289+      type: "control_plane_command",
290+      action,
291+      source: "claude_entry"
292+    });
293+
294+    if (response?.snapshot) {
295+      claudeEntryState.snapshot = normalizeEntrySnapshot(response.snapshot);
296+    }
297+
298+    if (!response?.ok) {
299+      throw new Error(response?.error || `${action} 执行失败`);
300+    }
301+  } catch (error) {
302+    claudeEntryState.error = error.message;
303+  } finally {
304+    claudeEntryState.busyAction = null;
305+    renderClaudeEntry();
306+  }
307+}
308+
309+sendBridgeMessage("baa_page_bridge_ready", {
310+  url: location.href,
311+  source: "content-script"
312+});
313+
314+window.addEventListener("__baa_ready__", (event) => {
315+  sendBridgeMessage("baa_page_bridge_ready", {
316+    ...(event.detail || {}),
317+    url: event.detail?.url || location.href,
318+    source: event.detail?.source || "page-interceptor"
319+  });
320+});
321+
322+window.addEventListener("__baa_net__", (event) => {
323+  sendBridgeMessage("baa_page_network", event.detail);
324+});
325+
326+window.addEventListener("__baa_sse__", (event) => {
327+  sendBridgeMessage("baa_page_sse", event.detail);
328+});
329+
330+window.addEventListener("__baa_proxy_response__", (event) => {
331+  sendBridgeMessage("baa_page_proxy_response", event.detail);
332+});
333+
334+browser.runtime.onMessage.addListener((message) => {
335+  if (!message || typeof message !== "object") return undefined;
336+  if (message.type !== "baa_page_proxy_request") return undefined;
337+
338+  window.dispatchEvent(new CustomEvent("__baa_proxy_request__", {
339+    detail: JSON.stringify(message.data || {})
340+  }));
341+
342+  return undefined;
343+});
344+
345+browser.storage.onChanged.addListener((changes, areaName) => {
346+  if (areaName !== "local" || !changes[CONTROL_STATE_KEY]) return;
347+  claudeEntryState.snapshot = normalizeEntrySnapshot(changes[CONTROL_STATE_KEY].newValue);
348+  renderClaudeEntry();
349+});
350+
351+if (document.readyState === "loading") {
352+  document.addEventListener("DOMContentLoaded", () => {
353+    ensureClaudeEntry();
354+    loadClaudeEntrySnapshot().catch(() => {});
355+  }, { once: true });
356+} else {
357+  ensureClaudeEntry();
358+  loadClaudeEntrySnapshot().catch(() => {});
359+}
A plugins/baa-firefox/controller.css
+211, -0
  1@@ -0,0 +1,211 @@
  2+:root {
  3+  --bg: #f3efe3;
  4+  --panel: rgba(255, 251, 240, 0.92);
  5+  --ink: #1b1b1b;
  6+  --muted: #625e52;
  7+  --line: rgba(27, 27, 27, 0.14);
  8+  --accent: #1f6f5f;
  9+  --warn: #a6512f;
 10+  --off: #7f7a70;
 11+}
 12+
 13+* {
 14+  box-sizing: border-box;
 15+}
 16+
 17+body {
 18+  margin: 0;
 19+  min-height: 100vh;
 20+  color: var(--ink);
 21+  background:
 22+    radial-gradient(circle at top left, rgba(31, 111, 95, 0.18), transparent 35%),
 23+    radial-gradient(circle at top right, rgba(166, 81, 47, 0.18), transparent 28%),
 24+    linear-gradient(180deg, #faf6ea 0%, var(--bg) 100%);
 25+  font-family: "Iowan Old Style", "Palatino Linotype", "Book Antiqua", serif;
 26+}
 27+
 28+.shell {
 29+  max-width: 1120px;
 30+  margin: 0 auto;
 31+  padding: 24px;
 32+}
 33+
 34+.topbar {
 35+  display: flex;
 36+  justify-content: space-between;
 37+  gap: 16px;
 38+  align-items: end;
 39+  margin-bottom: 18px;
 40+}
 41+
 42+.eyebrow {
 43+  margin: 0 0 4px;
 44+  font-size: 12px;
 45+  text-transform: uppercase;
 46+  letter-spacing: 0.18em;
 47+  color: var(--muted);
 48+}
 49+
 50+h1, h2, p {
 51+  margin: 0;
 52+}
 53+
 54+h1 {
 55+  font-size: 42px;
 56+  line-height: 1;
 57+}
 58+
 59+h2 {
 60+  font-size: 18px;
 61+}
 62+
 63+.actions,
 64+.settings {
 65+  display: flex;
 66+  gap: 10px;
 67+  align-items: center;
 68+  flex-wrap: wrap;
 69+}
 70+
 71+.settings {
 72+  margin-bottom: 18px;
 73+  padding: 14px;
 74+  border: 1px solid var(--line);
 75+  border-radius: 18px;
 76+  background: var(--panel);
 77+  backdrop-filter: blur(12px);
 78+}
 79+
 80+label {
 81+  font-size: 13px;
 82+  color: var(--muted);
 83+}
 84+
 85+input,
 86+button {
 87+  font: inherit;
 88+}
 89+
 90+input {
 91+  min-width: 320px;
 92+  padding: 10px 12px;
 93+  border-radius: 999px;
 94+  border: 1px solid var(--line);
 95+  background: rgba(255, 255, 255, 0.72);
 96+  color: var(--ink);
 97+}
 98+
 99+button {
100+  padding: 10px 14px;
101+  border-radius: 999px;
102+  border: 1px solid var(--line);
103+  background: #fffdf6;
104+  color: var(--ink);
105+  cursor: pointer;
106+}
107+
108+button:hover {
109+  border-color: rgba(27, 27, 27, 0.28);
110+}
111+
112+.grid {
113+  display: grid;
114+  grid-template-columns: repeat(4, minmax(0, 1fr));
115+  gap: 14px;
116+  margin-bottom: 18px;
117+}
118+
119+.card,
120+.panel {
121+  border: 1px solid var(--line);
122+  border-radius: 20px;
123+  background: var(--panel);
124+  backdrop-filter: blur(12px);
125+}
126+
127+.card {
128+  padding: 18px;
129+}
130+
131+.label {
132+  font-size: 12px;
133+  letter-spacing: 0.14em;
134+  text-transform: uppercase;
135+  color: var(--muted);
136+}
137+
138+.value {
139+  margin-top: 12px;
140+  font-size: 28px;
141+  line-height: 1;
142+}
143+
144+.value.on {
145+  color: var(--accent);
146+}
147+
148+.value.warn {
149+  color: var(--warn);
150+}
151+
152+.value.off {
153+  color: var(--off);
154+}
155+
156+.meta {
157+  margin-top: 10px;
158+  font-size: 13px;
159+  color: var(--muted);
160+}
161+
162+.panel {
163+  margin-bottom: 18px;
164+  overflow: hidden;
165+}
166+
167+.panel-head {
168+  display: flex;
169+  justify-content: space-between;
170+  align-items: center;
171+  padding: 16px 18px;
172+  border-bottom: 1px solid var(--line);
173+}
174+
175+.code {
176+  margin: 0;
177+  padding: 18px;
178+  min-height: 100px;
179+  max-height: 300px;
180+  overflow: auto;
181+  font-family: ui-monospace, "SFMono-Regular", "Menlo", monospace;
182+  font-size: 12px;
183+  line-height: 1.55;
184+  white-space: pre-wrap;
185+  word-break: break-word;
186+}
187+
188+@media (max-width: 900px) {
189+  .grid {
190+    grid-template-columns: repeat(2, minmax(0, 1fr));
191+  }
192+}
193+
194+@media (max-width: 640px) {
195+  .shell {
196+    padding: 16px;
197+  }
198+
199+  .topbar {
200+    flex-direction: column;
201+    align-items: start;
202+  }
203+
204+  .grid {
205+    grid-template-columns: 1fr;
206+  }
207+
208+  input {
209+    min-width: 0;
210+    width: 100%;
211+  }
212+}
A plugins/baa-firefox/controller.html
+128, -0
  1@@ -0,0 +1,128 @@
  2+<!doctype html>
  3+<html lang="zh-CN">
  4+<head>
  5+  <meta charset="utf-8">
  6+  <meta name="viewport" content="width=device-width,initial-scale=1">
  7+  <title>BAA Firefox 管理页</title>
  8+  <link rel="stylesheet" href="controller.css">
  9+</head>
 10+<body>
 11+  <main class="shell">
 12+    <section class="topbar">
 13+      <div>
 14+        <p class="eyebrow">BAA Firefox 控制台</p>
 15+        <h1>管理页面</h1>
 16+      </div>
 17+      <div class="actions">
 18+        <button id="focus-controller-btn" type="button">聚焦本页</button>
 19+        <button id="open-claude-btn" type="button">打开 Claude</button>
 20+        <button id="open-chatgpt-btn" type="button">打开 ChatGPT</button>
 21+        <button id="open-gemini-btn" type="button">打开 Gemini</button>
 22+        <button id="reconnect-btn" type="button">重连 WS</button>
 23+      </div>
 24+    </section>
 25+
 26+    <section class="settings">
 27+      <label for="ws-url">BAA WS</label>
 28+      <input id="ws-url" type="text" spellcheck="false">
 29+      <button id="save-ws-btn" type="button">保存</button>
 30+      <label for="control-base-url">控制 API</label>
 31+      <input id="control-base-url" type="text" spellcheck="false" placeholder="http://127.0.0.1:9800">
 32+      <label for="control-token">令牌</label>
 33+      <input id="control-token" type="password" spellcheck="false" placeholder="browser_session token">
 34+      <button id="save-control-btn" type="button">保存控制面配置</button>
 35+      <button id="refresh-control-btn" type="button">刷新状态</button>
 36+      <button id="pause-btn" type="button">暂停</button>
 37+      <button id="resume-btn" type="button">恢复</button>
 38+      <button id="drain-btn" type="button">排空</button>
 39+    </section>
 40+
 41+    <section class="grid">
 42+      <article class="card">
 43+        <p class="label">WS</p>
 44+        <p id="ws-status" class="value off">未连接</p>
 45+        <p id="client-id" class="meta">客户端: -</p>
 46+      </article>
 47+
 48+      <article class="card">
 49+        <p class="label">跟踪标签页</p>
 50+        <p id="tab-status" class="value off">0 / 3</p>
 51+        <p id="tab-meta" class="meta">标签页: -</p>
 52+      </article>
 53+
 54+      <article class="card">
 55+        <p class="label">凭证</p>
 56+        <p id="cred-status" class="value off">0 / 3</p>
 57+        <p id="cred-meta" class="meta">请求头: 0</p>
 58+      </article>
 59+
 60+      <article class="card">
 61+        <p class="label">端点</p>
 62+        <p id="endpoint-count" class="value">0</p>
 63+        <p class="meta">自动探测</p>
 64+      </article>
 65+
 66+      <article class="card">
 67+        <p class="label">自动化模式</p>
 68+        <p id="control-mode" class="value off">未知</p>
 69+        <p id="control-meta" class="meta">未同步</p>
 70+      </article>
 71+
 72+      <article class="card">
 73+        <p class="label">主控</p>
 74+        <p id="leader-value" class="value off">-</p>
 75+        <p id="leader-meta" class="meta">租约: -</p>
 76+      </article>
 77+
 78+      <article class="card">
 79+        <p class="label">队列</p>
 80+        <p id="queue-value" class="value off">-</p>
 81+        <p id="queue-meta" class="meta">排队任务</p>
 82+      </article>
 83+
 84+      <article class="card">
 85+        <p class="label">活动运行</p>
 86+        <p id="runs-value" class="value off">-</p>
 87+        <p id="runs-meta" class="meta">控制面</p>
 88+      </article>
 89+    </section>
 90+
 91+    <section class="panel">
 92+      <div class="panel-head">
 93+        <h2>控制面</h2>
 94+      </div>
 95+      <pre id="control-view" class="code"></pre>
 96+    </section>
 97+
 98+    <section class="panel">
 99+      <div class="panel-head">
100+        <h2>平台状态</h2>
101+      </div>
102+      <pre id="platforms-view" class="code"></pre>
103+    </section>
104+
105+    <section class="panel">
106+      <div class="panel-head">
107+        <h2>最新请求头</h2>
108+      </div>
109+      <pre id="headers-view" class="code"></pre>
110+    </section>
111+
112+    <section class="panel">
113+      <div class="panel-head">
114+        <h2>已发现端点</h2>
115+      </div>
116+      <pre id="endpoints-view" class="code"></pre>
117+    </section>
118+
119+    <section class="panel">
120+      <div class="panel-head">
121+        <h2>日志</h2>
122+      </div>
123+      <pre id="log-view" class="code"></pre>
124+    </section>
125+  </main>
126+
127+  <script src="controller.js"></script>
128+</body>
129+</html>
A plugins/baa-firefox/controller.js
+1774, -0
   1@@ -0,0 +1,1774 @@
   2+const LEGACY_STORAGE_KEYS = {
   3+  claudeTabId: "baaFirefox.claudeTabId",
   4+  endpoints: "baaFirefox.endpoints",
   5+  lastHeaders: "baaFirefox.lastHeaders",
   6+  lastCredentialAt: "baaFirefox.lastCredentialAt"
   7+};
   8+
   9+const CONTROLLER_STORAGE_KEYS = {
  10+  clientId: "baaFirefox.clientId",
  11+  wsUrl: "baaFirefox.wsUrl",
  12+  controlBaseUrl: "baaFirefox.controlBaseUrl",
  13+  controlToken: "baaFirefox.controlToken",
  14+  controlState: "baaFirefox.controlState",
  15+  trackedTabs: "baaFirefox.trackedTabs",
  16+  endpointsByPlatform: "baaFirefox.endpointsByPlatform",
  17+  lastHeadersByPlatform: "baaFirefox.lastHeadersByPlatform",
  18+  lastCredentialAtByPlatform: "baaFirefox.lastCredentialAtByPlatform",
  19+  geminiSendTemplate: "baaFirefox.geminiSendTemplate"
  20+};
  21+
  22+const DEFAULT_WS_URL = "ws://127.0.0.1:9800";
  23+const CREDENTIAL_SEND_INTERVAL = 30_000;
  24+const NETWORK_BODY_LIMIT = 5000;
  25+const LOG_LIMIT = 160;
  26+const PROXY_MESSAGE_RETRY = 10;
  27+const PROXY_MESSAGE_RETRY_DELAY = 400;
  28+const CONTROL_REFRESH_INTERVAL = 15_000;
  29+const CONTROL_STATUS_BODY_LIMIT = 12_000;
  30+const FORBIDDEN_PROXY_HEADER_NAMES = new Set([
  31+  "accept-encoding",
  32+  "connection",
  33+  "content-length",
  34+  "cookie",
  35+  "host",
  36+  "origin",
  37+  "referer",
  38+  "user-agent"
  39+]);
  40+
  41+function hostnameMatches(hostname, hosts) {
  42+  return hosts.some((host) => hostname === host || hostname.endsWith(`.${host}`));
  43+}
  44+
  45+function isLikelyStaticPath(pathname = "") {
  46+  const lower = pathname.toLowerCase();
  47+  return lower.startsWith("/_next/")
  48+    || lower.startsWith("/assets/")
  49+    || lower.startsWith("/static/")
  50+    || lower.startsWith("/images/")
  51+    || lower.startsWith("/fonts/")
  52+    || lower.startsWith("/favicon")
  53+    || /\.(?:js|mjs|css|map|png|jpe?g|gif|svg|webp|ico|woff2?|ttf|otf|mp4|webm|txt)$/i.test(lower);
  54+}
  55+
  56+const PLATFORMS = {
  57+  claude: {
  58+    label: "Claude",
  59+    rootUrl: "https://claude.ai/",
  60+    urlPatterns: ["*://claude.ai/*"],
  61+    hosts: ["claude.ai"],
  62+    requestUrlPatterns: ["*://claude.ai/*"],
  63+    requestHosts: ["claude.ai"],
  64+    matchesTabHost(hostname) {
  65+      return hostnameMatches(hostname, this.hosts);
  66+    },
  67+    matchesRequestHost(hostname) {
  68+      return hostnameMatches(hostname, this.requestHosts);
  69+    },
  70+    shouldTrackPath(pathname) {
  71+      return pathname.includes("/api/");
  72+    }
  73+  },
  74+  chatgpt: {
  75+    label: "ChatGPT",
  76+    rootUrl: "https://chatgpt.com/",
  77+    urlPatterns: ["*://chatgpt.com/*", "*://*.chatgpt.com/*", "*://chat.openai.com/*", "*://*.chat.openai.com/*"],
  78+    hosts: ["chatgpt.com", "chat.openai.com"],
  79+    requestUrlPatterns: [
  80+      "*://chatgpt.com/*",
  81+      "*://*.chatgpt.com/*",
  82+      "*://openai.com/*",
  83+      "*://*.openai.com/*",
  84+      "*://chat.openai.com/*",
  85+      "*://*.chat.openai.com/*",
  86+      "*://oaiusercontent.com/*",
  87+      "*://*.oaiusercontent.com/*"
  88+    ],
  89+    requestHosts: ["chatgpt.com", "chat.openai.com", "openai.com", "oaiusercontent.com"],
  90+    matchesTabHost(hostname) {
  91+      return hostnameMatches(hostname, this.hosts);
  92+    },
  93+    matchesRequestHost(hostname) {
  94+      return hostnameMatches(hostname, this.requestHosts);
  95+    },
  96+    shouldTrackPath(pathname) {
  97+      const lower = pathname.toLowerCase();
  98+      return pathname.includes("/backend-api/")
  99+        || pathname.includes("/backend-anon/")
 100+        || pathname.includes("/public-api/")
 101+        || lower.includes("/conversation")
 102+        || lower.includes("/models")
 103+        || lower.includes("/files")
 104+        || (!isLikelyStaticPath(pathname) && (lower.includes("/api/") || lower.includes("/backend")));
 105+    }
 106+  },
 107+  gemini: {
 108+    label: "Gemini",
 109+    rootUrl: "https://gemini.google.com/",
 110+    urlPatterns: ["*://gemini.google.com/*"],
 111+    hosts: ["gemini.google.com"],
 112+    requestUrlPatterns: ["*://gemini.google.com/*"],
 113+    requestHosts: ["gemini.google.com"],
 114+    matchesTabHost(hostname) {
 115+      return hostnameMatches(hostname, this.hosts);
 116+    },
 117+    matchesRequestHost(hostname) {
 118+      return hostnameMatches(hostname, this.requestHosts);
 119+    },
 120+    shouldTrackPath(pathname) {
 121+      const lower = pathname.toLowerCase();
 122+      return pathname.startsWith("/_/")
 123+        || pathname.includes("/api/")
 124+        || lower.includes("bardchatui")
 125+        || lower.includes("streamgenerate")
 126+        || lower.includes("generatecontent")
 127+        || lower.includes("modelresponse")
 128+        || (!isLikelyStaticPath(pathname) && lower.includes("assistant"));
 129+    }
 130+  }
 131+};
 132+
 133+const PLATFORM_ORDER = Object.keys(PLATFORMS);
 134+const PLATFORM_REQUEST_URL_PATTERNS = PLATFORM_ORDER.flatMap((platform) => PLATFORMS[platform].requestUrlPatterns || PLATFORMS[platform].urlPatterns);
 135+const pendingProxyRequests = new Map();
 136+
 137+const state = {
 138+  clientId: null,
 139+  wsUrl: DEFAULT_WS_URL,
 140+  controlBaseUrl: "",
 141+  controlToken: "",
 142+  controlState: null,
 143+  ws: null,
 144+  wsConnected: false,
 145+  reconnectTimer: null,
 146+  controlRefreshTimer: null,
 147+  trackedTabs: createPlatformMap(() => null),
 148+  endpoints: createPlatformMap(() => ({})),
 149+  lastHeaders: createPlatformMap(() => ({})),
 150+  lastCredentialAt: createPlatformMap(() => 0),
 151+  lastCredentialHash: createPlatformMap(() => ""),
 152+  geminiSendTemplate: null,
 153+  logs: []
 154+};
 155+
 156+const ui = {};
 157+
 158+function qs(id) {
 159+  return document.getElementById(id);
 160+}
 161+
 162+function isRecord(value) {
 163+  return !!value && typeof value === "object" && !Array.isArray(value);
 164+}
 165+
 166+function trimTrailingSlash(value) {
 167+  return String(value || "").trim().replace(/\/+$/u, "");
 168+}
 169+
 170+function deriveControlBaseUrl(wsUrl) {
 171+  try {
 172+    const parsed = new URL(wsUrl || DEFAULT_WS_URL);
 173+    parsed.protocol = parsed.protocol === "wss:" ? "https:" : "http:";
 174+    return trimTrailingSlash(parsed.toString());
 175+  } catch (_) {
 176+    return "http://127.0.0.1:9800";
 177+  }
 178+}
 179+
 180+function createDefaultControlState(overrides = {}) {
 181+  return {
 182+    mode: "unknown",
 183+    leader: null,
 184+    leaseHolder: null,
 185+    queueDepth: null,
 186+    activeRuns: null,
 187+    ok: false,
 188+    error: null,
 189+    message: null,
 190+    statusCode: null,
 191+    syncedAt: 0,
 192+    source: "bootstrap",
 193+    raw: null,
 194+    ...overrides
 195+  };
 196+}
 197+
 198+function cloneControlState(value) {
 199+  if (!isRecord(value)) return createDefaultControlState();
 200+  return {
 201+    ...createDefaultControlState(),
 202+    ...value
 203+  };
 204+}
 205+
 206+function createPlatformMap(factory) {
 207+  const out = {};
 208+  for (const platform of PLATFORM_ORDER) {
 209+    out[platform] = factory(platform);
 210+  }
 211+  return out;
 212+}
 213+
 214+function cloneHeaderMap(value) {
 215+  return value && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
 216+}
 217+
 218+function hasPlatformShape(value) {
 219+  return !!value
 220+    && typeof value === "object"
 221+    && !Array.isArray(value)
 222+    && PLATFORM_ORDER.some((platform) => Object.prototype.hasOwnProperty.call(value, platform));
 223+}
 224+
 225+function loadTrackedTabs(raw, legacyClaudeTabId) {
 226+  const next = createPlatformMap(() => null);
 227+  if (raw && typeof raw === "object" && !Array.isArray(raw)) {
 228+    for (const platform of PLATFORM_ORDER) {
 229+      next[platform] = Number.isInteger(raw[platform]) ? raw[platform] : null;
 230+    }
 231+  }
 232+  if (Number.isInteger(legacyClaudeTabId) && !Number.isInteger(next.claude)) {
 233+    next.claude = legacyClaudeTabId;
 234+  }
 235+  return next;
 236+}
 237+
 238+function loadObjectMap(raw, legacyValue = null) {
 239+  const next = createPlatformMap(() => ({}));
 240+  if (hasPlatformShape(raw)) {
 241+    for (const platform of PLATFORM_ORDER) {
 242+      next[platform] = cloneHeaderMap(raw[platform]);
 243+    }
 244+    return next;
 245+  }
 246+
 247+  if (raw && typeof raw === "object" && !Array.isArray(raw)) {
 248+    next.claude = cloneHeaderMap(raw);
 249+    return next;
 250+  }
 251+
 252+  if (legacyValue && typeof legacyValue === "object" && !Array.isArray(legacyValue)) {
 253+    next.claude = cloneHeaderMap(legacyValue);
 254+  }
 255+  return next;
 256+}
 257+
 258+function loadNumberMap(raw, legacyValue = 0) {
 259+  const next = createPlatformMap(() => 0);
 260+  if (hasPlatformShape(raw)) {
 261+    for (const platform of PLATFORM_ORDER) {
 262+      next[platform] = Number(raw[platform]) || 0;
 263+    }
 264+    return next;
 265+  }
 266+
 267+  if (Number.isFinite(raw)) {
 268+    next.claude = raw;
 269+    return next;
 270+  }
 271+
 272+  if (Number.isFinite(legacyValue)) {
 273+    next.claude = legacyValue;
 274+  }
 275+  return next;
 276+}
 277+
 278+function loadControlState(raw) {
 279+  if (!isRecord(raw)) return createDefaultControlState();
 280+  return cloneControlState(raw);
 281+}
 282+
 283+function getNestedValue(source, path) {
 284+  const segments = String(path || "").split(".");
 285+  let current = source;
 286+
 287+  for (const segment of segments) {
 288+    if (!isRecord(current) || !Object.prototype.hasOwnProperty.call(current, segment)) {
 289+      return undefined;
 290+    }
 291+    current = current[segment];
 292+  }
 293+
 294+  return current;
 295+}
 296+
 297+function getFirstDefinedValue(source, paths) {
 298+  for (const path of paths) {
 299+    const value = getNestedValue(source, path);
 300+    if (value !== undefined && value !== null && value !== "") {
 301+      return value;
 302+    }
 303+  }
 304+  return undefined;
 305+}
 306+
 307+function normalizeMode(value) {
 308+  const lower = String(value || "").trim().toLowerCase();
 309+  if (lower === "running" || lower === "paused" || lower === "draining") {
 310+    return lower;
 311+  }
 312+  return "unknown";
 313+}
 314+
 315+function formatModeLabel(mode) {
 316+  switch (normalizeMode(mode)) {
 317+    case "running":
 318+      return "运行中";
 319+    case "paused":
 320+      return "已暂停";
 321+    case "draining":
 322+      return "排空中";
 323+    default:
 324+      return "未知";
 325+  }
 326+}
 327+
 328+function normalizeLeader(value) {
 329+  if (typeof value === "string" && value.trim()) return value.trim();
 330+  if (isRecord(value)) {
 331+    const candidate = getFirstDefinedValue(value, ["host", "node_id", "nodeId", "lease_holder", "leaseHolder", "controller_id", "controllerId"]);
 332+    return typeof candidate === "string" && candidate.trim() ? candidate.trim() : null;
 333+  }
 334+  return null;
 335+}
 336+
 337+function normalizeCount(value) {
 338+  if (Number.isFinite(value)) return Number(value);
 339+  if (typeof value === "string" && value.trim()) {
 340+    const parsed = Number(value);
 341+    return Number.isFinite(parsed) ? parsed : null;
 342+  }
 343+  if (Array.isArray(value)) return value.length;
 344+  if (isRecord(value)) {
 345+    const candidate = getFirstDefinedValue(value, ["count", "depth", "total", "size", "value"]);
 346+    return normalizeCount(candidate);
 347+  }
 348+  return null;
 349+}
 350+
 351+function truncateControlRaw(value) {
 352+  if (value == null) return null;
 353+
 354+  try {
 355+    const text = typeof value === "string" ? value : JSON.stringify(value, null, 2);
 356+    return text.length > CONTROL_STATUS_BODY_LIMIT
 357+      ? `${text.slice(0, CONTROL_STATUS_BODY_LIMIT)}\n…`
 358+      : text;
 359+  } catch (_) {
 360+    return String(value);
 361+  }
 362+}
 363+
 364+function formatSyncTime(timestamp) {
 365+  if (!Number.isFinite(timestamp) || timestamp <= 0) return "未同步";
 366+  return new Date(timestamp).toLocaleString("zh-CN", { hour12: false });
 367+}
 368+
 369+function controlModeClass(snapshot) {
 370+  if (!snapshot || snapshot.mode === "unknown") return "off";
 371+  if (snapshot.error) return "warn";
 372+  return snapshot.mode === "running" ? "on" : "warn";
 373+}
 374+
 375+function normalizeControlStatePayload(payload, meta = {}) {
 376+  const source = isRecord(payload) ? payload : {};
 377+  const mode = normalizeMode(getFirstDefinedValue(source, [
 378+    "mode",
 379+    "automation.mode",
 380+    "system.mode",
 381+    "system_state.mode",
 382+    "systemState.mode",
 383+    "state.mode",
 384+    "data.mode",
 385+    "data.automation.mode",
 386+    "data.system.mode",
 387+    "data.system_state.mode",
 388+    "value.mode",
 389+    "value_json.mode"
 390+  ]));
 391+  const leader = normalizeLeader(getFirstDefinedValue(source, [
 392+    "leader",
 393+    "leader.host",
 394+    "data.leader",
 395+    "data.leader.host",
 396+    "leader_host",
 397+    "leaderHost",
 398+    "lease_holder",
 399+    "leaseHolder"
 400+  ]));
 401+  const leaseHolder = normalizeLeader(getFirstDefinedValue(source, [
 402+    "lease_holder",
 403+    "leaseHolder",
 404+    "leader.lease_holder",
 405+    "leader.leaseHolder",
 406+    "data.lease_holder",
 407+    "data.leaseHolder"
 408+  ]));
 409+  const queueDepth = normalizeCount(getFirstDefinedValue(source, [
 410+    "queue_depth",
 411+    "queueDepth",
 412+    "queued_tasks",
 413+    "queuedTasks",
 414+    "queue.depth",
 415+    "queue.count",
 416+    "stats.queue_depth",
 417+    "stats.queueDepth",
 418+    "data.queue_depth",
 419+    "data.queueDepth",
 420+    "data.queued_tasks",
 421+    "data.queuedTasks",
 422+    "data.queue.depth",
 423+    "data.stats.queue_depth",
 424+    "data.stats.queueDepth"
 425+  ]));
 426+  const activeRuns = normalizeCount(getFirstDefinedValue(source, [
 427+    "active_runs",
 428+    "activeRuns",
 429+    "runs.active",
 430+    "runs.active_count",
 431+    "stats.active_runs",
 432+    "stats.activeRuns",
 433+    "data.active_runs",
 434+    "data.activeRuns",
 435+    "data.runs.active",
 436+    "data.stats.active_runs",
 437+    "data.stats.activeRuns"
 438+  ]));
 439+  const message = getFirstDefinedValue(source, ["message", "data.message"]);
 440+  const apiReportedError = getFirstDefinedValue(source, ["error", "data.error"]);
 441+  const ok = meta.ok !== false && source.ok !== false && !apiReportedError;
 442+
 443+  return createDefaultControlState({
 444+    ok,
 445+    mode,
 446+    leader,
 447+    leaseHolder,
 448+    queueDepth,
 449+    activeRuns,
 450+    message: typeof message === "string" ? message : null,
 451+    error: ok ? null : String(apiReportedError || message || meta.error || "控制面错误"),
 452+    statusCode: Number.isFinite(meta.statusCode) ? meta.statusCode : null,
 453+    syncedAt: Date.now(),
 454+    source: meta.source || "http",
 455+    raw: truncateControlRaw(payload)
 456+  });
 457+}
 458+
 459+function genClientId() {
 460+  return `firefox-${Math.random().toString(36).slice(2, 8)}`;
 461+}
 462+
 463+function platformLabel(platform) {
 464+  return PLATFORMS[platform]?.label || platform;
 465+}
 466+
 467+function detectPlatformFromUrl(url) {
 468+  try {
 469+    const hostname = new URL(url, location.href).hostname;
 470+    for (const platform of PLATFORM_ORDER) {
 471+      if (PLATFORMS[platform].matchesTabHost(hostname)) return platform;
 472+    }
 473+  } catch (_) {}
 474+  return null;
 475+}
 476+
 477+function detectPlatformFromRequestUrl(url) {
 478+  try {
 479+    const hostname = new URL(url, location.href).hostname;
 480+    for (const platform of PLATFORM_ORDER) {
 481+      if (PLATFORMS[platform].matchesRequestHost(hostname)) return platform;
 482+    }
 483+  } catch (_) {}
 484+  return null;
 485+}
 486+
 487+function isPlatformUrl(platform, url) {
 488+  return detectPlatformFromUrl(url) === platform;
 489+}
 490+
 491+function shouldTrackRequest(platform, url) {
 492+  const config = PLATFORMS[platform];
 493+  if (!config) return false;
 494+  try {
 495+    const parsed = new URL(url, location.href);
 496+    if (!config.matchesRequestHost(parsed.hostname)) return false;
 497+    return config.shouldTrackPath(parsed.pathname || "/");
 498+  } catch (_) {
 499+    return false;
 500+  }
 501+}
 502+
 503+function findTrackedPlatformByTabId(tabId) {
 504+  if (!Number.isInteger(tabId)) return null;
 505+  for (const platform of PLATFORM_ORDER) {
 506+    if (state.trackedTabs[platform] === tabId) return platform;
 507+  }
 508+  return null;
 509+}
 510+
 511+function addLog(level, text, sendRemote = true) {
 512+  const line = `[${new Date().toLocaleTimeString("zh-CN", { hour12: false })}] [${level}] ${text}`;
 513+  state.logs.push(line);
 514+  if (state.logs.length > LOG_LIMIT) state.logs.shift();
 515+  render();
 516+
 517+  if (sendRemote) {
 518+    wsSend({
 519+      type: "client_log",
 520+      clientId: state.clientId,
 521+      level,
 522+      text
 523+    });
 524+  }
 525+}
 526+
 527+function normalizePath(url) {
 528+  try {
 529+    const parsed = new URL(url);
 530+    let path = parsed.pathname || "/";
 531+    path = path.replace(/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/gi, "{id}");
 532+    path = path.replace(/\/\d{6,}\b/g, "/{id}");
 533+    path = path.replace(/\/[a-f0-9]{24,}\b/gi, "/{id}");
 534+    return path;
 535+  } catch (_) {
 536+    return url;
 537+  }
 538+}
 539+
 540+function headerArrayToObject(headers = []) {
 541+  const out = {};
 542+  for (const header of headers) {
 543+    if (!header || !header.name) continue;
 544+    out[header.name.toLowerCase()] = header.value || "";
 545+  }
 546+  return out;
 547+}
 548+
 549+function mergeKnownHeaders(platform, extra = {}) {
 550+  const known = state.lastHeaders[platform] || {};
 551+  if (Object.keys(known).length === 0) return extra;
 552+  return { ...known, ...extra };
 553+}
 554+
 555+function trimBody(body) {
 556+  if (body == null) return null;
 557+  const text = typeof body === "string" ? body : JSON.stringify(body);
 558+  return text.length > NETWORK_BODY_LIMIT ? text.slice(0, NETWORK_BODY_LIMIT) : text;
 559+}
 560+
 561+function normalizeStoredUrl(url, baseUrl = location.href) {
 562+  try {
 563+    const parsed = new URL(url, baseUrl);
 564+    return `${parsed.pathname || "/"}${parsed.search || ""}`;
 565+  } catch (_) {
 566+    return url;
 567+  }
 568+}
 569+
 570+function isGeminiStreamGenerateUrl(url) {
 571+  try {
 572+    const parsed = new URL(url, PLATFORMS.gemini.rootUrl);
 573+    return (parsed.pathname || "").toLowerCase().includes("streamgenerate");
 574+  } catch (_) {
 575+    return String(url || "").toLowerCase().includes("streamgenerate");
 576+  }
 577+}
 578+
 579+function hasGeminiTemplateHeaders(headers) {
 580+  const source = headers && typeof headers === "object" ? headers : {};
 581+  const contentType = String(source["content-type"] || "").toLowerCase();
 582+  return contentType.startsWith("application/x-www-form-urlencoded")
 583+    && (source["x-same-domain"] === "1" || Object.keys(source).some((name) => name.startsWith("x-goog-ext-")));
 584+}
 585+
 586+function parseGeminiSendTemplate(url, reqBody, reqHeaders = null) {
 587+  if (!isGeminiStreamGenerateUrl(url) || typeof reqBody !== "string" || !reqBody) return null;
 588+
 589+  try {
 590+    const normalizedUrl = normalizeStoredUrl(url, PLATFORMS.gemini.rootUrl);
 591+    const parsedUrl = new URL(normalizedUrl, PLATFORMS.gemini.rootUrl);
 592+    const headers = cloneHeaderMap(reqHeaders);
 593+    if (Object.keys(headers).length > 0 && !hasGeminiTemplateHeaders(headers)) return null;
 594+    const params = new URLSearchParams(reqBody);
 595+    const outerPayload = params.get("f.req");
 596+    if (!outerPayload) return null;
 597+
 598+    const outer = JSON.parse(outerPayload);
 599+    if (!Array.isArray(outer) || typeof outer[1] !== "string") return null;
 600+
 601+    const inner = JSON.parse(outer[1]);
 602+    if (!Array.isArray(inner) || !Array.isArray(inner[0])) return null;
 603+
 604+    return {
 605+      url: normalizedUrl,
 606+      reqBody,
 607+      headers,
 608+      prompt: typeof inner[0][0] === "string" ? inner[0][0] : "",
 609+      reqId: Number(parsedUrl.searchParams.get("_reqid")) || null,
 610+      updatedAt: Date.now()
 611+    };
 612+  } catch (_) {
 613+    return null;
 614+  }
 615+}
 616+
 617+function rememberGeminiSendTemplate(url, reqBody, reqHeaders = null) {
 618+  const next = parseGeminiSendTemplate(url, reqBody, reqHeaders);
 619+  if (!next) return false;
 620+
 621+  const previous = state.geminiSendTemplate;
 622+  if (Number.isFinite(previous?.reqId) && Number.isFinite(next.reqId)) {
 623+    next.reqId = Math.max(previous.reqId, next.reqId);
 624+  } else if (Number.isFinite(previous?.reqId) && !Number.isFinite(next.reqId)) {
 625+    next.reqId = previous.reqId;
 626+  }
 627+
 628+  const changed = !previous
 629+    || previous.url !== next.url
 630+    || previous.reqBody !== next.reqBody
 631+    || previous.prompt !== next.prompt
 632+    || previous.reqId !== next.reqId
 633+    || JSON.stringify(previous.headers || {}) !== JSON.stringify(next.headers || {});
 634+
 635+  state.geminiSendTemplate = next;
 636+  persistState().catch(() => {});
 637+
 638+  if (changed) {
 639+    addLog("info", `已捕获 Gemini 发送模板 reqid=${next.reqId || "-"}`);
 640+  }
 641+  return true;
 642+}
 643+
 644+function extractGeminiXsrfToken(body) {
 645+  const match = String(body || "").match(/"xsrf","([^"]+)"/);
 646+  return match ? match[1] : null;
 647+}
 648+
 649+function updateGeminiTemplateXsrf(xsrfToken) {
 650+  if (!xsrfToken || !state.geminiSendTemplate?.reqBody) return false;
 651+
 652+  const params = new URLSearchParams(state.geminiSendTemplate.reqBody);
 653+  params.set("at", xsrfToken);
 654+  state.geminiSendTemplate = {
 655+    ...state.geminiSendTemplate,
 656+    reqBody: params.toString(),
 657+    updatedAt: Date.now()
 658+  };
 659+  persistState().catch(() => {});
 660+  return true;
 661+}
 662+
 663+function extractPromptFromProxyBody(body) {
 664+  if (typeof body === "string") {
 665+    const text = body.trim();
 666+    if (!text) return null;
 667+    if (text.includes("f.req=") || text.includes("&at=") || text.startsWith("{") || text.startsWith("[")) {
 668+      return null;
 669+    }
 670+    return text;
 671+  }
 672+  if (!body || typeof body !== "object") return null;
 673+
 674+  const prompt = body.text ?? body.prompt ?? body.message;
 675+  return typeof prompt === "string" && prompt.trim() ? prompt.trim() : null;
 676+}
 677+
 678+function sleep(ms) {
 679+  return new Promise((resolve) => setTimeout(resolve, ms));
 680+}
 681+
 682+async function persistState() {
 683+  await browser.storage.local.set({
 684+    [CONTROLLER_STORAGE_KEYS.clientId]: state.clientId,
 685+    [CONTROLLER_STORAGE_KEYS.wsUrl]: state.wsUrl,
 686+    [CONTROLLER_STORAGE_KEYS.controlBaseUrl]: state.controlBaseUrl,
 687+    [CONTROLLER_STORAGE_KEYS.controlToken]: state.controlToken,
 688+    [CONTROLLER_STORAGE_KEYS.controlState]: state.controlState,
 689+    [CONTROLLER_STORAGE_KEYS.trackedTabs]: state.trackedTabs,
 690+    [CONTROLLER_STORAGE_KEYS.endpointsByPlatform]: state.endpoints,
 691+    [CONTROLLER_STORAGE_KEYS.lastHeadersByPlatform]: state.lastHeaders,
 692+    [CONTROLLER_STORAGE_KEYS.lastCredentialAtByPlatform]: state.lastCredentialAt,
 693+    [CONTROLLER_STORAGE_KEYS.geminiSendTemplate]: state.geminiSendTemplate
 694+  });
 695+}
 696+
 697+function getTrackedCount() {
 698+  return PLATFORM_ORDER.filter((platform) => Number.isInteger(state.trackedTabs[platform])).length;
 699+}
 700+
 701+function getCredentialCount() {
 702+  return PLATFORM_ORDER.filter((platform) => Object.keys(state.lastHeaders[platform]).length > 0).length;
 703+}
 704+
 705+function getEndpointCount(platform) {
 706+  return Object.keys(state.endpoints[platform] || {}).length;
 707+}
 708+
 709+function getTotalEndpointCount() {
 710+  return PLATFORM_ORDER.reduce((sum, platform) => sum + getEndpointCount(platform), 0);
 711+}
 712+
 713+function renderPlatformStatus() {
 714+  const lines = [];
 715+  for (const platform of PLATFORM_ORDER) {
 716+    const tabId = Number.isInteger(state.trackedTabs[platform]) ? state.trackedTabs[platform] : "-";
 717+    const headerCount = Object.keys(state.lastHeaders[platform]).length;
 718+    const endpointCount = getEndpointCount(platform);
 719+    lines.push(
 720+      `${platformLabel(platform).padEnd(8)} 标签页=${String(tabId).padEnd(4)} 凭证=${String(headerCount).padEnd(3)} 端点=${endpointCount}`
 721+    );
 722+  }
 723+  return lines.join("\n");
 724+}
 725+
 726+function renderHeaderSnapshot() {
 727+  const snapshot = {};
 728+  for (const platform of PLATFORM_ORDER) {
 729+    if (Object.keys(state.lastHeaders[platform]).length > 0) {
 730+      snapshot[platform] = state.lastHeaders[platform];
 731+    }
 732+  }
 733+  return Object.keys(snapshot).length > 0
 734+    ? JSON.stringify(snapshot, null, 2)
 735+    : "还没有凭证快照。";
 736+}
 737+
 738+function renderEndpointSnapshot() {
 739+  const lines = [];
 740+  for (const platform of PLATFORM_ORDER) {
 741+    const endpoints = Object.keys(state.endpoints[platform]).sort();
 742+    if (endpoints.length === 0) continue;
 743+    lines.push(`[${platformLabel(platform)}]`);
 744+    lines.push(...endpoints);
 745+    lines.push("");
 746+  }
 747+  if (lines.length === 0) return "还没有发现端点。";
 748+  if (lines[lines.length - 1] === "") lines.pop();
 749+  return lines.join("\n");
 750+}
 751+
 752+function renderControlSnapshot() {
 753+  const snapshot = cloneControlState(state.controlState);
 754+
 755+  return JSON.stringify({
 756+    ok: snapshot.ok,
 757+    mode: snapshot.mode,
 758+    leader: snapshot.leader,
 759+    leaseHolder: snapshot.leaseHolder,
 760+    queueDepth: snapshot.queueDepth,
 761+    activeRuns: snapshot.activeRuns,
 762+    statusCode: snapshot.statusCode,
 763+    syncedAt: snapshot.syncedAt ? new Date(snapshot.syncedAt).toISOString() : null,
 764+    source: snapshot.source,
 765+    error: snapshot.error,
 766+    message: snapshot.message,
 767+    raw: snapshot.raw
 768+  }, null, 2);
 769+}
 770+
 771+function render() {
 772+  const trackedCount = getTrackedCount();
 773+  const credentialCount = getCredentialCount();
 774+  const totalEndpointCount = getTotalEndpointCount();
 775+  const controlSnapshot = cloneControlState(state.controlState);
 776+
 777+  ui.wsStatus.textContent = state.wsConnected ? "已连接" : "未连接";
 778+  ui.wsStatus.className = `value ${state.wsConnected ? "on" : "off"}`;
 779+
 780+  ui.tabStatus.textContent = `${trackedCount} / ${PLATFORM_ORDER.length}`;
 781+  ui.tabStatus.className = `value ${trackedCount === 0 ? "off" : trackedCount === PLATFORM_ORDER.length ? "on" : "warn"}`;
 782+  ui.tabMeta.textContent = trackedCount > 0
 783+    ? PLATFORM_ORDER
 784+        .filter((platform) => Number.isInteger(state.trackedTabs[platform]))
 785+        .map((platform) => `${platformLabel(platform)}:${state.trackedTabs[platform]}`)
 786+        .join(" | ")
 787+    : "标签页: -";
 788+
 789+  ui.credStatus.textContent = `${credentialCount} / ${PLATFORM_ORDER.length}`;
 790+  ui.credStatus.className = `value ${credentialCount === 0 ? "off" : credentialCount === PLATFORM_ORDER.length ? "on" : "warn"}`;
 791+  ui.credMeta.textContent = `请求头: ${PLATFORM_ORDER.reduce((sum, platform) => sum + Object.keys(state.lastHeaders[platform]).length, 0)}`;
 792+
 793+  ui.endpointCount.textContent = String(totalEndpointCount);
 794+  ui.endpointCount.className = `value ${totalEndpointCount > 0 ? "on" : "off"}`;
 795+  ui.clientId.textContent = `客户端: ${state.clientId || "-"}`;
 796+  ui.controlMode.textContent = formatModeLabel(controlSnapshot.mode);
 797+  ui.controlMode.className = `value ${controlModeClass(controlSnapshot)}`;
 798+  ui.controlMeta.textContent = `${formatSyncTime(controlSnapshot.syncedAt)}${controlSnapshot.error ? ` · ${controlSnapshot.error}` : ""}`;
 799+  ui.leaderValue.textContent = controlSnapshot.leader || "-";
 800+  ui.leaderValue.className = `value ${controlSnapshot.leader ? "on" : "off"}`;
 801+  ui.leaderMeta.textContent = `租约: ${controlSnapshot.leaseHolder || "-"}`;
 802+  ui.queueValue.textContent = controlSnapshot.queueDepth == null ? "-" : String(controlSnapshot.queueDepth);
 803+  ui.queueValue.className = `value ${controlSnapshot.queueDepth > 0 ? "warn" : controlSnapshot.queueDepth === 0 ? "on" : "off"}`;
 804+  ui.queueMeta.textContent = "排队任务";
 805+  ui.runsValue.textContent = controlSnapshot.activeRuns == null ? "-" : String(controlSnapshot.activeRuns);
 806+  ui.runsValue.className = `value ${controlSnapshot.activeRuns > 0 ? "warn" : controlSnapshot.activeRuns === 0 ? "on" : "off"}`;
 807+  ui.runsMeta.textContent = controlSnapshot.message || "控制面";
 808+  ui.platformsView.textContent = renderPlatformStatus();
 809+  ui.controlView.textContent = renderControlSnapshot();
 810+  ui.headersView.textContent = renderHeaderSnapshot();
 811+  ui.endpointsView.textContent = renderEndpointSnapshot();
 812+  ui.logView.textContent = state.logs.length > 0
 813+    ? state.logs.join("\n")
 814+    : "还没有日志。";
 815+}
 816+
 817+async function setControlState(next) {
 818+  state.controlState = cloneControlState(next);
 819+  await persistState();
 820+  render();
 821+}
 822+
 823+async function requestControlPlane(path, options = {}) {
 824+  const baseUrl = trimTrailingSlash(state.controlBaseUrl || deriveControlBaseUrl(state.wsUrl));
 825+  if (!baseUrl) {
 826+    throw new Error("缺少控制 API 地址");
 827+  }
 828+
 829+  const url = `${baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
 830+  const headers = new Headers({
 831+    Accept: "application/json"
 832+  });
 833+
 834+  if (state.controlToken) {
 835+    headers.set("Authorization", `Bearer ${state.controlToken}`);
 836+  }
 837+
 838+  if (options.body != null && !headers.has("Content-Type")) {
 839+    headers.set("Content-Type", "application/json");
 840+  }
 841+
 842+  const response = await fetch(url, {
 843+    method: options.method || "GET",
 844+    headers,
 845+    body: options.body == null ? undefined : options.body
 846+  });
 847+
 848+  const responseText = await response.text();
 849+  let payload = null;
 850+
 851+  if (responseText) {
 852+    try {
 853+      payload = JSON.parse(responseText);
 854+    } catch (_) {
 855+      payload = responseText;
 856+    }
 857+  }
 858+
 859+  if (!response.ok || (isRecord(payload) && payload.ok === false)) {
 860+    const message = isRecord(payload)
 861+      ? String(payload.message || payload.error || `${response.status}`)
 862+      : String(responseText || response.status);
 863+    const error = new Error(message);
 864+    error.statusCode = response.status;
 865+    error.payload = payload;
 866+    throw error;
 867+  }
 868+
 869+  return {
 870+    statusCode: response.status,
 871+    payload
 872+  };
 873+}
 874+
 875+async function refreshControlPlaneState(options = {}) {
 876+  try {
 877+    const response = await requestControlPlane("/v1/system/state");
 878+    const snapshot = normalizeControlStatePayload(response.payload, {
 879+      ok: true,
 880+      statusCode: response.statusCode,
 881+      source: options.source || "http"
 882+    });
 883+    await setControlState(snapshot);
 884+
 885+    if (!options.silent) {
 886+      addLog("info", `控制面状态已同步:模式=${formatModeLabel(snapshot.mode)},主控=${snapshot.leader || "-"}`);
 887+    }
 888+
 889+    return snapshot;
 890+  } catch (error) {
 891+    const snapshot = normalizeControlStatePayload(error.payload || error.message, {
 892+      ok: false,
 893+      statusCode: Number.isFinite(error.statusCode) ? error.statusCode : null,
 894+      source: options.source || "http",
 895+      error: error.message
 896+    });
 897+    await setControlState(snapshot);
 898+
 899+    if (!options.silent) {
 900+      addLog("error", `控制面状态同步失败:${error.message}`);
 901+    }
 902+
 903+    throw error;
 904+  }
 905+}
 906+
 907+async function runControlPlaneAction(action, options = {}) {
 908+  const methodName = String(action || "").trim().toLowerCase();
 909+
 910+  if (!["pause", "resume", "drain"].includes(methodName)) {
 911+    throw new Error(`未知控制动作:${action || "-"}`);
 912+  }
 913+
 914+  const response = await requestControlPlane(`/v1/system/${methodName}`, {
 915+    method: "POST"
 916+  });
 917+
 918+  addLog("info", `控制动作 ${methodName} 已接受(${response.statusCode})`);
 919+
 920+  try {
 921+    await refreshControlPlaneState({
 922+      source: options.source || "http",
 923+      silent: true
 924+    });
 925+  } catch (_) {}
 926+
 927+  return {
 928+    action: methodName,
 929+    statusCode: response.statusCode,
 930+    payload: response.payload,
 931+    snapshot: cloneControlState(state.controlState)
 932+  };
 933+}
 934+
 935+function restartControlPlaneRefreshTimer() {
 936+  clearInterval(state.controlRefreshTimer);
 937+  state.controlRefreshTimer = setInterval(() => {
 938+    refreshControlPlaneState({ source: "poll", silent: true }).catch(() => {});
 939+  }, CONTROL_REFRESH_INTERVAL);
 940+}
 941+
 942+function wsSend(payload) {
 943+  if (!state.ws || state.ws.readyState !== WebSocket.OPEN) return false;
 944+  state.ws.send(JSON.stringify(payload));
 945+  return true;
 946+}
 947+
 948+function sendApiResponse(id, ok, status = null, body = null, error = null) {
 949+  wsSend({
 950+    type: "api_response",
 951+    id,
 952+    ok,
 953+    status,
 954+    body,
 955+    error
 956+  });
 957+}
 958+
 959+function sendHello() {
 960+  wsSend({
 961+    type: "hello",
 962+    clientId: state.clientId,
 963+    nodeType: "browser",
 964+    nodeCategory: "proxy",
 965+    nodePlatform: "firefox"
 966+  });
 967+}
 968+
 969+function getTargetPlatforms(platform) {
 970+  if (platform && PLATFORMS[platform]) return [platform];
 971+  return PLATFORM_ORDER;
 972+}
 973+
 974+function getProxyHeaderPath(apiPath) {
 975+  try {
 976+    return new URL(apiPath, "https://example.invalid").pathname || "/";
 977+  } catch (_) {
 978+    return apiPath || "/";
 979+  }
 980+}
 981+
 982+function isForbiddenProxyHeader(name) {
 983+  return FORBIDDEN_PROXY_HEADER_NAMES.has(name) || name.startsWith("sec-");
 984+}
 985+
 986+function copyProxyHeaders(sourceHeaders = {}) {
 987+  const out = {};
 988+
 989+  for (const [name, value] of Object.entries(sourceHeaders || {})) {
 990+    const lower = String(name || "").toLowerCase();
 991+    if (!lower || value == null || value === "" || isForbiddenProxyHeader(lower)) continue;
 992+    out[lower] = value;
 993+  }
 994+  return out;
 995+}
 996+
 997+function buildProxyHeaders(platform, apiPath, sourceHeaders = null) {
 998+  const targetPath = getProxyHeaderPath(apiPath);
 999+  const out = copyProxyHeaders(sourceHeaders || state.lastHeaders[platform] || {});
1000+
1001+  if (platform === "chatgpt") {
1002+    out["x-openai-target-path"] = targetPath;
1003+    out["x-openai-target-route"] = targetPath;
1004+    if (!out["x-conduit-token"]) out["x-conduit-token"] = "no-token";
1005+    if (!out["x-oai-turn-trace-id"] && typeof crypto?.randomUUID === "function") {
1006+      out["x-oai-turn-trace-id"] = crypto.randomUUID();
1007+    }
1008+  }
1009+
1010+  return out;
1011+}
1012+
1013+function buildGeminiAutoRequest(prompt) {
1014+  const template = state.geminiSendTemplate;
1015+  if (!template?.url || !template?.reqBody) {
1016+    throw new Error("missing Gemini send template; send one real Gemini message first");
1017+  }
1018+
1019+  try {
1020+    const url = new URL(template.url, PLATFORMS.gemini.rootUrl);
1021+    const params = new URLSearchParams(template.reqBody);
1022+    const outerPayload = params.get("f.req");
1023+    if (!outerPayload) throw new Error("template missing f.req");
1024+
1025+    const outer = JSON.parse(outerPayload);
1026+    if (!Array.isArray(outer) || typeof outer[1] !== "string") {
1027+      throw new Error("invalid Gemini outer payload");
1028+    }
1029+
1030+    const inner = JSON.parse(outer[1]);
1031+    if (!Array.isArray(inner) || !Array.isArray(inner[0])) {
1032+      throw new Error("无效的 Gemini 提示词元组");
1033+    }
1034+
1035+    inner[0][0] = prompt;
1036+    outer[1] = JSON.stringify(inner);
1037+    params.set("f.req", JSON.stringify(outer));
1038+
1039+    const currentReqId = Number(url.searchParams.get("_reqid"));
1040+    const baseReqId = Number.isFinite(template.reqId) ? template.reqId : currentReqId;
1041+    if (Number.isFinite(baseReqId)) {
1042+      const nextReqId = baseReqId + 100000;
1043+      url.searchParams.set("_reqid", String(nextReqId));
1044+      state.geminiSendTemplate = {
1045+        ...template,
1046+        reqId: nextReqId,
1047+        updatedAt: Date.now()
1048+      };
1049+      persistState().catch(() => {});
1050+    }
1051+
1052+    const path = `${url.pathname || "/"}${url.search || ""}`;
1053+    const headerSource = hasGeminiTemplateHeaders(template.headers)
1054+      ? template.headers
1055+      : state.lastHeaders.gemini;
1056+    const headers = buildProxyHeaders("gemini", path, headerSource);
1057+    if (!hasGeminiTemplateHeaders(headers)) {
1058+      throw new Error("缺少 Gemini 请求头;请先手动发送一条真实 Gemini 消息");
1059+    }
1060+
1061+    return {
1062+      path,
1063+      body: params.toString(),
1064+      headers
1065+    };
1066+  } catch (error) {
1067+    throw new Error(`构建 Gemini 请求失败:${error.message}`);
1068+  }
1069+}
1070+
1071+function sendEndpointSnapshot(platform = null) {
1072+  for (const target of getTargetPlatforms(platform)) {
1073+    const endpoints = Object.keys(state.endpoints[target]).sort();
1074+    if (endpoints.length === 0) continue;
1075+    wsSend({
1076+      type: "api_endpoints",
1077+      platform: target,
1078+      endpoints
1079+    });
1080+  }
1081+}
1082+
1083+function sendCredentialSnapshot(platform = null, force = false) {
1084+  let changed = false;
1085+
1086+  for (const target of getTargetPlatforms(platform)) {
1087+    const headers = state.lastHeaders[target];
1088+    if (Object.keys(headers).length === 0) continue;
1089+
1090+    const now = Date.now();
1091+    const serialized = JSON.stringify(headers);
1092+    if (!force && serialized === state.lastCredentialHash[target] && now - state.lastCredentialAt[target] < CREDENTIAL_SEND_INTERVAL) {
1093+      continue;
1094+    }
1095+
1096+    state.lastCredentialHash[target] = serialized;
1097+    state.lastCredentialAt[target] = now;
1098+    changed = true;
1099+
1100+    wsSend({
1101+      type: "credentials",
1102+      platform: target,
1103+      headers,
1104+      timestamp: now
1105+    });
1106+  }
1107+
1108+  if (changed) {
1109+    persistState().catch(() => {});
1110+  }
1111+}
1112+
1113+function connectWs() {
1114+  clearTimeout(state.reconnectTimer);
1115+
1116+  if (state.ws) {
1117+    state.ws.onopen = null;
1118+    state.ws.onmessage = null;
1119+    state.ws.onclose = null;
1120+    state.ws.onerror = null;
1121+    try {
1122+      state.ws.close();
1123+    } catch (_) {}
1124+  }
1125+
1126+  addLog("info", `正在连接 WS:${state.wsUrl}`, false);
1127+
1128+  try {
1129+    state.ws = new WebSocket(state.wsUrl);
1130+  } catch (error) {
1131+    addLog("error", `WS 创建失败:${error.message}`, false);
1132+    scheduleReconnect();
1133+    return;
1134+  }
1135+
1136+  state.ws.onopen = () => {
1137+    state.wsConnected = true;
1138+    render();
1139+    sendHello();
1140+    addLog("info", "WS 已连接");
1141+    sendCredentialSnapshot(null, true);
1142+    sendEndpointSnapshot();
1143+  };
1144+
1145+  state.ws.onmessage = (event) => {
1146+    let message = null;
1147+    try {
1148+      message = JSON.parse(event.data);
1149+    } catch (_) {
1150+      return;
1151+    }
1152+
1153+    if (!message || typeof message !== "object") return;
1154+
1155+    switch (message.type) {
1156+      case "open_tab": {
1157+        const targets = getTargetPlatforms(message.platform);
1158+        for (const target of targets) {
1159+          ensurePlatformTab(target, { focus: targets.length === 1 }).catch(() => {});
1160+        }
1161+        break;
1162+      }
1163+      case "api_request":
1164+        proxyApiRequest(message).catch((error) => {
1165+          sendApiResponse(message.id, false, null, null, error.message);
1166+          addLog("error", `代理 ${message.platform || "未知"} ${message.method || "GET"} ${message.path || "-"} 失败:${error.message}`);
1167+        });
1168+        break;
1169+      case "request_credentials":
1170+        sendCredentialSnapshot(message.platform || null, true);
1171+        break;
1172+      case "reload":
1173+        addLog("warn", "收到重载命令");
1174+        window.location.reload();
1175+        break;
1176+      default:
1177+        break;
1178+    }
1179+  };
1180+
1181+  state.ws.onclose = (event) => {
1182+    state.wsConnected = false;
1183+    render();
1184+    addLog("warn", `WS 已关闭 code=${event.code || 0} reason=${event.reason || "-"}`, false);
1185+    scheduleReconnect();
1186+  };
1187+
1188+  state.ws.onerror = () => {
1189+    state.wsConnected = false;
1190+    render();
1191+    addLog("error", `WS 错误:${state.wsUrl}`, false);
1192+  };
1193+}
1194+
1195+function scheduleReconnect() {
1196+  clearTimeout(state.reconnectTimer);
1197+  state.reconnectTimer = setTimeout(() => {
1198+    connectWs();
1199+  }, 3000);
1200+}
1201+
1202+async function resolveTrackedTab(platform) {
1203+  const tabId = state.trackedTabs[platform];
1204+  if (!Number.isInteger(tabId)) return null;
1205+  try {
1206+    const tab = await browser.tabs.get(tabId);
1207+    if (!tab || !tab.url || !isPlatformUrl(platform, tab.url)) return null;
1208+    return tab;
1209+  } catch (_) {
1210+    return null;
1211+  }
1212+}
1213+
1214+async function findPlatformTab(platform) {
1215+  const tabs = await browser.tabs.query({ url: PLATFORMS[platform].urlPatterns });
1216+  if (tabs.length === 0) return null;
1217+  tabs.sort((left, right) => {
1218+    if (!!left.active !== !!right.active) return left.active ? -1 : 1;
1219+    const leftAccess = Number(left.lastAccessed) || 0;
1220+    const rightAccess = Number(right.lastAccessed) || 0;
1221+    return rightAccess - leftAccess;
1222+  });
1223+  return tabs[0];
1224+}
1225+
1226+async function setTrackedTab(platform, tab) {
1227+  state.trackedTabs[platform] = tab ? tab.id : null;
1228+  await persistState();
1229+  render();
1230+}
1231+
1232+async function ensurePlatformTab(platform, options = {}) {
1233+  const { focus = false, reloadIfExisting = false } = options;
1234+  let existed = true;
1235+  let tab = await resolveTrackedTab(platform);
1236+  if (!tab) {
1237+    tab = await findPlatformTab(platform);
1238+  }
1239+
1240+  if (!tab) {
1241+    existed = false;
1242+    tab = await browser.tabs.create({
1243+      url: PLATFORMS[platform].rootUrl,
1244+      active: focus
1245+    });
1246+    addLog("info", `已打开 ${platformLabel(platform)} 标签页 ${tab.id}`);
1247+  } else if (focus) {
1248+    await browser.tabs.update(tab.id, { active: true });
1249+    if (tab.windowId != null) {
1250+      await browser.windows.update(tab.windowId, { focused: true });
1251+    }
1252+  }
1253+
1254+  await setTrackedTab(platform, tab);
1255+
1256+  if (existed && reloadIfExisting) {
1257+    try {
1258+      await browser.tabs.reload(tab.id);
1259+      addLog("info", `已重新加载现有 ${platformLabel(platform)} 标签页 ${tab.id}`);
1260+    } catch (error) {
1261+      addLog("error", `重新加载 ${platformLabel(platform)} 失败:${error.message}`);
1262+    }
1263+  }
1264+
1265+  return tab;
1266+}
1267+
1268+async function ensureAllPlatformTabs(options = {}) {
1269+  for (const platform of PLATFORM_ORDER) {
1270+    await ensurePlatformTab(platform, { focus: false, ...options });
1271+  }
1272+}
1273+
1274+function collectEndpoint(platform, method, url) {
1275+  if (!shouldTrackRequest(platform, url)) return;
1276+
1277+  const key = `${(method || "GET").toUpperCase()} ${normalizePath(url)}`;
1278+  if (state.endpoints[platform][key]) return;
1279+
1280+  state.endpoints[platform][key] = true;
1281+  persistState().catch(() => {});
1282+  render();
1283+  addLog("info", `已发现 ${platformLabel(platform)} 端点 ${key}`);
1284+  sendEndpointSnapshot(platform);
1285+}
1286+
1287+function buildNetworkEntry(platform, data, tabId) {
1288+  return {
1289+    ts: new Date().toISOString(),
1290+    platform,
1291+    tabId,
1292+    url: data.url,
1293+    method: data.method,
1294+    reqHeaders: mergeKnownHeaders(platform, data.reqHeaders || {}),
1295+    reqBody: trimBody(data.reqBody),
1296+    status: data.status,
1297+    resHeaders: data.resHeaders || null,
1298+    resBody: trimBody(data.resBody),
1299+    error: data.error || null,
1300+    duration: data.duration || null,
1301+    source: data.source || "page"
1302+  };
1303+}
1304+
1305+function ensureTrackedTabId(platform, tabId, source) {
1306+  if (!Number.isInteger(tabId) || tabId < 0) return false;
1307+
1308+  const current = state.trackedTabs[platform];
1309+  if (current === tabId) return true;
1310+
1311+  state.trackedTabs[platform] = tabId;
1312+  persistState().catch(() => {});
1313+  render();
1314+
1315+  if (!Number.isInteger(current)) {
1316+    addLog("info", `已接管 ${platformLabel(platform)} 标签页 ${tabId},来源 ${source}`);
1317+  } else {
1318+    addLog("warn", `已切换 ${platformLabel(platform)} 标签页 ${current} -> ${tabId},来源 ${source}`);
1319+  }
1320+  return true;
1321+}
1322+
1323+function getSenderContext(sender, fallbackPlatform = null) {
1324+  const tabId = sender?.tab?.id;
1325+  const senderPlatform = detectPlatformFromUrl(sender?.tab?.url || "");
1326+  const platform = senderPlatform || fallbackPlatform;
1327+  if (!platform) return null;
1328+  if (!ensureTrackedTabId(platform, tabId, "message")) return null;
1329+  return { platform, tabId };
1330+}
1331+
1332+function resolvePlatformFromRequest(details) {
1333+  const platform = findTrackedPlatformByTabId(details.tabId) || detectPlatformFromRequestUrl(details.url);
1334+  if (!platform) return null;
1335+  return ensureTrackedTabId(platform, details.tabId, "request") ? platform : null;
1336+}
1337+
1338+function handlePageNetwork(data, sender) {
1339+  const context = getSenderContext(sender, data?.platform || null);
1340+  if (!context || !data || !data.url || !data.method) return;
1341+
1342+  if (context.platform === "gemini" && typeof data.reqBody === "string" && data.reqBody) {
1343+    const templateHeaders = Object.keys(data.reqHeaders || {}).length > 0
1344+      ? mergeKnownHeaders(context.platform, data.reqHeaders || {})
1345+      : cloneHeaderMap(state.lastHeaders.gemini);
1346+    rememberGeminiSendTemplate(data.url, data.reqBody, templateHeaders);
1347+  }
1348+
1349+  collectEndpoint(context.platform, data.method, data.url);
1350+  const entry = buildNetworkEntry(context.platform, data, context.tabId);
1351+  wsSend({
1352+    type: "network_log",
1353+    clientId: state.clientId,
1354+    platform: context.platform,
1355+    entry
1356+  });
1357+
1358+  if (data.status >= 400 || data.error) {
1359+    addLog("error", `${platformLabel(context.platform)} ${data.method} ${normalizePath(data.url)} -> ${data.status || data.error}`);
1360+  }
1361+}
1362+
1363+function handlePageSse(data, sender) {
1364+  const context = getSenderContext(sender, data?.platform || null);
1365+  if (!context || !data || !data.url) return;
1366+
1367+  wsSend({
1368+    type: "sse_event",
1369+    clientId: state.clientId,
1370+    platform: context.platform,
1371+    url: data.url,
1372+    method: data.method,
1373+    reqBody: trimBody(data.reqBody),
1374+    chunk: data.chunk || null,
1375+    done: !!data.done,
1376+    error: data.error || null,
1377+    ts: data.ts || Date.now()
1378+  });
1379+}
1380+
1381+function handlePageProxyResponse(data, sender) {
1382+  const context = getSenderContext(sender, data?.platform || null);
1383+  if (!context || !data || !data.id) return;
1384+  const pending = pendingProxyRequests.get(data.id);
1385+
1386+  if (
1387+    context.platform === "gemini"
1388+    && pending?.prompt
1389+    && pending.attempts < 1
1390+    && Number(data.status) === 400
1391+  ) {
1392+    const xsrfToken = extractGeminiXsrfToken(data.body);
1393+    if (updateGeminiTemplateXsrf(xsrfToken)) {
1394+      pending.attempts += 1;
1395+      addLog("info", `Gemini xsrf 已刷新,正在重试代理 ${data.id}`);
1396+      try {
1397+        const retry = buildGeminiAutoRequest(pending.prompt);
1398+        postProxyRequestToTab(context.tabId, {
1399+          id: data.id,
1400+          platform: "gemini",
1401+          method: "POST",
1402+          path: retry.path,
1403+          body: retry.body,
1404+          headers: retry.headers
1405+        }).catch((error) => {
1406+          pendingProxyRequests.delete(data.id);
1407+          sendApiResponse(data.id, false, null, null, error.message);
1408+          addLog("error", `Gemini 重试 ${data.id} 失败:${error.message}`);
1409+        });
1410+        return;
1411+      } catch (error) {
1412+        pendingProxyRequests.delete(data.id);
1413+        sendApiResponse(data.id, false, null, null, error.message);
1414+        addLog("error", `Gemini 重试 ${data.id} 失败:${error.message}`);
1415+        return;
1416+      }
1417+    }
1418+  }
1419+
1420+  pendingProxyRequests.delete(data.id);
1421+
1422+  sendApiResponse(
1423+    data.id,
1424+    !data.error,
1425+    Number.isFinite(data.status) ? data.status : null,
1426+    typeof data.body === "string" ? data.body : (data.body == null ? null : JSON.stringify(data.body)),
1427+    data.error || null
1428+  );
1429+
1430+  if (!data.ok || (Number.isFinite(data.status) && data.status >= 400)) {
1431+    addLog(
1432+      "error",
1433+      `${platformLabel(context.platform)} 代理 ${data.method || "GET"} ${normalizePath(data.url || data.path || "-")} -> ${data.status || data.error || "失败"}`
1434+    );
1435+  }
1436+}
1437+
1438+function handlePageBridgeReady(data, sender) {
1439+  const tabId = sender?.tab?.id;
1440+  const senderUrl = sender?.tab?.url || data?.url || "";
1441+  const platform = detectPlatformFromUrl(senderUrl) || data?.platform || null;
1442+  if (!platform || !Number.isInteger(tabId)) return;
1443+
1444+  ensureTrackedTabId(platform, tabId, "bridge");
1445+  addLog("info", `${platformLabel(platform)} 钩子已就绪,标签页 ${tabId},来源 ${data?.source || "未知"}`);
1446+}
1447+
1448+function handleBeforeSendHeaders(details) {
1449+  const platform = resolvePlatformFromRequest(details);
1450+  if (!platform) return;
1451+
1452+  const headers = headerArrayToObject(details.requestHeaders);
1453+  if (Object.keys(headers).length === 0) return;
1454+
1455+  state.lastHeaders[platform] = headers;
1456+  collectEndpoint(platform, details.method || "GET", details.url);
1457+  render();
1458+  sendCredentialSnapshot(platform);
1459+  persistState().catch(() => {});
1460+}
1461+
1462+function handleCompleted(details) {
1463+  const platform = resolvePlatformFromRequest(details);
1464+  if (!platform || !shouldTrackRequest(platform, details.url)) return;
1465+  collectEndpoint(platform, details.method || "GET", details.url);
1466+}
1467+
1468+function handleErrorOccurred(details) {
1469+  const platform = resolvePlatformFromRequest(details);
1470+  if (!platform || !shouldTrackRequest(platform, details.url)) return;
1471+  addLog("error", `${platformLabel(platform)} ${details.method} ${normalizePath(details.url)} 失败:${details.error}`);
1472+}
1473+
1474+async function postProxyRequestToTab(tabId, data) {
1475+  let lastError = null;
1476+  for (let attempt = 0; attempt < PROXY_MESSAGE_RETRY; attempt += 1) {
1477+    try {
1478+      await browser.tabs.sendMessage(tabId, {
1479+        type: "baa_page_proxy_request",
1480+        data
1481+      });
1482+      return;
1483+    } catch (error) {
1484+      lastError = error;
1485+      if (attempt < PROXY_MESSAGE_RETRY - 1) {
1486+        await sleep(PROXY_MESSAGE_RETRY_DELAY);
1487+      }
1488+    }
1489+  }
1490+  throw lastError || new Error("无法连接内容脚本");
1491+}
1492+
1493+async function proxyApiRequest(message) {
1494+  const { id, platform, method = "GET", path: apiPath, body = null } = message || {};
1495+  if (!id) throw new Error("缺少代理请求 ID");
1496+  if (!platform || !PLATFORMS[platform]) throw new Error(`未知平台:${platform || "-"}`);
1497+  if (!apiPath) throw new Error("缺少代理请求路径");
1498+
1499+  const tab = await ensurePlatformTab(platform, { focus: false });
1500+  const prompt = platform === "gemini" ? extractPromptFromProxyBody(body) : null;
1501+  const geminiAutoRequest = platform === "gemini" && prompt && isGeminiStreamGenerateUrl(apiPath)
1502+    ? buildGeminiAutoRequest(prompt)
1503+    : null;
1504+  const payload = {
1505+    id,
1506+    platform,
1507+    method: geminiAutoRequest ? "POST" : String(method || "GET").toUpperCase(),
1508+    path: geminiAutoRequest ? geminiAutoRequest.path : apiPath,
1509+    body: geminiAutoRequest ? geminiAutoRequest.body : body,
1510+    headers: geminiAutoRequest ? geminiAutoRequest.headers : buildProxyHeaders(platform, apiPath)
1511+  };
1512+
1513+  pendingProxyRequests.set(id, {
1514+    platform,
1515+    prompt,
1516+    attempts: 0
1517+  });
1518+  try {
1519+    await postProxyRequestToTab(tab.id, payload);
1520+  } catch (error) {
1521+    pendingProxyRequests.delete(id);
1522+    throw error;
1523+  }
1524+}
1525+
1526+function registerWebRequestListeners() {
1527+  browser.webRequest.onBeforeSendHeaders.addListener(
1528+    handleBeforeSendHeaders,
1529+    { urls: PLATFORM_REQUEST_URL_PATTERNS },
1530+    ["requestHeaders"]
1531+  );
1532+
1533+  browser.webRequest.onCompleted.addListener(
1534+    handleCompleted,
1535+    { urls: PLATFORM_REQUEST_URL_PATTERNS },
1536+    ["responseHeaders"]
1537+  );
1538+
1539+  browser.webRequest.onErrorOccurred.addListener(
1540+    handleErrorOccurred,
1541+    { urls: PLATFORM_REQUEST_URL_PATTERNS }
1542+  );
1543+}
1544+
1545+function registerRuntimeListeners() {
1546+  browser.runtime.onMessage.addListener((message, sender) => {
1547+    if (!message || typeof message !== "object") return undefined;
1548+
1549+    switch (message.type) {
1550+      case "baa_page_bridge_ready":
1551+        handlePageBridgeReady(message.data, sender);
1552+        break;
1553+      case "baa_page_network":
1554+        handlePageNetwork(message.data, sender);
1555+        break;
1556+      case "baa_page_sse":
1557+        handlePageSse(message.data, sender);
1558+        break;
1559+      case "baa_page_proxy_response":
1560+        handlePageProxyResponse(message.data, sender);
1561+        break;
1562+      case "control_plane_command":
1563+        return runControlPlaneAction(message.action, {
1564+          source: message.source || "runtime"
1565+        }).then((result) => ({
1566+          ok: true,
1567+          ...result
1568+        })).catch((error) => ({
1569+          ok: false,
1570+          error: error.message,
1571+          snapshot: cloneControlState(state.controlState)
1572+        }));
1573+      case "get_control_plane_state":
1574+        return Promise.resolve({
1575+          ok: true,
1576+          snapshot: cloneControlState(state.controlState)
1577+        });
1578+      default:
1579+        break;
1580+    }
1581+
1582+    return undefined;
1583+  });
1584+}
1585+
1586+function registerTabListeners() {
1587+  browser.tabs.onActivated.addListener(async ({ tabId }) => {
1588+    try {
1589+      const tab = await browser.tabs.get(tabId);
1590+      const platform = detectPlatformFromUrl(tab?.url || "");
1591+      if (!platform) return;
1592+      ensureTrackedTabId(platform, tabId, "activation");
1593+    } catch (_) {}
1594+  });
1595+
1596+  browser.tabs.onRemoved.addListener((tabId) => {
1597+    const platform = findTrackedPlatformByTabId(tabId);
1598+    if (!platform) return;
1599+
1600+    state.trackedTabs[platform] = null;
1601+    persistState().catch(() => {});
1602+    render();
1603+    addLog("warn", `${platformLabel(platform)} tab closed, reopening`);
1604+    ensurePlatformTab(platform, { focus: false }).catch(() => {});
1605+  });
1606+
1607+  browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
1608+    const platform = findTrackedPlatformByTabId(tabId);
1609+    if (!platform || changeInfo.status !== "complete") return;
1610+
1611+    if (tab?.url && isPlatformUrl(platform, tab.url)) {
1612+      addLog("info", `${platformLabel(platform)} tab ready ${tabId}`);
1613+      return;
1614+    }
1615+
1616+    state.trackedTabs[platform] = null;
1617+    persistState().catch(() => {});
1618+    render();
1619+    addLog("warn", `${platformLabel(platform)} tab moved away, reacquiring`);
1620+    ensurePlatformTab(platform, { focus: false }).catch(() => {});
1621+  });
1622+}
1623+
1624+function bindUi() {
1625+  ui.wsStatus = qs("ws-status");
1626+  ui.tabStatus = qs("tab-status");
1627+  ui.credStatus = qs("cred-status");
1628+  ui.endpointCount = qs("endpoint-count");
1629+  ui.clientId = qs("client-id");
1630+  ui.controlMode = qs("control-mode");
1631+  ui.controlMeta = qs("control-meta");
1632+  ui.leaderValue = qs("leader-value");
1633+  ui.leaderMeta = qs("leader-meta");
1634+  ui.queueValue = qs("queue-value");
1635+  ui.queueMeta = qs("queue-meta");
1636+  ui.runsValue = qs("runs-value");
1637+  ui.runsMeta = qs("runs-meta");
1638+  ui.tabMeta = qs("tab-meta");
1639+  ui.credMeta = qs("cred-meta");
1640+  ui.controlView = qs("control-view");
1641+  ui.platformsView = qs("platforms-view");
1642+  ui.headersView = qs("headers-view");
1643+  ui.endpointsView = qs("endpoints-view");
1644+  ui.logView = qs("log-view");
1645+  ui.wsUrl = qs("ws-url");
1646+  ui.controlBaseUrl = qs("control-base-url");
1647+  ui.controlToken = qs("control-token");
1648+
1649+  for (const platform of PLATFORM_ORDER) {
1650+    qs(`open-${platform}-btn`).addEventListener("click", () => {
1651+      ensurePlatformTab(platform, { focus: true }).catch((error) => {
1652+        addLog("error", `打开 ${platformLabel(platform)} 失败:${error.message}`);
1653+      });
1654+    });
1655+  }
1656+
1657+  qs("reconnect-btn").addEventListener("click", () => {
1658+    state.wsUrl = ui.wsUrl.value.trim() || DEFAULT_WS_URL;
1659+    persistState().catch(() => {});
1660+    connectWs();
1661+  });
1662+
1663+  qs("save-ws-btn").addEventListener("click", () => {
1664+    const previousDerived = deriveControlBaseUrl(state.wsUrl);
1665+    state.wsUrl = ui.wsUrl.value.trim() || DEFAULT_WS_URL;
1666+    if (!ui.controlBaseUrl.value.trim() || trimTrailingSlash(ui.controlBaseUrl.value) === previousDerived) {
1667+      state.controlBaseUrl = deriveControlBaseUrl(state.wsUrl);
1668+    }
1669+    persistState().catch(() => {});
1670+    addLog("info", `已保存 WS 地址:${state.wsUrl}`, false);
1671+    render();
1672+  });
1673+
1674+  qs("save-control-btn").addEventListener("click", () => {
1675+    state.controlBaseUrl = trimTrailingSlash(ui.controlBaseUrl.value) || deriveControlBaseUrl(state.wsUrl);
1676+    state.controlToken = ui.controlToken.value.trim();
1677+    persistState().catch(() => {});
1678+    addLog("info", `已保存控制 API:${state.controlBaseUrl}`, false);
1679+    render();
1680+    refreshControlPlaneState({ source: "manual", silent: false }).catch(() => {});
1681+  });
1682+
1683+  qs("refresh-control-btn").addEventListener("click", () => {
1684+    refreshControlPlaneState({ source: "manual", silent: false }).catch(() => {});
1685+  });
1686+
1687+  for (const action of ["pause", "resume", "drain"]) {
1688+    qs(`${action}-btn`).addEventListener("click", () => {
1689+      runControlPlaneAction(action, { source: "manual" }).catch((error) => {
1690+        addLog("error", `控制动作 ${action} 失败:${error.message}`);
1691+      });
1692+    });
1693+  }
1694+
1695+  qs("focus-controller-btn").addEventListener("click", async () => {
1696+    const tab = await browser.tabs.getCurrent();
1697+    await browser.tabs.update(tab.id, { active: true });
1698+    if (tab.windowId != null) {
1699+      await browser.windows.update(tab.windowId, { focused: true });
1700+    }
1701+  });
1702+}
1703+
1704+async function init() {
1705+  bindUi();
1706+
1707+  const saved = await browser.storage.local.get([
1708+    ...Object.values(CONTROLLER_STORAGE_KEYS),
1709+    ...Object.values(LEGACY_STORAGE_KEYS)
1710+  ]);
1711+
1712+  state.clientId = saved[CONTROLLER_STORAGE_KEYS.clientId] || genClientId();
1713+  state.wsUrl = saved[CONTROLLER_STORAGE_KEYS.wsUrl] || DEFAULT_WS_URL;
1714+  if (state.wsUrl === "ws://localhost:9800") {
1715+    state.wsUrl = DEFAULT_WS_URL;
1716+  }
1717+  state.controlBaseUrl = trimTrailingSlash(saved[CONTROLLER_STORAGE_KEYS.controlBaseUrl]) || deriveControlBaseUrl(state.wsUrl);
1718+  state.controlToken = typeof saved[CONTROLLER_STORAGE_KEYS.controlToken] === "string"
1719+    ? saved[CONTROLLER_STORAGE_KEYS.controlToken]
1720+    : "";
1721+  state.controlState = loadControlState(saved[CONTROLLER_STORAGE_KEYS.controlState]);
1722+
1723+  state.trackedTabs = loadTrackedTabs(
1724+    saved[CONTROLLER_STORAGE_KEYS.trackedTabs],
1725+    saved[LEGACY_STORAGE_KEYS.claudeTabId]
1726+  );
1727+  state.endpoints = loadObjectMap(
1728+    saved[CONTROLLER_STORAGE_KEYS.endpointsByPlatform],
1729+    saved[LEGACY_STORAGE_KEYS.endpoints]
1730+  );
1731+  state.lastHeaders = loadObjectMap(
1732+    saved[CONTROLLER_STORAGE_KEYS.lastHeadersByPlatform],
1733+    saved[LEGACY_STORAGE_KEYS.lastHeaders]
1734+  );
1735+  state.lastCredentialAt = loadNumberMap(
1736+    saved[CONTROLLER_STORAGE_KEYS.lastCredentialAtByPlatform],
1737+    saved[LEGACY_STORAGE_KEYS.lastCredentialAt]
1738+  );
1739+  state.geminiSendTemplate = saved[CONTROLLER_STORAGE_KEYS.geminiSendTemplate] || null;
1740+  state.lastCredentialHash = createPlatformMap((platform) => JSON.stringify(state.lastHeaders[platform]));
1741+
1742+  ui.wsUrl.value = state.wsUrl;
1743+  ui.controlBaseUrl.value = state.controlBaseUrl;
1744+  ui.controlToken.value = state.controlToken;
1745+
1746+  registerRuntimeListeners();
1747+  registerTabListeners();
1748+  registerWebRequestListeners();
1749+
1750+  const current = await browser.tabs.getCurrent();
1751+  await browser.runtime.sendMessage({ type: "controller_ready", tabId: current.id });
1752+  await persistState();
1753+  render();
1754+  addLog("info", `controller ready ${state.clientId}`, false);
1755+
1756+  await ensureAllPlatformTabs({ reloadIfExisting: true });
1757+  connectWs();
1758+  restartControlPlaneRefreshTimer();
1759+  refreshControlPlaneState({ source: "startup", silent: true }).catch(() => {});
1760+}
1761+
1762+window.addEventListener("beforeunload", () => {
1763+  clearTimeout(state.reconnectTimer);
1764+  clearInterval(state.controlRefreshTimer);
1765+  if (state.ws) {
1766+    try {
1767+      state.ws.close();
1768+    } catch (_) {}
1769+  }
1770+});
1771+
1772+init().catch((error) => {
1773+  console.error(error);
1774+  addLog("error", error.message, false);
1775+});
A plugins/baa-firefox/docs/conductor-control.md
+58, -0
 1@@ -0,0 +1,58 @@
 2+# Firefox Conductor Control
 3+
 4+`baa-firefox` 现在包含一条最小可用的 conductor control-plane 接线。
 5+
 6+## 范围
 7+
 8+- `controller.html` / `controller.js`
 9+  - 配置 Control API base URL 和 bearer token
10+  - 读取 `GET /v1/system/state`
11+  - 调用 `POST /v1/system/pause`
12+  - 调用 `POST /v1/system/resume`
13+  - 调用 `POST /v1/system/drain`
14+- `background.js`
15+  - 根据最新 control snapshot 更新扩展 badge
16+- `content-script.js`
17+  - 在 Claude 页面右下角提供最小浮层入口
18+
19+## 配置
20+
21+控制页新增了两项:
22+
23+- `Control API`
24+  - 默认从当前 `wsUrl` 推导,例如 `ws://127.0.0.1:9800` -> `http://127.0.0.1:9800`
25+- `Bearer`
26+  - 直接写入 `Authorization: Bearer <token>`
27+
28+状态快照会持久化到 `browser.storage.local` 的 `baaFirefox.controlState`,供:
29+
30+- controller 面板渲染
31+- toolbar badge 渲染
32+- Claude 页面浮层渲染
33+
34+## 状态字段
35+
36+插件会尽量从 `/v1/system/state` 响应中归一化这些字段:
37+
38+- `mode`
39+- `leader`
40+- `lease_holder`
41+- `queue_depth` / `queued_tasks`
42+- `active_runs`
43+
44+如果服务端实际字段名略有不同,`controller.js` 已做多路径兼容解析。
45+
46+## 可见入口
47+
48+- 扩展工具栏 badge
49+  - `RUN` / `PAU` / `DRN` / `ERR`
50+- `controller.html`
51+  - 可见 control-plane 卡片、原始快照和动作按钮
52+- Claude 页面
53+  - 右下角 `BAA` 浮层,可直接 `Pause` / `Resume` / `Drain` 或打开 controller
54+
55+## 当前限制
56+
57+- 本次没有改 `manifest.json`
58+- 因此 Control API 需要落在当前扩展 CSP 允许的地址范围内
59+- 最稳妥的做法是把 conductor control API 暴露在与当前 `wsUrl` 同源的本地桥接地址上
A plugins/baa-firefox/manifest.json
+76, -0
 1@@ -0,0 +1,76 @@
 2+{
 3+  "manifest_version": 3,
 4+  "name": "BAA Firefox Controller",
 5+  "version": "0.1.0",
 6+  "description": "Firefox MVP for BAA: persistent controller page plus real AI tabs.",
 7+  "permissions": [
 8+    "tabs",
 9+    "storage",
10+    "webRequest",
11+    "cookies",
12+    "scripting"
13+  ],
14+  "host_permissions": [
15+    "https://claude.ai/*",
16+    "https://chatgpt.com/*",
17+    "https://*.chatgpt.com/*",
18+    "https://openai.com/*",
19+    "https://*.openai.com/*",
20+    "https://chat.openai.com/*",
21+    "https://*.chat.openai.com/*",
22+    "https://oaiusercontent.com/*",
23+    "https://*.oaiusercontent.com/*",
24+    "https://gemini.google.com/*",
25+    "http://localhost/*",
26+    "http://127.0.0.1/*",
27+    "ws://localhost/*",
28+    "ws://127.0.0.1/*"
29+  ],
30+  "content_security_policy": {
31+    "extension_pages": "default-src 'self'; connect-src ws://localhost:9800 ws://127.0.0.1:9800 http://localhost:9800 http://127.0.0.1:9800"
32+  },
33+  "background": {
34+    "scripts": [
35+      "background.js"
36+    ]
37+  },
38+  "content_scripts": [
39+    {
40+      "matches": [
41+        "https://claude.ai/*",
42+        "https://chatgpt.com/*",
43+        "https://*.chatgpt.com/*",
44+        "https://chat.openai.com/*",
45+        "https://*.chat.openai.com/*",
46+        "https://gemini.google.com/*"
47+      ],
48+      "js": [
49+        "content-script.js"
50+      ],
51+      "run_at": "document_start"
52+    },
53+    {
54+      "matches": [
55+        "https://claude.ai/*",
56+        "https://chatgpt.com/*",
57+        "https://*.chatgpt.com/*",
58+        "https://chat.openai.com/*",
59+        "https://*.chat.openai.com/*",
60+        "https://gemini.google.com/*"
61+      ],
62+      "js": [
63+        "page-interceptor.js"
64+      ],
65+      "run_at": "document_start",
66+      "world": "MAIN"
67+    }
68+  ],
69+  "action": {
70+    "default_title": "BAA Firefox Controller"
71+  },
72+  "browser_specific_settings": {
73+    "gecko": {
74+      "id": "baa-firefox@makefile.so"
75+    }
76+  }
77+}
A plugins/baa-firefox/package-lock.json
+3818, -0
   1@@ -0,0 +1,3818 @@
   2+{
   3+  "name": "baa-firefox",
   4+  "lockfileVersion": 3,
   5+  "requires": true,
   6+  "packages": {
   7+    "": {
   8+      "dependencies": {
   9+        "web-ext": "^10.0.0"
  10+      }
  11+    },
  12+    "node_modules/@babel/code-frame": {
  13+      "version": "7.29.0",
  14+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
  15+      "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
  16+      "license": "MIT",
  17+      "dependencies": {
  18+        "@babel/helper-validator-identifier": "^7.28.5",
  19+        "js-tokens": "^4.0.0",
  20+        "picocolors": "^1.1.1"
  21+      },
  22+      "engines": {
  23+        "node": ">=6.9.0"
  24+      }
  25+    },
  26+    "node_modules/@babel/helper-validator-identifier": {
  27+      "version": "7.28.5",
  28+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
  29+      "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
  30+      "license": "MIT",
  31+      "engines": {
  32+        "node": ">=6.9.0"
  33+      }
  34+    },
  35+    "node_modules/@babel/runtime": {
  36+      "version": "7.28.6",
  37+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz",
  38+      "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==",
  39+      "license": "MIT",
  40+      "engines": {
  41+        "node": ">=6.9.0"
  42+      }
  43+    },
  44+    "node_modules/@devicefarmer/adbkit": {
  45+      "version": "3.3.8",
  46+      "resolved": "https://registry.npmjs.org/@devicefarmer/adbkit/-/adbkit-3.3.8.tgz",
  47+      "integrity": "sha512-7rBLLzWQnBwutH2WZ0EWUkQdihqrnLYCUMaB44hSol9e0/cdIhuNFcqZO0xNheAU6qqHVA8sMiLofkYTgb+lmw==",
  48+      "license": "Apache-2.0",
  49+      "dependencies": {
  50+        "@devicefarmer/adbkit-logcat": "^2.1.2",
  51+        "@devicefarmer/adbkit-monkey": "~1.2.1",
  52+        "bluebird": "~3.7",
  53+        "commander": "^9.1.0",
  54+        "debug": "~4.3.1",
  55+        "node-forge": "^1.3.1",
  56+        "split": "~1.0.1"
  57+      },
  58+      "bin": {
  59+        "adbkit": "bin/adbkit"
  60+      },
  61+      "engines": {
  62+        "node": ">= 0.10.4"
  63+      }
  64+    },
  65+    "node_modules/@devicefarmer/adbkit-logcat": {
  66+      "version": "2.1.3",
  67+      "resolved": "https://registry.npmjs.org/@devicefarmer/adbkit-logcat/-/adbkit-logcat-2.1.3.tgz",
  68+      "integrity": "sha512-yeaGFjNBc/6+svbDeul1tNHtNChw6h8pSHAt5D+JsedUrMTN7tla7B15WLDyekxsuS2XlZHRxpuC6m92wiwCNw==",
  69+      "license": "Apache-2.0",
  70+      "engines": {
  71+        "node": ">= 4"
  72+      }
  73+    },
  74+    "node_modules/@devicefarmer/adbkit-monkey": {
  75+      "version": "1.2.1",
  76+      "resolved": "https://registry.npmjs.org/@devicefarmer/adbkit-monkey/-/adbkit-monkey-1.2.1.tgz",
  77+      "integrity": "sha512-ZzZY/b66W2Jd6NHbAhLyDWOEIBWC11VizGFk7Wx7M61JZRz7HR9Cq5P+65RKWUU7u6wgsE8Lmh9nE4Mz+U2eTg==",
  78+      "license": "Apache-2.0",
  79+      "engines": {
  80+        "node": ">= 0.10.4"
  81+      }
  82+    },
  83+    "node_modules/@eslint-community/eslint-utils": {
  84+      "version": "4.9.1",
  85+      "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
  86+      "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
  87+      "license": "MIT",
  88+      "dependencies": {
  89+        "eslint-visitor-keys": "^3.4.3"
  90+      },
  91+      "engines": {
  92+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
  93+      },
  94+      "funding": {
  95+        "url": "https://opencollective.com/eslint"
  96+      },
  97+      "peerDependencies": {
  98+        "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
  99+      }
 100+    },
 101+    "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
 102+      "version": "3.4.3",
 103+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
 104+      "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
 105+      "license": "Apache-2.0",
 106+      "engines": {
 107+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
 108+      },
 109+      "funding": {
 110+        "url": "https://opencollective.com/eslint"
 111+      }
 112+    },
 113+    "node_modules/@eslint-community/regexpp": {
 114+      "version": "4.12.2",
 115+      "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
 116+      "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
 117+      "license": "MIT",
 118+      "engines": {
 119+        "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
 120+      }
 121+    },
 122+    "node_modules/@eslint/config-array": {
 123+      "version": "0.21.2",
 124+      "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz",
 125+      "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==",
 126+      "license": "Apache-2.0",
 127+      "dependencies": {
 128+        "@eslint/object-schema": "^2.1.7",
 129+        "debug": "^4.3.1",
 130+        "minimatch": "^3.1.5"
 131+      },
 132+      "engines": {
 133+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 134+      }
 135+    },
 136+    "node_modules/@eslint/config-helpers": {
 137+      "version": "0.4.2",
 138+      "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
 139+      "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
 140+      "license": "Apache-2.0",
 141+      "dependencies": {
 142+        "@eslint/core": "^0.17.0"
 143+      },
 144+      "engines": {
 145+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 146+      }
 147+    },
 148+    "node_modules/@eslint/core": {
 149+      "version": "0.17.0",
 150+      "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
 151+      "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
 152+      "license": "Apache-2.0",
 153+      "dependencies": {
 154+        "@types/json-schema": "^7.0.15"
 155+      },
 156+      "engines": {
 157+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 158+      }
 159+    },
 160+    "node_modules/@eslint/eslintrc": {
 161+      "version": "3.3.5",
 162+      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz",
 163+      "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==",
 164+      "license": "MIT",
 165+      "dependencies": {
 166+        "ajv": "^6.14.0",
 167+        "debug": "^4.3.2",
 168+        "espree": "^10.0.1",
 169+        "globals": "^14.0.0",
 170+        "ignore": "^5.2.0",
 171+        "import-fresh": "^3.2.1",
 172+        "js-yaml": "^4.1.1",
 173+        "minimatch": "^3.1.5",
 174+        "strip-json-comments": "^3.1.1"
 175+      },
 176+      "engines": {
 177+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 178+      },
 179+      "funding": {
 180+        "url": "https://opencollective.com/eslint"
 181+      }
 182+    },
 183+    "node_modules/@eslint/eslintrc/node_modules/ajv": {
 184+      "version": "6.14.0",
 185+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
 186+      "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
 187+      "license": "MIT",
 188+      "dependencies": {
 189+        "fast-deep-equal": "^3.1.1",
 190+        "fast-json-stable-stringify": "^2.0.0",
 191+        "json-schema-traverse": "^0.4.1",
 192+        "uri-js": "^4.2.2"
 193+      },
 194+      "funding": {
 195+        "type": "github",
 196+        "url": "https://github.com/sponsors/epoberezkin"
 197+      }
 198+    },
 199+    "node_modules/@eslint/eslintrc/node_modules/eslint-visitor-keys": {
 200+      "version": "4.2.1",
 201+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
 202+      "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
 203+      "license": "Apache-2.0",
 204+      "engines": {
 205+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 206+      },
 207+      "funding": {
 208+        "url": "https://opencollective.com/eslint"
 209+      }
 210+    },
 211+    "node_modules/@eslint/eslintrc/node_modules/espree": {
 212+      "version": "10.4.0",
 213+      "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
 214+      "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
 215+      "license": "BSD-2-Clause",
 216+      "dependencies": {
 217+        "acorn": "^8.15.0",
 218+        "acorn-jsx": "^5.3.2",
 219+        "eslint-visitor-keys": "^4.2.1"
 220+      },
 221+      "engines": {
 222+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 223+      },
 224+      "funding": {
 225+        "url": "https://opencollective.com/eslint"
 226+      }
 227+    },
 228+    "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": {
 229+      "version": "0.4.1",
 230+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
 231+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
 232+      "license": "MIT"
 233+    },
 234+    "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": {
 235+      "version": "3.1.1",
 236+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
 237+      "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
 238+      "license": "MIT",
 239+      "engines": {
 240+        "node": ">=8"
 241+      },
 242+      "funding": {
 243+        "url": "https://github.com/sponsors/sindresorhus"
 244+      }
 245+    },
 246+    "node_modules/@eslint/js": {
 247+      "version": "9.39.2",
 248+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
 249+      "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
 250+      "license": "MIT",
 251+      "engines": {
 252+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 253+      },
 254+      "funding": {
 255+        "url": "https://eslint.org/donate"
 256+      }
 257+    },
 258+    "node_modules/@eslint/object-schema": {
 259+      "version": "2.1.7",
 260+      "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
 261+      "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
 262+      "license": "Apache-2.0",
 263+      "engines": {
 264+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 265+      }
 266+    },
 267+    "node_modules/@eslint/plugin-kit": {
 268+      "version": "0.4.1",
 269+      "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
 270+      "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
 271+      "license": "Apache-2.0",
 272+      "dependencies": {
 273+        "@eslint/core": "^0.17.0",
 274+        "levn": "^0.4.1"
 275+      },
 276+      "engines": {
 277+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
 278+      }
 279+    },
 280+    "node_modules/@fluent/syntax": {
 281+      "version": "0.19.0",
 282+      "resolved": "https://registry.npmjs.org/@fluent/syntax/-/syntax-0.19.0.tgz",
 283+      "integrity": "sha512-5D2qVpZrgpjtqU4eNOcWGp1gnUCgjfM+vKGE2y03kKN6z5EBhtx0qdRFbg8QuNNj8wXNoX93KJoYb+NqoxswmQ==",
 284+      "license": "Apache-2.0",
 285+      "engines": {
 286+        "node": ">=14.0.0",
 287+        "npm": ">=7.0.0"
 288+      }
 289+    },
 290+    "node_modules/@fregante/relaxed-json": {
 291+      "version": "2.0.0",
 292+      "resolved": "https://registry.npmjs.org/@fregante/relaxed-json/-/relaxed-json-2.0.0.tgz",
 293+      "integrity": "sha512-PyUXQWB42s4jBli435TDiYuVsadwRHnMc27YaLouINktvTWsL3FcKrRMGawTayFk46X+n5bE23RjUTWQwrukWw==",
 294+      "license": "BSD-3-Clause",
 295+      "engines": {
 296+        "node": ">= 0.10.0"
 297+      }
 298+    },
 299+    "node_modules/@humanfs/core": {
 300+      "version": "0.19.1",
 301+      "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
 302+      "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
 303+      "license": "Apache-2.0",
 304+      "engines": {
 305+        "node": ">=18.18.0"
 306+      }
 307+    },
 308+    "node_modules/@humanfs/node": {
 309+      "version": "0.16.7",
 310+      "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
 311+      "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
 312+      "license": "Apache-2.0",
 313+      "dependencies": {
 314+        "@humanfs/core": "^0.19.1",
 315+        "@humanwhocodes/retry": "^0.4.0"
 316+      },
 317+      "engines": {
 318+        "node": ">=18.18.0"
 319+      }
 320+    },
 321+    "node_modules/@humanwhocodes/module-importer": {
 322+      "version": "1.0.1",
 323+      "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
 324+      "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
 325+      "license": "Apache-2.0",
 326+      "engines": {
 327+        "node": ">=12.22"
 328+      },
 329+      "funding": {
 330+        "type": "github",
 331+        "url": "https://github.com/sponsors/nzakas"
 332+      }
 333+    },
 334+    "node_modules/@humanwhocodes/retry": {
 335+      "version": "0.4.3",
 336+      "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
 337+      "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
 338+      "license": "Apache-2.0",
 339+      "engines": {
 340+        "node": ">=18.18"
 341+      },
 342+      "funding": {
 343+        "type": "github",
 344+        "url": "https://github.com/sponsors/nzakas"
 345+      }
 346+    },
 347+    "node_modules/@mdn/browser-compat-data": {
 348+      "version": "7.3.6",
 349+      "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-7.3.6.tgz",
 350+      "integrity": "sha512-eLTmNSxv2DaDO1Hq7C9lwQbThlF5+vMMUQfIl6xRPSC2q6EcFXhim4Mc9uxHNxcPvDFCdB3qviMFDdAzQVhYcw==",
 351+      "license": "CC0-1.0"
 352+    },
 353+    "node_modules/@pinojs/redact": {
 354+      "version": "0.4.0",
 355+      "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz",
 356+      "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==",
 357+      "license": "MIT"
 358+    },
 359+    "node_modules/@pnpm/config.env-replace": {
 360+      "version": "1.1.0",
 361+      "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz",
 362+      "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==",
 363+      "license": "MIT",
 364+      "engines": {
 365+        "node": ">=12.22.0"
 366+      }
 367+    },
 368+    "node_modules/@pnpm/network.ca-file": {
 369+      "version": "1.0.2",
 370+      "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz",
 371+      "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==",
 372+      "license": "MIT",
 373+      "dependencies": {
 374+        "graceful-fs": "4.2.10"
 375+      },
 376+      "engines": {
 377+        "node": ">=12.22.0"
 378+      }
 379+    },
 380+    "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": {
 381+      "version": "4.2.10",
 382+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
 383+      "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
 384+      "license": "ISC"
 385+    },
 386+    "node_modules/@pnpm/npm-conf": {
 387+      "version": "3.0.2",
 388+      "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-3.0.2.tgz",
 389+      "integrity": "sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==",
 390+      "license": "MIT",
 391+      "dependencies": {
 392+        "@pnpm/config.env-replace": "^1.1.0",
 393+        "@pnpm/network.ca-file": "^1.0.1",
 394+        "config-chain": "^1.1.11"
 395+      },
 396+      "engines": {
 397+        "node": ">=12"
 398+      }
 399+    },
 400+    "node_modules/@types/estree": {
 401+      "version": "1.0.8",
 402+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
 403+      "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
 404+      "license": "MIT"
 405+    },
 406+    "node_modules/@types/json-schema": {
 407+      "version": "7.0.15",
 408+      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
 409+      "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
 410+      "license": "MIT"
 411+    },
 412+    "node_modules/@types/minimatch": {
 413+      "version": "3.0.5",
 414+      "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz",
 415+      "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==",
 416+      "license": "MIT"
 417+    },
 418+    "node_modules/@types/node": {
 419+      "version": "25.5.0",
 420+      "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz",
 421+      "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
 422+      "license": "MIT",
 423+      "dependencies": {
 424+        "undici-types": "~7.18.0"
 425+      }
 426+    },
 427+    "node_modules/acorn": {
 428+      "version": "8.16.0",
 429+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
 430+      "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
 431+      "license": "MIT",
 432+      "peer": true,
 433+      "bin": {
 434+        "acorn": "bin/acorn"
 435+      },
 436+      "engines": {
 437+        "node": ">=0.4.0"
 438+      }
 439+    },
 440+    "node_modules/acorn-jsx": {
 441+      "version": "5.3.2",
 442+      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
 443+      "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
 444+      "license": "MIT",
 445+      "peerDependencies": {
 446+        "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
 447+      }
 448+    },
 449+    "node_modules/addons-linter": {
 450+      "version": "10.1.0",
 451+      "resolved": "https://registry.npmjs.org/addons-linter/-/addons-linter-10.1.0.tgz",
 452+      "integrity": "sha512-Qo8QE/tGxaGMTQGiLPGfxDyrYJCKtsXFkyto3UGuVPb2V+Jc725U3Jjpwpo7cXoImCebueUVXC8KC8D7dpacTQ==",
 453+      "license": "MPL-2.0",
 454+      "dependencies": {
 455+        "@fluent/syntax": "0.19.0",
 456+        "@fregante/relaxed-json": "2.0.0",
 457+        "@mdn/browser-compat-data": "7.3.6",
 458+        "addons-moz-compare": "1.3.0",
 459+        "addons-scanner-utils": "13.1.0",
 460+        "ajv": "8.18.0",
 461+        "chalk": "4.1.2",
 462+        "cheerio": "1.2.0",
 463+        "columnify": "1.6.0",
 464+        "common-tags": "1.8.2",
 465+        "deepmerge": "4.3.1",
 466+        "eslint": "9.39.2",
 467+        "eslint-plugin-no-unsanitized": "4.1.5",
 468+        "eslint-visitor-keys": "5.0.1",
 469+        "espree": "11.2.0",
 470+        "esprima": "4.0.1",
 471+        "fast-json-patch": "3.1.1",
 472+        "image-size": "2.0.2",
 473+        "json-merge-patch": "1.0.2",
 474+        "pino": "10.3.1",
 475+        "semver": "7.7.4",
 476+        "source-map-support": "0.5.21",
 477+        "upath": "2.0.1",
 478+        "yargs": "17.7.2",
 479+        "yauzl": "3.2.1"
 480+      },
 481+      "bin": {
 482+        "addons-linter": "bin/addons-linter"
 483+      },
 484+      "engines": {
 485+        "node": ">=20.0.0"
 486+      }
 487+    },
 488+    "node_modules/addons-moz-compare": {
 489+      "version": "1.3.0",
 490+      "resolved": "https://registry.npmjs.org/addons-moz-compare/-/addons-moz-compare-1.3.0.tgz",
 491+      "integrity": "sha512-/rXpQeaY0nOKhNx00pmZXdk5Mu+KhVlL3/pSBuAYwrxRrNiTvI/9xfQI8Lmm7DMMl+PDhtfAHY/0ibTpdeoQQQ==",
 492+      "license": "MPL-2.0"
 493+    },
 494+    "node_modules/addons-scanner-utils": {
 495+      "version": "13.1.0",
 496+      "resolved": "https://registry.npmjs.org/addons-scanner-utils/-/addons-scanner-utils-13.1.0.tgz",
 497+      "integrity": "sha512-4apAnr0xrEVbIJhNPQA6XUnVAiUR0/lt8EvflrPgrmBQYwTwo7z+yv92t7F1iO2Y07lNhDCZEoL8q1H4sLJyGw==",
 498+      "license": "MPL-2.0",
 499+      "dependencies": {
 500+        "common-tags": "1.8.2",
 501+        "first-chunk-stream": "3.0.0",
 502+        "jsonwebtoken": "^9.0.3",
 503+        "strip-bom-stream": "4.0.0",
 504+        "upath": "2.0.1",
 505+        "yauzl": "3.2.1"
 506+      },
 507+      "peerDependencies": {
 508+        "body-parser": "2.2.2",
 509+        "express": "5.2.1",
 510+        "safe-compare": "1.1.4"
 511+      },
 512+      "peerDependenciesMeta": {
 513+        "body-parser": {
 514+          "optional": true
 515+        },
 516+        "express": {
 517+          "optional": true
 518+        },
 519+        "safe-compare": {
 520+          "optional": true
 521+        }
 522+      }
 523+    },
 524+    "node_modules/adm-zip": {
 525+      "version": "0.5.16",
 526+      "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz",
 527+      "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==",
 528+      "license": "MIT",
 529+      "engines": {
 530+        "node": ">=12.0"
 531+      }
 532+    },
 533+    "node_modules/agent-base": {
 534+      "version": "7.1.4",
 535+      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
 536+      "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
 537+      "license": "MIT",
 538+      "engines": {
 539+        "node": ">= 14"
 540+      }
 541+    },
 542+    "node_modules/ajv": {
 543+      "version": "8.18.0",
 544+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
 545+      "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
 546+      "license": "MIT",
 547+      "dependencies": {
 548+        "fast-deep-equal": "^3.1.3",
 549+        "fast-uri": "^3.0.1",
 550+        "json-schema-traverse": "^1.0.0",
 551+        "require-from-string": "^2.0.2"
 552+      },
 553+      "funding": {
 554+        "type": "github",
 555+        "url": "https://github.com/sponsors/epoberezkin"
 556+      }
 557+    },
 558+    "node_modules/ansi-align": {
 559+      "version": "3.0.1",
 560+      "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
 561+      "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==",
 562+      "license": "ISC",
 563+      "dependencies": {
 564+        "string-width": "^4.1.0"
 565+      }
 566+    },
 567+    "node_modules/ansi-align/node_modules/emoji-regex": {
 568+      "version": "8.0.0",
 569+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
 570+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
 571+      "license": "MIT"
 572+    },
 573+    "node_modules/ansi-align/node_modules/string-width": {
 574+      "version": "4.2.3",
 575+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
 576+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
 577+      "license": "MIT",
 578+      "dependencies": {
 579+        "emoji-regex": "^8.0.0",
 580+        "is-fullwidth-code-point": "^3.0.0",
 581+        "strip-ansi": "^6.0.1"
 582+      },
 583+      "engines": {
 584+        "node": ">=8"
 585+      }
 586+    },
 587+    "node_modules/ansi-regex": {
 588+      "version": "5.0.1",
 589+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
 590+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
 591+      "license": "MIT",
 592+      "engines": {
 593+        "node": ">=8"
 594+      }
 595+    },
 596+    "node_modules/ansi-styles": {
 597+      "version": "4.3.0",
 598+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
 599+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
 600+      "license": "MIT",
 601+      "dependencies": {
 602+        "color-convert": "^2.0.1"
 603+      },
 604+      "engines": {
 605+        "node": ">=8"
 606+      },
 607+      "funding": {
 608+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
 609+      }
 610+    },
 611+    "node_modules/argparse": {
 612+      "version": "2.0.1",
 613+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
 614+      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
 615+      "license": "Python-2.0"
 616+    },
 617+    "node_modules/array-differ": {
 618+      "version": "4.0.0",
 619+      "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-4.0.0.tgz",
 620+      "integrity": "sha512-Q6VPTLMsmXZ47ENG3V+wQyZS1ZxXMxFyYzA+Z/GMrJ6yIutAIEf9wTyroTzmGjNfox9/h3GdGBCVh43GVFx4Uw==",
 621+      "license": "MIT",
 622+      "engines": {
 623+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
 624+      },
 625+      "funding": {
 626+        "url": "https://github.com/sponsors/sindresorhus"
 627+      }
 628+    },
 629+    "node_modules/array-union": {
 630+      "version": "3.0.1",
 631+      "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz",
 632+      "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==",
 633+      "license": "MIT",
 634+      "engines": {
 635+        "node": ">=12"
 636+      },
 637+      "funding": {
 638+        "url": "https://github.com/sponsors/sindresorhus"
 639+      }
 640+    },
 641+    "node_modules/async": {
 642+      "version": "3.2.6",
 643+      "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
 644+      "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
 645+      "license": "MIT"
 646+    },
 647+    "node_modules/atomic-sleep": {
 648+      "version": "1.0.0",
 649+      "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
 650+      "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
 651+      "license": "MIT",
 652+      "engines": {
 653+        "node": ">=8.0.0"
 654+      }
 655+    },
 656+    "node_modules/atomically": {
 657+      "version": "2.1.1",
 658+      "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.1.tgz",
 659+      "integrity": "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==",
 660+      "license": "MIT",
 661+      "dependencies": {
 662+        "stubborn-fs": "^2.0.0",
 663+        "when-exit": "^2.1.4"
 664+      }
 665+    },
 666+    "node_modules/balanced-match": {
 667+      "version": "1.0.2",
 668+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
 669+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
 670+      "license": "MIT"
 671+    },
 672+    "node_modules/bluebird": {
 673+      "version": "3.7.2",
 674+      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
 675+      "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
 676+      "license": "MIT"
 677+    },
 678+    "node_modules/boolbase": {
 679+      "version": "1.0.0",
 680+      "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
 681+      "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
 682+      "license": "ISC"
 683+    },
 684+    "node_modules/boxen": {
 685+      "version": "8.0.1",
 686+      "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz",
 687+      "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==",
 688+      "license": "MIT",
 689+      "dependencies": {
 690+        "ansi-align": "^3.0.1",
 691+        "camelcase": "^8.0.0",
 692+        "chalk": "^5.3.0",
 693+        "cli-boxes": "^3.0.0",
 694+        "string-width": "^7.2.0",
 695+        "type-fest": "^4.21.0",
 696+        "widest-line": "^5.0.0",
 697+        "wrap-ansi": "^9.0.0"
 698+      },
 699+      "engines": {
 700+        "node": ">=18"
 701+      },
 702+      "funding": {
 703+        "url": "https://github.com/sponsors/sindresorhus"
 704+      }
 705+    },
 706+    "node_modules/boxen/node_modules/chalk": {
 707+      "version": "5.6.2",
 708+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
 709+      "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
 710+      "license": "MIT",
 711+      "engines": {
 712+        "node": "^12.17.0 || ^14.13 || >=16.0.0"
 713+      },
 714+      "funding": {
 715+        "url": "https://github.com/chalk/chalk?sponsor=1"
 716+      }
 717+    },
 718+    "node_modules/brace-expansion": {
 719+      "version": "1.1.12",
 720+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
 721+      "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
 722+      "license": "MIT",
 723+      "dependencies": {
 724+        "balanced-match": "^1.0.0",
 725+        "concat-map": "0.0.1"
 726+      }
 727+    },
 728+    "node_modules/buffer-crc32": {
 729+      "version": "0.2.13",
 730+      "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
 731+      "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
 732+      "license": "MIT",
 733+      "engines": {
 734+        "node": "*"
 735+      }
 736+    },
 737+    "node_modules/buffer-equal-constant-time": {
 738+      "version": "1.0.1",
 739+      "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
 740+      "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
 741+      "license": "BSD-3-Clause"
 742+    },
 743+    "node_modules/buffer-from": {
 744+      "version": "1.1.2",
 745+      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
 746+      "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
 747+      "license": "MIT"
 748+    },
 749+    "node_modules/bundle-name": {
 750+      "version": "4.1.0",
 751+      "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz",
 752+      "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==",
 753+      "license": "MIT",
 754+      "dependencies": {
 755+        "run-applescript": "^7.0.0"
 756+      },
 757+      "engines": {
 758+        "node": ">=18"
 759+      },
 760+      "funding": {
 761+        "url": "https://github.com/sponsors/sindresorhus"
 762+      }
 763+    },
 764+    "node_modules/callsites": {
 765+      "version": "3.1.0",
 766+      "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
 767+      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
 768+      "license": "MIT",
 769+      "engines": {
 770+        "node": ">=6"
 771+      }
 772+    },
 773+    "node_modules/camelcase": {
 774+      "version": "8.0.0",
 775+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz",
 776+      "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==",
 777+      "license": "MIT",
 778+      "engines": {
 779+        "node": ">=16"
 780+      },
 781+      "funding": {
 782+        "url": "https://github.com/sponsors/sindresorhus"
 783+      }
 784+    },
 785+    "node_modules/chalk": {
 786+      "version": "4.1.2",
 787+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
 788+      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
 789+      "license": "MIT",
 790+      "dependencies": {
 791+        "ansi-styles": "^4.1.0",
 792+        "supports-color": "^7.1.0"
 793+      },
 794+      "engines": {
 795+        "node": ">=10"
 796+      },
 797+      "funding": {
 798+        "url": "https://github.com/chalk/chalk?sponsor=1"
 799+      }
 800+    },
 801+    "node_modules/cheerio": {
 802+      "version": "1.2.0",
 803+      "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz",
 804+      "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==",
 805+      "license": "MIT",
 806+      "dependencies": {
 807+        "cheerio-select": "^2.1.0",
 808+        "dom-serializer": "^2.0.0",
 809+        "domhandler": "^5.0.3",
 810+        "domutils": "^3.2.2",
 811+        "encoding-sniffer": "^0.2.1",
 812+        "htmlparser2": "^10.1.0",
 813+        "parse5": "^7.3.0",
 814+        "parse5-htmlparser2-tree-adapter": "^7.1.0",
 815+        "parse5-parser-stream": "^7.1.2",
 816+        "undici": "^7.19.0",
 817+        "whatwg-mimetype": "^4.0.0"
 818+      },
 819+      "engines": {
 820+        "node": ">=20.18.1"
 821+      },
 822+      "funding": {
 823+        "url": "https://github.com/cheeriojs/cheerio?sponsor=1"
 824+      }
 825+    },
 826+    "node_modules/cheerio-select": {
 827+      "version": "2.1.0",
 828+      "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
 829+      "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
 830+      "license": "BSD-2-Clause",
 831+      "dependencies": {
 832+        "boolbase": "^1.0.0",
 833+        "css-select": "^5.1.0",
 834+        "css-what": "^6.1.0",
 835+        "domelementtype": "^2.3.0",
 836+        "domhandler": "^5.0.3",
 837+        "domutils": "^3.0.1"
 838+      },
 839+      "funding": {
 840+        "url": "https://github.com/sponsors/fb55"
 841+      }
 842+    },
 843+    "node_modules/chrome-launcher": {
 844+      "version": "1.2.0",
 845+      "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.2.0.tgz",
 846+      "integrity": "sha512-JbuGuBNss258bvGil7FT4HKdC3SC2K7UAEUqiPy3ACS3Yxo3hAW6bvFpCu2HsIJLgTqxgEX6BkujvzZfLpUD0Q==",
 847+      "license": "Apache-2.0",
 848+      "dependencies": {
 849+        "@types/node": "*",
 850+        "escape-string-regexp": "^4.0.0",
 851+        "is-wsl": "^2.2.0",
 852+        "lighthouse-logger": "^2.0.1"
 853+      },
 854+      "bin": {
 855+        "print-chrome-path": "bin/print-chrome-path.cjs"
 856+      },
 857+      "engines": {
 858+        "node": ">=12.13.0"
 859+      }
 860+    },
 861+    "node_modules/cli-boxes": {
 862+      "version": "3.0.0",
 863+      "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
 864+      "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==",
 865+      "license": "MIT",
 866+      "engines": {
 867+        "node": ">=10"
 868+      },
 869+      "funding": {
 870+        "url": "https://github.com/sponsors/sindresorhus"
 871+      }
 872+    },
 873+    "node_modules/cliui": {
 874+      "version": "8.0.1",
 875+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
 876+      "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
 877+      "license": "ISC",
 878+      "dependencies": {
 879+        "string-width": "^4.2.0",
 880+        "strip-ansi": "^6.0.1",
 881+        "wrap-ansi": "^7.0.0"
 882+      },
 883+      "engines": {
 884+        "node": ">=12"
 885+      }
 886+    },
 887+    "node_modules/cliui/node_modules/emoji-regex": {
 888+      "version": "8.0.0",
 889+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
 890+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
 891+      "license": "MIT"
 892+    },
 893+    "node_modules/cliui/node_modules/string-width": {
 894+      "version": "4.2.3",
 895+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
 896+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
 897+      "license": "MIT",
 898+      "dependencies": {
 899+        "emoji-regex": "^8.0.0",
 900+        "is-fullwidth-code-point": "^3.0.0",
 901+        "strip-ansi": "^6.0.1"
 902+      },
 903+      "engines": {
 904+        "node": ">=8"
 905+      }
 906+    },
 907+    "node_modules/cliui/node_modules/wrap-ansi": {
 908+      "version": "7.0.0",
 909+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
 910+      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
 911+      "license": "MIT",
 912+      "dependencies": {
 913+        "ansi-styles": "^4.0.0",
 914+        "string-width": "^4.1.0",
 915+        "strip-ansi": "^6.0.0"
 916+      },
 917+      "engines": {
 918+        "node": ">=10"
 919+      },
 920+      "funding": {
 921+        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
 922+      }
 923+    },
 924+    "node_modules/clone": {
 925+      "version": "1.0.4",
 926+      "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
 927+      "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
 928+      "license": "MIT",
 929+      "engines": {
 930+        "node": ">=0.8"
 931+      }
 932+    },
 933+    "node_modules/color-convert": {
 934+      "version": "2.0.1",
 935+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
 936+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
 937+      "license": "MIT",
 938+      "dependencies": {
 939+        "color-name": "~1.1.4"
 940+      },
 941+      "engines": {
 942+        "node": ">=7.0.0"
 943+      }
 944+    },
 945+    "node_modules/color-name": {
 946+      "version": "1.1.4",
 947+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
 948+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
 949+      "license": "MIT"
 950+    },
 951+    "node_modules/columnify": {
 952+      "version": "1.6.0",
 953+      "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.6.0.tgz",
 954+      "integrity": "sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q==",
 955+      "license": "MIT",
 956+      "dependencies": {
 957+        "strip-ansi": "^6.0.1",
 958+        "wcwidth": "^1.0.0"
 959+      },
 960+      "engines": {
 961+        "node": ">=8.0.0"
 962+      }
 963+    },
 964+    "node_modules/commander": {
 965+      "version": "9.5.0",
 966+      "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
 967+      "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
 968+      "license": "MIT",
 969+      "engines": {
 970+        "node": "^12.20.0 || >=14"
 971+      }
 972+    },
 973+    "node_modules/common-tags": {
 974+      "version": "1.8.2",
 975+      "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz",
 976+      "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==",
 977+      "license": "MIT",
 978+      "engines": {
 979+        "node": ">=4.0.0"
 980+      }
 981+    },
 982+    "node_modules/concat-map": {
 983+      "version": "0.0.1",
 984+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
 985+      "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
 986+      "license": "MIT"
 987+    },
 988+    "node_modules/concat-stream": {
 989+      "version": "1.6.2",
 990+      "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
 991+      "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
 992+      "engines": [
 993+        "node >= 0.8"
 994+      ],
 995+      "license": "MIT",
 996+      "dependencies": {
 997+        "buffer-from": "^1.0.0",
 998+        "inherits": "^2.0.3",
 999+        "readable-stream": "^2.2.2",
1000+        "typedarray": "^0.0.6"
1001+      }
1002+    },
1003+    "node_modules/config-chain": {
1004+      "version": "1.1.13",
1005+      "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
1006+      "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==",
1007+      "license": "MIT",
1008+      "dependencies": {
1009+        "ini": "^1.3.4",
1010+        "proto-list": "~1.2.1"
1011+      }
1012+    },
1013+    "node_modules/config-chain/node_modules/ini": {
1014+      "version": "1.3.8",
1015+      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
1016+      "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
1017+      "license": "ISC"
1018+    },
1019+    "node_modules/configstore": {
1020+      "version": "7.1.0",
1021+      "resolved": "https://registry.npmjs.org/configstore/-/configstore-7.1.0.tgz",
1022+      "integrity": "sha512-N4oog6YJWbR9kGyXvS7jEykLDXIE2C0ILYqNBZBp9iwiJpoCBWYsuAdW6PPFn6w06jjnC+3JstVvWHO4cZqvRg==",
1023+      "license": "BSD-2-Clause",
1024+      "dependencies": {
1025+        "atomically": "^2.0.3",
1026+        "dot-prop": "^9.0.0",
1027+        "graceful-fs": "^4.2.11",
1028+        "xdg-basedir": "^5.1.0"
1029+      },
1030+      "engines": {
1031+        "node": ">=18"
1032+      },
1033+      "funding": {
1034+        "url": "https://github.com/sponsors/sindresorhus"
1035+      }
1036+    },
1037+    "node_modules/core-util-is": {
1038+      "version": "1.0.3",
1039+      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
1040+      "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
1041+      "license": "MIT"
1042+    },
1043+    "node_modules/cross-spawn": {
1044+      "version": "7.0.6",
1045+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
1046+      "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
1047+      "license": "MIT",
1048+      "dependencies": {
1049+        "path-key": "^3.1.0",
1050+        "shebang-command": "^2.0.0",
1051+        "which": "^2.0.1"
1052+      },
1053+      "engines": {
1054+        "node": ">= 8"
1055+      }
1056+    },
1057+    "node_modules/css-select": {
1058+      "version": "5.2.2",
1059+      "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
1060+      "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
1061+      "license": "BSD-2-Clause",
1062+      "dependencies": {
1063+        "boolbase": "^1.0.0",
1064+        "css-what": "^6.1.0",
1065+        "domhandler": "^5.0.2",
1066+        "domutils": "^3.0.1",
1067+        "nth-check": "^2.0.1"
1068+      },
1069+      "funding": {
1070+        "url": "https://github.com/sponsors/fb55"
1071+      }
1072+    },
1073+    "node_modules/css-what": {
1074+      "version": "6.2.2",
1075+      "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
1076+      "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
1077+      "license": "BSD-2-Clause",
1078+      "engines": {
1079+        "node": ">= 6"
1080+      },
1081+      "funding": {
1082+        "url": "https://github.com/sponsors/fb55"
1083+      }
1084+    },
1085+    "node_modules/debounce": {
1086+      "version": "1.2.1",
1087+      "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
1088+      "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==",
1089+      "license": "MIT"
1090+    },
1091+    "node_modules/debug": {
1092+      "version": "4.3.7",
1093+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
1094+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
1095+      "license": "MIT",
1096+      "dependencies": {
1097+        "ms": "^2.1.3"
1098+      },
1099+      "engines": {
1100+        "node": ">=6.0"
1101+      },
1102+      "peerDependenciesMeta": {
1103+        "supports-color": {
1104+          "optional": true
1105+        }
1106+      }
1107+    },
1108+    "node_modules/decamelize": {
1109+      "version": "6.0.1",
1110+      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.1.tgz",
1111+      "integrity": "sha512-G7Cqgaelq68XHJNGlZ7lrNQyhZGsFqpwtGFexqUv4IQdjKoSYF7ipZ9UuTJZUSQXFj/XaoBLuEVIVqr8EJngEQ==",
1112+      "license": "MIT",
1113+      "engines": {
1114+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
1115+      },
1116+      "funding": {
1117+        "url": "https://github.com/sponsors/sindresorhus"
1118+      }
1119+    },
1120+    "node_modules/deep-extend": {
1121+      "version": "0.6.0",
1122+      "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
1123+      "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
1124+      "license": "MIT",
1125+      "engines": {
1126+        "node": ">=4.0.0"
1127+      }
1128+    },
1129+    "node_modules/deep-is": {
1130+      "version": "0.1.4",
1131+      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
1132+      "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
1133+      "license": "MIT"
1134+    },
1135+    "node_modules/deepmerge": {
1136+      "version": "4.3.1",
1137+      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
1138+      "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
1139+      "license": "MIT",
1140+      "engines": {
1141+        "node": ">=0.10.0"
1142+      }
1143+    },
1144+    "node_modules/default-browser": {
1145+      "version": "5.5.0",
1146+      "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz",
1147+      "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==",
1148+      "license": "MIT",
1149+      "dependencies": {
1150+        "bundle-name": "^4.1.0",
1151+        "default-browser-id": "^5.0.0"
1152+      },
1153+      "engines": {
1154+        "node": ">=18"
1155+      },
1156+      "funding": {
1157+        "url": "https://github.com/sponsors/sindresorhus"
1158+      }
1159+    },
1160+    "node_modules/default-browser-id": {
1161+      "version": "5.0.1",
1162+      "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz",
1163+      "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==",
1164+      "license": "MIT",
1165+      "engines": {
1166+        "node": ">=18"
1167+      },
1168+      "funding": {
1169+        "url": "https://github.com/sponsors/sindresorhus"
1170+      }
1171+    },
1172+    "node_modules/defaults": {
1173+      "version": "1.0.4",
1174+      "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
1175+      "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
1176+      "license": "MIT",
1177+      "dependencies": {
1178+        "clone": "^1.0.2"
1179+      },
1180+      "funding": {
1181+        "url": "https://github.com/sponsors/sindresorhus"
1182+      }
1183+    },
1184+    "node_modules/define-lazy-prop": {
1185+      "version": "3.0.0",
1186+      "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
1187+      "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
1188+      "license": "MIT",
1189+      "engines": {
1190+        "node": ">=12"
1191+      },
1192+      "funding": {
1193+        "url": "https://github.com/sponsors/sindresorhus"
1194+      }
1195+    },
1196+    "node_modules/dom-serializer": {
1197+      "version": "2.0.0",
1198+      "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
1199+      "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
1200+      "license": "MIT",
1201+      "dependencies": {
1202+        "domelementtype": "^2.3.0",
1203+        "domhandler": "^5.0.2",
1204+        "entities": "^4.2.0"
1205+      },
1206+      "funding": {
1207+        "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
1208+      }
1209+    },
1210+    "node_modules/domelementtype": {
1211+      "version": "2.3.0",
1212+      "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
1213+      "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
1214+      "funding": [
1215+        {
1216+          "type": "github",
1217+          "url": "https://github.com/sponsors/fb55"
1218+        }
1219+      ],
1220+      "license": "BSD-2-Clause"
1221+    },
1222+    "node_modules/domhandler": {
1223+      "version": "5.0.3",
1224+      "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
1225+      "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
1226+      "license": "BSD-2-Clause",
1227+      "dependencies": {
1228+        "domelementtype": "^2.3.0"
1229+      },
1230+      "engines": {
1231+        "node": ">= 4"
1232+      },
1233+      "funding": {
1234+        "url": "https://github.com/fb55/domhandler?sponsor=1"
1235+      }
1236+    },
1237+    "node_modules/domutils": {
1238+      "version": "3.2.2",
1239+      "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
1240+      "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
1241+      "license": "BSD-2-Clause",
1242+      "dependencies": {
1243+        "dom-serializer": "^2.0.0",
1244+        "domelementtype": "^2.3.0",
1245+        "domhandler": "^5.0.3"
1246+      },
1247+      "funding": {
1248+        "url": "https://github.com/fb55/domutils?sponsor=1"
1249+      }
1250+    },
1251+    "node_modules/dot-prop": {
1252+      "version": "9.0.0",
1253+      "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz",
1254+      "integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==",
1255+      "license": "MIT",
1256+      "dependencies": {
1257+        "type-fest": "^4.18.2"
1258+      },
1259+      "engines": {
1260+        "node": ">=18"
1261+      },
1262+      "funding": {
1263+        "url": "https://github.com/sponsors/sindresorhus"
1264+      }
1265+    },
1266+    "node_modules/ecdsa-sig-formatter": {
1267+      "version": "1.0.11",
1268+      "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
1269+      "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
1270+      "license": "Apache-2.0",
1271+      "dependencies": {
1272+        "safe-buffer": "^5.0.1"
1273+      }
1274+    },
1275+    "node_modules/emoji-regex": {
1276+      "version": "10.6.0",
1277+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
1278+      "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
1279+      "license": "MIT"
1280+    },
1281+    "node_modules/encoding-sniffer": {
1282+      "version": "0.2.1",
1283+      "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz",
1284+      "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==",
1285+      "license": "MIT",
1286+      "dependencies": {
1287+        "iconv-lite": "^0.6.3",
1288+        "whatwg-encoding": "^3.1.1"
1289+      },
1290+      "funding": {
1291+        "url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
1292+      }
1293+    },
1294+    "node_modules/entities": {
1295+      "version": "4.5.0",
1296+      "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
1297+      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
1298+      "license": "BSD-2-Clause",
1299+      "engines": {
1300+        "node": ">=0.12"
1301+      },
1302+      "funding": {
1303+        "url": "https://github.com/fb55/entities?sponsor=1"
1304+      }
1305+    },
1306+    "node_modules/es6-error": {
1307+      "version": "4.1.1",
1308+      "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
1309+      "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
1310+      "license": "MIT"
1311+    },
1312+    "node_modules/escalade": {
1313+      "version": "3.2.0",
1314+      "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
1315+      "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
1316+      "license": "MIT",
1317+      "engines": {
1318+        "node": ">=6"
1319+      }
1320+    },
1321+    "node_modules/escape-goat": {
1322+      "version": "4.0.0",
1323+      "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz",
1324+      "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==",
1325+      "license": "MIT",
1326+      "engines": {
1327+        "node": ">=12"
1328+      },
1329+      "funding": {
1330+        "url": "https://github.com/sponsors/sindresorhus"
1331+      }
1332+    },
1333+    "node_modules/escape-string-regexp": {
1334+      "version": "4.0.0",
1335+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
1336+      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
1337+      "license": "MIT",
1338+      "engines": {
1339+        "node": ">=10"
1340+      },
1341+      "funding": {
1342+        "url": "https://github.com/sponsors/sindresorhus"
1343+      }
1344+    },
1345+    "node_modules/eslint": {
1346+      "version": "9.39.2",
1347+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
1348+      "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
1349+      "license": "MIT",
1350+      "dependencies": {
1351+        "@eslint-community/eslint-utils": "^4.8.0",
1352+        "@eslint-community/regexpp": "^4.12.1",
1353+        "@eslint/config-array": "^0.21.1",
1354+        "@eslint/config-helpers": "^0.4.2",
1355+        "@eslint/core": "^0.17.0",
1356+        "@eslint/eslintrc": "^3.3.1",
1357+        "@eslint/js": "9.39.2",
1358+        "@eslint/plugin-kit": "^0.4.1",
1359+        "@humanfs/node": "^0.16.6",
1360+        "@humanwhocodes/module-importer": "^1.0.1",
1361+        "@humanwhocodes/retry": "^0.4.2",
1362+        "@types/estree": "^1.0.6",
1363+        "ajv": "^6.12.4",
1364+        "chalk": "^4.0.0",
1365+        "cross-spawn": "^7.0.6",
1366+        "debug": "^4.3.2",
1367+        "escape-string-regexp": "^4.0.0",
1368+        "eslint-scope": "^8.4.0",
1369+        "eslint-visitor-keys": "^4.2.1",
1370+        "espree": "^10.4.0",
1371+        "esquery": "^1.5.0",
1372+        "esutils": "^2.0.2",
1373+        "fast-deep-equal": "^3.1.3",
1374+        "file-entry-cache": "^8.0.0",
1375+        "find-up": "^5.0.0",
1376+        "glob-parent": "^6.0.2",
1377+        "ignore": "^5.2.0",
1378+        "imurmurhash": "^0.1.4",
1379+        "is-glob": "^4.0.0",
1380+        "json-stable-stringify-without-jsonify": "^1.0.1",
1381+        "lodash.merge": "^4.6.2",
1382+        "minimatch": "^3.1.2",
1383+        "natural-compare": "^1.4.0",
1384+        "optionator": "^0.9.3"
1385+      },
1386+      "bin": {
1387+        "eslint": "bin/eslint.js"
1388+      },
1389+      "engines": {
1390+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
1391+      },
1392+      "funding": {
1393+        "url": "https://eslint.org/donate"
1394+      },
1395+      "peerDependencies": {
1396+        "jiti": "*"
1397+      },
1398+      "peerDependenciesMeta": {
1399+        "jiti": {
1400+          "optional": true
1401+        }
1402+      }
1403+    },
1404+    "node_modules/eslint-plugin-no-unsanitized": {
1405+      "version": "4.1.5",
1406+      "resolved": "https://registry.npmjs.org/eslint-plugin-no-unsanitized/-/eslint-plugin-no-unsanitized-4.1.5.tgz",
1407+      "integrity": "sha512-MSB4hXPVFQrI8weqzs6gzl7reP2k/qSjtCoL2vUMSDejIIq9YL1ZKvq5/ORBXab/PvfBBrWO2jWviYpL+4Ghfg==",
1408+      "license": "MPL-2.0",
1409+      "peerDependencies": {
1410+        "eslint": "^9 || ^10"
1411+      }
1412+    },
1413+    "node_modules/eslint-scope": {
1414+      "version": "8.4.0",
1415+      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
1416+      "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
1417+      "license": "BSD-2-Clause",
1418+      "dependencies": {
1419+        "esrecurse": "^4.3.0",
1420+        "estraverse": "^5.2.0"
1421+      },
1422+      "engines": {
1423+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
1424+      },
1425+      "funding": {
1426+        "url": "https://opencollective.com/eslint"
1427+      }
1428+    },
1429+    "node_modules/eslint-visitor-keys": {
1430+      "version": "5.0.1",
1431+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
1432+      "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
1433+      "license": "Apache-2.0",
1434+      "engines": {
1435+        "node": "^20.19.0 || ^22.13.0 || >=24"
1436+      },
1437+      "funding": {
1438+        "url": "https://opencollective.com/eslint"
1439+      }
1440+    },
1441+    "node_modules/eslint/node_modules/ajv": {
1442+      "version": "6.14.0",
1443+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
1444+      "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
1445+      "license": "MIT",
1446+      "dependencies": {
1447+        "fast-deep-equal": "^3.1.1",
1448+        "fast-json-stable-stringify": "^2.0.0",
1449+        "json-schema-traverse": "^0.4.1",
1450+        "uri-js": "^4.2.2"
1451+      },
1452+      "funding": {
1453+        "type": "github",
1454+        "url": "https://github.com/sponsors/epoberezkin"
1455+      }
1456+    },
1457+    "node_modules/eslint/node_modules/eslint-visitor-keys": {
1458+      "version": "4.2.1",
1459+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
1460+      "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
1461+      "license": "Apache-2.0",
1462+      "engines": {
1463+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
1464+      },
1465+      "funding": {
1466+        "url": "https://opencollective.com/eslint"
1467+      }
1468+    },
1469+    "node_modules/eslint/node_modules/espree": {
1470+      "version": "10.4.0",
1471+      "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
1472+      "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
1473+      "license": "BSD-2-Clause",
1474+      "dependencies": {
1475+        "acorn": "^8.15.0",
1476+        "acorn-jsx": "^5.3.2",
1477+        "eslint-visitor-keys": "^4.2.1"
1478+      },
1479+      "engines": {
1480+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
1481+      },
1482+      "funding": {
1483+        "url": "https://opencollective.com/eslint"
1484+      }
1485+    },
1486+    "node_modules/eslint/node_modules/json-schema-traverse": {
1487+      "version": "0.4.1",
1488+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
1489+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
1490+      "license": "MIT"
1491+    },
1492+    "node_modules/espree": {
1493+      "version": "11.2.0",
1494+      "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz",
1495+      "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==",
1496+      "license": "BSD-2-Clause",
1497+      "dependencies": {
1498+        "acorn": "^8.16.0",
1499+        "acorn-jsx": "^5.3.2",
1500+        "eslint-visitor-keys": "^5.0.1"
1501+      },
1502+      "engines": {
1503+        "node": "^20.19.0 || ^22.13.0 || >=24"
1504+      },
1505+      "funding": {
1506+        "url": "https://opencollective.com/eslint"
1507+      }
1508+    },
1509+    "node_modules/esprima": {
1510+      "version": "4.0.1",
1511+      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
1512+      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
1513+      "license": "BSD-2-Clause",
1514+      "bin": {
1515+        "esparse": "bin/esparse.js",
1516+        "esvalidate": "bin/esvalidate.js"
1517+      },
1518+      "engines": {
1519+        "node": ">=4"
1520+      }
1521+    },
1522+    "node_modules/esquery": {
1523+      "version": "1.7.0",
1524+      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
1525+      "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
1526+      "license": "BSD-3-Clause",
1527+      "dependencies": {
1528+        "estraverse": "^5.1.0"
1529+      },
1530+      "engines": {
1531+        "node": ">=0.10"
1532+      }
1533+    },
1534+    "node_modules/esrecurse": {
1535+      "version": "4.3.0",
1536+      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
1537+      "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
1538+      "license": "BSD-2-Clause",
1539+      "dependencies": {
1540+        "estraverse": "^5.2.0"
1541+      },
1542+      "engines": {
1543+        "node": ">=4.0"
1544+      }
1545+    },
1546+    "node_modules/estraverse": {
1547+      "version": "5.3.0",
1548+      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
1549+      "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
1550+      "license": "BSD-2-Clause",
1551+      "engines": {
1552+        "node": ">=4.0"
1553+      }
1554+    },
1555+    "node_modules/esutils": {
1556+      "version": "2.0.3",
1557+      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
1558+      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
1559+      "license": "BSD-2-Clause",
1560+      "engines": {
1561+        "node": ">=0.10.0"
1562+      }
1563+    },
1564+    "node_modules/fast-deep-equal": {
1565+      "version": "3.1.3",
1566+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
1567+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
1568+      "license": "MIT"
1569+    },
1570+    "node_modules/fast-json-patch": {
1571+      "version": "3.1.1",
1572+      "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz",
1573+      "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==",
1574+      "license": "MIT"
1575+    },
1576+    "node_modules/fast-json-stable-stringify": {
1577+      "version": "2.1.0",
1578+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
1579+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
1580+      "license": "MIT"
1581+    },
1582+    "node_modules/fast-levenshtein": {
1583+      "version": "2.0.6",
1584+      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
1585+      "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
1586+      "license": "MIT"
1587+    },
1588+    "node_modules/fast-uri": {
1589+      "version": "3.1.0",
1590+      "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
1591+      "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
1592+      "funding": [
1593+        {
1594+          "type": "github",
1595+          "url": "https://github.com/sponsors/fastify"
1596+        },
1597+        {
1598+          "type": "opencollective",
1599+          "url": "https://opencollective.com/fastify"
1600+        }
1601+      ],
1602+      "license": "BSD-3-Clause"
1603+    },
1604+    "node_modules/file-entry-cache": {
1605+      "version": "8.0.0",
1606+      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
1607+      "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
1608+      "license": "MIT",
1609+      "dependencies": {
1610+        "flat-cache": "^4.0.0"
1611+      },
1612+      "engines": {
1613+        "node": ">=16.0.0"
1614+      }
1615+    },
1616+    "node_modules/find-up": {
1617+      "version": "5.0.0",
1618+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
1619+      "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
1620+      "license": "MIT",
1621+      "dependencies": {
1622+        "locate-path": "^6.0.0",
1623+        "path-exists": "^4.0.0"
1624+      },
1625+      "engines": {
1626+        "node": ">=10"
1627+      },
1628+      "funding": {
1629+        "url": "https://github.com/sponsors/sindresorhus"
1630+      }
1631+    },
1632+    "node_modules/firefox-profile": {
1633+      "version": "4.7.0",
1634+      "resolved": "https://registry.npmjs.org/firefox-profile/-/firefox-profile-4.7.0.tgz",
1635+      "integrity": "sha512-aGApEu5bfCNbA4PGUZiRJAIU6jKmghV2UVdklXAofnNtiDjqYw0czLS46W7IfFqVKgKhFB8Ao2YoNGHY4BoIMQ==",
1636+      "license": "MIT",
1637+      "dependencies": {
1638+        "adm-zip": "~0.5.x",
1639+        "fs-extra": "^11.2.0",
1640+        "ini": "^4.1.3",
1641+        "minimist": "^1.2.8",
1642+        "xml2js": "^0.6.2"
1643+      },
1644+      "bin": {
1645+        "firefox-profile": "lib/cli.js"
1646+      },
1647+      "engines": {
1648+        "node": ">=18"
1649+      }
1650+    },
1651+    "node_modules/first-chunk-stream": {
1652+      "version": "3.0.0",
1653+      "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-3.0.0.tgz",
1654+      "integrity": "sha512-LNRvR4hr/S8cXXkIY5pTgVP7L3tq6LlYWcg9nWBuW7o1NMxKZo6oOVa/6GIekMGI0Iw7uC+HWimMe9u/VAeKqw==",
1655+      "license": "MIT",
1656+      "engines": {
1657+        "node": ">=8"
1658+      }
1659+    },
1660+    "node_modules/flat-cache": {
1661+      "version": "4.0.1",
1662+      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
1663+      "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
1664+      "license": "MIT",
1665+      "dependencies": {
1666+        "flatted": "^3.2.9",
1667+        "keyv": "^4.5.4"
1668+      },
1669+      "engines": {
1670+        "node": ">=16"
1671+      }
1672+    },
1673+    "node_modules/flatted": {
1674+      "version": "3.4.2",
1675+      "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
1676+      "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
1677+      "license": "ISC"
1678+    },
1679+    "node_modules/fs-extra": {
1680+      "version": "11.3.4",
1681+      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz",
1682+      "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==",
1683+      "license": "MIT",
1684+      "dependencies": {
1685+        "graceful-fs": "^4.2.0",
1686+        "jsonfile": "^6.0.1",
1687+        "universalify": "^2.0.0"
1688+      },
1689+      "engines": {
1690+        "node": ">=14.14"
1691+      }
1692+    },
1693+    "node_modules/fx-runner": {
1694+      "version": "1.4.0",
1695+      "resolved": "https://registry.npmjs.org/fx-runner/-/fx-runner-1.4.0.tgz",
1696+      "integrity": "sha512-rci1g6U0rdTg6bAaBboP7XdRu01dzTAaKXxFf+PUqGuCv6Xu7o8NZdY1D5MvKGIjb6EdS1g3VlXOgksir1uGkg==",
1697+      "license": "MPL-2.0",
1698+      "dependencies": {
1699+        "commander": "2.9.0",
1700+        "shell-quote": "1.7.3",
1701+        "spawn-sync": "1.0.15",
1702+        "when": "3.7.7",
1703+        "which": "1.2.4",
1704+        "winreg": "0.0.12"
1705+      },
1706+      "bin": {
1707+        "fx-runner": "bin/fx-runner"
1708+      }
1709+    },
1710+    "node_modules/fx-runner/node_modules/commander": {
1711+      "version": "2.9.0",
1712+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz",
1713+      "integrity": "sha512-bmkUukX8wAOjHdN26xj5c4ctEV22TQ7dQYhSmuckKhToXrkUn0iIaolHdIxYYqD55nhpSPA9zPQ1yP57GdXP2A==",
1714+      "license": "MIT",
1715+      "dependencies": {
1716+        "graceful-readlink": ">= 1.0.0"
1717+      },
1718+      "engines": {
1719+        "node": ">= 0.6.x"
1720+      }
1721+    },
1722+    "node_modules/fx-runner/node_modules/isexe": {
1723+      "version": "1.1.2",
1724+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-1.1.2.tgz",
1725+      "integrity": "sha512-d2eJzK691yZwPHcv1LbeAOa91yMJ9QmfTgSO1oXB65ezVhXQsxBac2vEB4bMVms9cGzaA99n6V2viHMq82VLDw==",
1726+      "license": "ISC"
1727+    },
1728+    "node_modules/fx-runner/node_modules/which": {
1729+      "version": "1.2.4",
1730+      "resolved": "https://registry.npmjs.org/which/-/which-1.2.4.tgz",
1731+      "integrity": "sha512-zDRAqDSBudazdfM9zpiI30Fu9ve47htYXcGi3ln0wfKu2a7SmrT6F3VDoYONu//48V8Vz4TdCRNPjtvyRO3yBA==",
1732+      "license": "ISC",
1733+      "dependencies": {
1734+        "is-absolute": "^0.1.7",
1735+        "isexe": "^1.1.1"
1736+      },
1737+      "bin": {
1738+        "which": "bin/which"
1739+      }
1740+    },
1741+    "node_modules/get-caller-file": {
1742+      "version": "2.0.5",
1743+      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
1744+      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
1745+      "license": "ISC",
1746+      "engines": {
1747+        "node": "6.* || 8.* || >= 10.*"
1748+      }
1749+    },
1750+    "node_modules/get-east-asian-width": {
1751+      "version": "1.5.0",
1752+      "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz",
1753+      "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==",
1754+      "license": "MIT",
1755+      "engines": {
1756+        "node": ">=18"
1757+      },
1758+      "funding": {
1759+        "url": "https://github.com/sponsors/sindresorhus"
1760+      }
1761+    },
1762+    "node_modules/glob-parent": {
1763+      "version": "6.0.2",
1764+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
1765+      "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
1766+      "license": "ISC",
1767+      "dependencies": {
1768+        "is-glob": "^4.0.3"
1769+      },
1770+      "engines": {
1771+        "node": ">=10.13.0"
1772+      }
1773+    },
1774+    "node_modules/glob-to-regexp": {
1775+      "version": "0.4.1",
1776+      "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
1777+      "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
1778+      "license": "BSD-2-Clause"
1779+    },
1780+    "node_modules/global-directory": {
1781+      "version": "4.0.1",
1782+      "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz",
1783+      "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==",
1784+      "license": "MIT",
1785+      "dependencies": {
1786+        "ini": "4.1.1"
1787+      },
1788+      "engines": {
1789+        "node": ">=18"
1790+      },
1791+      "funding": {
1792+        "url": "https://github.com/sponsors/sindresorhus"
1793+      }
1794+    },
1795+    "node_modules/global-directory/node_modules/ini": {
1796+      "version": "4.1.1",
1797+      "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz",
1798+      "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==",
1799+      "license": "ISC",
1800+      "engines": {
1801+        "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
1802+      }
1803+    },
1804+    "node_modules/globals": {
1805+      "version": "14.0.0",
1806+      "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
1807+      "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
1808+      "license": "MIT",
1809+      "engines": {
1810+        "node": ">=18"
1811+      },
1812+      "funding": {
1813+        "url": "https://github.com/sponsors/sindresorhus"
1814+      }
1815+    },
1816+    "node_modules/graceful-fs": {
1817+      "version": "4.2.11",
1818+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
1819+      "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
1820+      "license": "ISC"
1821+    },
1822+    "node_modules/graceful-readlink": {
1823+      "version": "1.0.1",
1824+      "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
1825+      "integrity": "sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==",
1826+      "license": "MIT"
1827+    },
1828+    "node_modules/growly": {
1829+      "version": "1.3.0",
1830+      "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
1831+      "integrity": "sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==",
1832+      "license": "MIT"
1833+    },
1834+    "node_modules/has-flag": {
1835+      "version": "4.0.0",
1836+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
1837+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
1838+      "license": "MIT",
1839+      "engines": {
1840+        "node": ">=8"
1841+      }
1842+    },
1843+    "node_modules/htmlparser2": {
1844+      "version": "10.1.0",
1845+      "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz",
1846+      "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==",
1847+      "funding": [
1848+        "https://github.com/fb55/htmlparser2?sponsor=1",
1849+        {
1850+          "type": "github",
1851+          "url": "https://github.com/sponsors/fb55"
1852+        }
1853+      ],
1854+      "license": "MIT",
1855+      "dependencies": {
1856+        "domelementtype": "^2.3.0",
1857+        "domhandler": "^5.0.3",
1858+        "domutils": "^3.2.2",
1859+        "entities": "^7.0.1"
1860+      }
1861+    },
1862+    "node_modules/htmlparser2/node_modules/entities": {
1863+      "version": "7.0.1",
1864+      "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
1865+      "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
1866+      "license": "BSD-2-Clause",
1867+      "engines": {
1868+        "node": ">=0.12"
1869+      },
1870+      "funding": {
1871+        "url": "https://github.com/fb55/entities?sponsor=1"
1872+      }
1873+    },
1874+    "node_modules/https-proxy-agent": {
1875+      "version": "7.0.6",
1876+      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
1877+      "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
1878+      "license": "MIT",
1879+      "dependencies": {
1880+        "agent-base": "^7.1.2",
1881+        "debug": "4"
1882+      },
1883+      "engines": {
1884+        "node": ">= 14"
1885+      }
1886+    },
1887+    "node_modules/iconv-lite": {
1888+      "version": "0.6.3",
1889+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
1890+      "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
1891+      "license": "MIT",
1892+      "dependencies": {
1893+        "safer-buffer": ">= 2.1.2 < 3.0.0"
1894+      },
1895+      "engines": {
1896+        "node": ">=0.10.0"
1897+      }
1898+    },
1899+    "node_modules/ignore": {
1900+      "version": "5.3.2",
1901+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
1902+      "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
1903+      "license": "MIT",
1904+      "engines": {
1905+        "node": ">= 4"
1906+      }
1907+    },
1908+    "node_modules/image-size": {
1909+      "version": "2.0.2",
1910+      "resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.2.tgz",
1911+      "integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==",
1912+      "license": "MIT",
1913+      "bin": {
1914+        "image-size": "bin/image-size.js"
1915+      },
1916+      "engines": {
1917+        "node": ">=16.x"
1918+      }
1919+    },
1920+    "node_modules/immediate": {
1921+      "version": "3.0.6",
1922+      "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
1923+      "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
1924+      "license": "MIT"
1925+    },
1926+    "node_modules/import-fresh": {
1927+      "version": "3.3.1",
1928+      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
1929+      "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
1930+      "license": "MIT",
1931+      "dependencies": {
1932+        "parent-module": "^1.0.0",
1933+        "resolve-from": "^4.0.0"
1934+      },
1935+      "engines": {
1936+        "node": ">=6"
1937+      },
1938+      "funding": {
1939+        "url": "https://github.com/sponsors/sindresorhus"
1940+      }
1941+    },
1942+    "node_modules/imurmurhash": {
1943+      "version": "0.1.4",
1944+      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
1945+      "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
1946+      "license": "MIT",
1947+      "engines": {
1948+        "node": ">=0.8.19"
1949+      }
1950+    },
1951+    "node_modules/index-to-position": {
1952+      "version": "1.2.0",
1953+      "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz",
1954+      "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==",
1955+      "license": "MIT",
1956+      "engines": {
1957+        "node": ">=18"
1958+      },
1959+      "funding": {
1960+        "url": "https://github.com/sponsors/sindresorhus"
1961+      }
1962+    },
1963+    "node_modules/inherits": {
1964+      "version": "2.0.4",
1965+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
1966+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
1967+      "license": "ISC"
1968+    },
1969+    "node_modules/ini": {
1970+      "version": "4.1.3",
1971+      "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz",
1972+      "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==",
1973+      "license": "ISC",
1974+      "engines": {
1975+        "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
1976+      }
1977+    },
1978+    "node_modules/is-absolute": {
1979+      "version": "0.1.7",
1980+      "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz",
1981+      "integrity": "sha512-Xi9/ZSn4NFapG8RP98iNPMOeaV3mXPisxKxzKtHVqr3g56j/fBn+yZmnxSVAA8lmZbl2J9b/a4kJvfU3hqQYgA==",
1982+      "license": "MIT",
1983+      "dependencies": {
1984+        "is-relative": "^0.1.0"
1985+      },
1986+      "engines": {
1987+        "node": ">=0.10.0"
1988+      }
1989+    },
1990+    "node_modules/is-docker": {
1991+      "version": "2.2.1",
1992+      "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
1993+      "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
1994+      "license": "MIT",
1995+      "bin": {
1996+        "is-docker": "cli.js"
1997+      },
1998+      "engines": {
1999+        "node": ">=8"
2000+      },
2001+      "funding": {
2002+        "url": "https://github.com/sponsors/sindresorhus"
2003+      }
2004+    },
2005+    "node_modules/is-extglob": {
2006+      "version": "2.1.1",
2007+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
2008+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
2009+      "license": "MIT",
2010+      "engines": {
2011+        "node": ">=0.10.0"
2012+      }
2013+    },
2014+    "node_modules/is-fullwidth-code-point": {
2015+      "version": "3.0.0",
2016+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
2017+      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
2018+      "license": "MIT",
2019+      "engines": {
2020+        "node": ">=8"
2021+      }
2022+    },
2023+    "node_modules/is-glob": {
2024+      "version": "4.0.3",
2025+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
2026+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
2027+      "license": "MIT",
2028+      "dependencies": {
2029+        "is-extglob": "^2.1.1"
2030+      },
2031+      "engines": {
2032+        "node": ">=0.10.0"
2033+      }
2034+    },
2035+    "node_modules/is-in-ci": {
2036+      "version": "1.0.0",
2037+      "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-1.0.0.tgz",
2038+      "integrity": "sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg==",
2039+      "license": "MIT",
2040+      "bin": {
2041+        "is-in-ci": "cli.js"
2042+      },
2043+      "engines": {
2044+        "node": ">=18"
2045+      },
2046+      "funding": {
2047+        "url": "https://github.com/sponsors/sindresorhus"
2048+      }
2049+    },
2050+    "node_modules/is-in-ssh": {
2051+      "version": "1.0.0",
2052+      "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz",
2053+      "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==",
2054+      "license": "MIT",
2055+      "engines": {
2056+        "node": ">=20"
2057+      },
2058+      "funding": {
2059+        "url": "https://github.com/sponsors/sindresorhus"
2060+      }
2061+    },
2062+    "node_modules/is-inside-container": {
2063+      "version": "1.0.0",
2064+      "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
2065+      "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
2066+      "license": "MIT",
2067+      "dependencies": {
2068+        "is-docker": "^3.0.0"
2069+      },
2070+      "bin": {
2071+        "is-inside-container": "cli.js"
2072+      },
2073+      "engines": {
2074+        "node": ">=14.16"
2075+      },
2076+      "funding": {
2077+        "url": "https://github.com/sponsors/sindresorhus"
2078+      }
2079+    },
2080+    "node_modules/is-inside-container/node_modules/is-docker": {
2081+      "version": "3.0.0",
2082+      "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
2083+      "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
2084+      "license": "MIT",
2085+      "bin": {
2086+        "is-docker": "cli.js"
2087+      },
2088+      "engines": {
2089+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
2090+      },
2091+      "funding": {
2092+        "url": "https://github.com/sponsors/sindresorhus"
2093+      }
2094+    },
2095+    "node_modules/is-installed-globally": {
2096+      "version": "1.0.0",
2097+      "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-1.0.0.tgz",
2098+      "integrity": "sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ==",
2099+      "license": "MIT",
2100+      "dependencies": {
2101+        "global-directory": "^4.0.1",
2102+        "is-path-inside": "^4.0.0"
2103+      },
2104+      "engines": {
2105+        "node": ">=18"
2106+      },
2107+      "funding": {
2108+        "url": "https://github.com/sponsors/sindresorhus"
2109+      }
2110+    },
2111+    "node_modules/is-npm": {
2112+      "version": "6.1.0",
2113+      "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.1.0.tgz",
2114+      "integrity": "sha512-O2z4/kNgyjhQwVR1Wpkbfc19JIhggF97NZNCpWTnjH7kVcZMUrnut9XSN7txI7VdyIYk5ZatOq3zvSuWpU8hoA==",
2115+      "license": "MIT",
2116+      "engines": {
2117+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
2118+      },
2119+      "funding": {
2120+        "url": "https://github.com/sponsors/sindresorhus"
2121+      }
2122+    },
2123+    "node_modules/is-path-inside": {
2124+      "version": "4.0.0",
2125+      "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz",
2126+      "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==",
2127+      "license": "MIT",
2128+      "engines": {
2129+        "node": ">=12"
2130+      },
2131+      "funding": {
2132+        "url": "https://github.com/sponsors/sindresorhus"
2133+      }
2134+    },
2135+    "node_modules/is-relative": {
2136+      "version": "0.1.3",
2137+      "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz",
2138+      "integrity": "sha512-wBOr+rNM4gkAZqoLRJI4myw5WzzIdQosFAAbnvfXP5z1LyzgAI3ivOKehC5KfqlQJZoihVhirgtCBj378Eg8GA==",
2139+      "engines": {
2140+        "node": ">=0.10.0"
2141+      }
2142+    },
2143+    "node_modules/is-utf8": {
2144+      "version": "0.2.1",
2145+      "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
2146+      "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==",
2147+      "license": "MIT"
2148+    },
2149+    "node_modules/is-wsl": {
2150+      "version": "2.2.0",
2151+      "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
2152+      "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
2153+      "license": "MIT",
2154+      "dependencies": {
2155+        "is-docker": "^2.0.0"
2156+      },
2157+      "engines": {
2158+        "node": ">=8"
2159+      }
2160+    },
2161+    "node_modules/isarray": {
2162+      "version": "1.0.0",
2163+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
2164+      "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
2165+      "license": "MIT"
2166+    },
2167+    "node_modules/isexe": {
2168+      "version": "2.0.0",
2169+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
2170+      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
2171+      "license": "ISC"
2172+    },
2173+    "node_modules/jose": {
2174+      "version": "5.9.6",
2175+      "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz",
2176+      "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==",
2177+      "license": "MIT",
2178+      "funding": {
2179+        "url": "https://github.com/sponsors/panva"
2180+      }
2181+    },
2182+    "node_modules/js-tokens": {
2183+      "version": "4.0.0",
2184+      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
2185+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
2186+      "license": "MIT"
2187+    },
2188+    "node_modules/js-yaml": {
2189+      "version": "4.1.1",
2190+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
2191+      "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
2192+      "license": "MIT",
2193+      "dependencies": {
2194+        "argparse": "^2.0.1"
2195+      },
2196+      "bin": {
2197+        "js-yaml": "bin/js-yaml.js"
2198+      }
2199+    },
2200+    "node_modules/json-buffer": {
2201+      "version": "3.0.1",
2202+      "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
2203+      "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
2204+      "license": "MIT"
2205+    },
2206+    "node_modules/json-merge-patch": {
2207+      "version": "1.0.2",
2208+      "resolved": "https://registry.npmjs.org/json-merge-patch/-/json-merge-patch-1.0.2.tgz",
2209+      "integrity": "sha512-M6Vp2GN9L7cfuMXiWOmHj9bEFbeC250iVtcKQbqVgEsDVYnIsrNsbU+h/Y/PkbBQCtEa4Bez+Ebv0zfbC8ObLg==",
2210+      "license": "MIT",
2211+      "dependencies": {
2212+        "fast-deep-equal": "^3.1.3"
2213+      }
2214+    },
2215+    "node_modules/json-schema-traverse": {
2216+      "version": "1.0.0",
2217+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
2218+      "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
2219+      "license": "MIT"
2220+    },
2221+    "node_modules/json-stable-stringify-without-jsonify": {
2222+      "version": "1.0.1",
2223+      "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
2224+      "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
2225+      "license": "MIT"
2226+    },
2227+    "node_modules/jsonfile": {
2228+      "version": "6.2.0",
2229+      "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
2230+      "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
2231+      "license": "MIT",
2232+      "dependencies": {
2233+        "universalify": "^2.0.0"
2234+      },
2235+      "optionalDependencies": {
2236+        "graceful-fs": "^4.1.6"
2237+      }
2238+    },
2239+    "node_modules/jsonwebtoken": {
2240+      "version": "9.0.3",
2241+      "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
2242+      "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
2243+      "license": "MIT",
2244+      "dependencies": {
2245+        "jws": "^4.0.1",
2246+        "lodash.includes": "^4.3.0",
2247+        "lodash.isboolean": "^3.0.3",
2248+        "lodash.isinteger": "^4.0.4",
2249+        "lodash.isnumber": "^3.0.3",
2250+        "lodash.isplainobject": "^4.0.6",
2251+        "lodash.isstring": "^4.0.1",
2252+        "lodash.once": "^4.0.0",
2253+        "ms": "^2.1.1",
2254+        "semver": "^7.5.4"
2255+      },
2256+      "engines": {
2257+        "node": ">=12",
2258+        "npm": ">=6"
2259+      }
2260+    },
2261+    "node_modules/jszip": {
2262+      "version": "3.10.1",
2263+      "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
2264+      "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
2265+      "license": "(MIT OR GPL-3.0-or-later)",
2266+      "dependencies": {
2267+        "lie": "~3.3.0",
2268+        "pako": "~1.0.2",
2269+        "readable-stream": "~2.3.6",
2270+        "setimmediate": "^1.0.5"
2271+      }
2272+    },
2273+    "node_modules/jwa": {
2274+      "version": "2.0.1",
2275+      "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
2276+      "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
2277+      "license": "MIT",
2278+      "dependencies": {
2279+        "buffer-equal-constant-time": "^1.0.1",
2280+        "ecdsa-sig-formatter": "1.0.11",
2281+        "safe-buffer": "^5.0.1"
2282+      }
2283+    },
2284+    "node_modules/jws": {
2285+      "version": "4.0.1",
2286+      "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
2287+      "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
2288+      "license": "MIT",
2289+      "dependencies": {
2290+        "jwa": "^2.0.1",
2291+        "safe-buffer": "^5.0.1"
2292+      }
2293+    },
2294+    "node_modules/keyv": {
2295+      "version": "4.5.4",
2296+      "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
2297+      "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
2298+      "license": "MIT",
2299+      "dependencies": {
2300+        "json-buffer": "3.0.1"
2301+      }
2302+    },
2303+    "node_modules/ky": {
2304+      "version": "1.14.3",
2305+      "resolved": "https://registry.npmjs.org/ky/-/ky-1.14.3.tgz",
2306+      "integrity": "sha512-9zy9lkjac+TR1c2tG+mkNSVlyOpInnWdSMiue4F+kq8TwJSgv6o8jhLRg8Ho6SnZ9wOYUq/yozts9qQCfk7bIw==",
2307+      "license": "MIT",
2308+      "engines": {
2309+        "node": ">=18"
2310+      },
2311+      "funding": {
2312+        "url": "https://github.com/sindresorhus/ky?sponsor=1"
2313+      }
2314+    },
2315+    "node_modules/latest-version": {
2316+      "version": "9.0.0",
2317+      "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-9.0.0.tgz",
2318+      "integrity": "sha512-7W0vV3rqv5tokqkBAFV1LbR7HPOWzXQDpDgEuib/aJ1jsZZx6x3c2mBI+TJhJzOhkGeaLbCKEHXEXLfirtG2JA==",
2319+      "license": "MIT",
2320+      "dependencies": {
2321+        "package-json": "^10.0.0"
2322+      },
2323+      "engines": {
2324+        "node": ">=18"
2325+      },
2326+      "funding": {
2327+        "url": "https://github.com/sponsors/sindresorhus"
2328+      }
2329+    },
2330+    "node_modules/levn": {
2331+      "version": "0.4.1",
2332+      "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
2333+      "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
2334+      "license": "MIT",
2335+      "dependencies": {
2336+        "prelude-ls": "^1.2.1",
2337+        "type-check": "~0.4.0"
2338+      },
2339+      "engines": {
2340+        "node": ">= 0.8.0"
2341+      }
2342+    },
2343+    "node_modules/lie": {
2344+      "version": "3.3.0",
2345+      "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
2346+      "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
2347+      "license": "MIT",
2348+      "dependencies": {
2349+        "immediate": "~3.0.5"
2350+      }
2351+    },
2352+    "node_modules/lighthouse-logger": {
2353+      "version": "2.0.2",
2354+      "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.2.tgz",
2355+      "integrity": "sha512-vWl2+u5jgOQuZR55Z1WM0XDdrJT6mzMP8zHUct7xTlWhuQs+eV0g+QL0RQdFjT54zVmbhLCP8vIVpy1wGn/gCg==",
2356+      "license": "Apache-2.0",
2357+      "dependencies": {
2358+        "debug": "^4.4.1",
2359+        "marky": "^1.2.2"
2360+      }
2361+    },
2362+    "node_modules/lighthouse-logger/node_modules/debug": {
2363+      "version": "4.4.3",
2364+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
2365+      "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
2366+      "license": "MIT",
2367+      "dependencies": {
2368+        "ms": "^2.1.3"
2369+      },
2370+      "engines": {
2371+        "node": ">=6.0"
2372+      },
2373+      "peerDependenciesMeta": {
2374+        "supports-color": {
2375+          "optional": true
2376+        }
2377+      }
2378+    },
2379+    "node_modules/locate-path": {
2380+      "version": "6.0.0",
2381+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
2382+      "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
2383+      "license": "MIT",
2384+      "dependencies": {
2385+        "p-locate": "^5.0.0"
2386+      },
2387+      "engines": {
2388+        "node": ">=10"
2389+      },
2390+      "funding": {
2391+        "url": "https://github.com/sponsors/sindresorhus"
2392+      }
2393+    },
2394+    "node_modules/lodash.includes": {
2395+      "version": "4.3.0",
2396+      "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
2397+      "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
2398+      "license": "MIT"
2399+    },
2400+    "node_modules/lodash.isboolean": {
2401+      "version": "3.0.3",
2402+      "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
2403+      "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
2404+      "license": "MIT"
2405+    },
2406+    "node_modules/lodash.isinteger": {
2407+      "version": "4.0.4",
2408+      "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
2409+      "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
2410+      "license": "MIT"
2411+    },
2412+    "node_modules/lodash.isnumber": {
2413+      "version": "3.0.3",
2414+      "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
2415+      "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
2416+      "license": "MIT"
2417+    },
2418+    "node_modules/lodash.isplainobject": {
2419+      "version": "4.0.6",
2420+      "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
2421+      "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
2422+      "license": "MIT"
2423+    },
2424+    "node_modules/lodash.isstring": {
2425+      "version": "4.0.1",
2426+      "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
2427+      "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
2428+      "license": "MIT"
2429+    },
2430+    "node_modules/lodash.merge": {
2431+      "version": "4.6.2",
2432+      "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
2433+      "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
2434+      "license": "MIT"
2435+    },
2436+    "node_modules/lodash.once": {
2437+      "version": "4.1.1",
2438+      "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
2439+      "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
2440+      "license": "MIT"
2441+    },
2442+    "node_modules/make-error": {
2443+      "version": "1.3.6",
2444+      "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
2445+      "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
2446+      "license": "ISC"
2447+    },
2448+    "node_modules/marky": {
2449+      "version": "1.3.0",
2450+      "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz",
2451+      "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==",
2452+      "license": "Apache-2.0"
2453+    },
2454+    "node_modules/minimatch": {
2455+      "version": "3.1.5",
2456+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
2457+      "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
2458+      "license": "ISC",
2459+      "dependencies": {
2460+        "brace-expansion": "^1.1.7"
2461+      },
2462+      "engines": {
2463+        "node": "*"
2464+      }
2465+    },
2466+    "node_modules/minimist": {
2467+      "version": "1.2.8",
2468+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
2469+      "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
2470+      "license": "MIT",
2471+      "funding": {
2472+        "url": "https://github.com/sponsors/ljharb"
2473+      }
2474+    },
2475+    "node_modules/ms": {
2476+      "version": "2.1.3",
2477+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
2478+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
2479+      "license": "MIT"
2480+    },
2481+    "node_modules/multimatch": {
2482+      "version": "6.0.0",
2483+      "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-6.0.0.tgz",
2484+      "integrity": "sha512-I7tSVxHGPlmPN/enE3mS1aOSo6bWBfls+3HmuEeCUBCE7gWnm3cBXCBkpurzFjVRwC6Kld8lLaZ1Iv5vOcjvcQ==",
2485+      "license": "MIT",
2486+      "dependencies": {
2487+        "@types/minimatch": "^3.0.5",
2488+        "array-differ": "^4.0.0",
2489+        "array-union": "^3.0.1",
2490+        "minimatch": "^3.0.4"
2491+      },
2492+      "engines": {
2493+        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
2494+      },
2495+      "funding": {
2496+        "url": "https://github.com/sponsors/sindresorhus"
2497+      }
2498+    },
2499+    "node_modules/natural-compare": {
2500+      "version": "1.4.0",
2501+      "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
2502+      "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
2503+      "license": "MIT"
2504+    },
2505+    "node_modules/node-forge": {
2506+      "version": "1.3.3",
2507+      "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz",
2508+      "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==",
2509+      "license": "(BSD-3-Clause OR GPL-2.0)",
2510+      "engines": {
2511+        "node": ">= 6.13.0"
2512+      }
2513+    },
2514+    "node_modules/node-notifier": {
2515+      "version": "10.0.1",
2516+      "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-10.0.1.tgz",
2517+      "integrity": "sha512-YX7TSyDukOZ0g+gmzjB6abKu+hTGvO8+8+gIFDsRCU2t8fLV/P2unmt+LGFaIa4y64aX98Qksa97rgz4vMNeLQ==",
2518+      "license": "MIT",
2519+      "dependencies": {
2520+        "growly": "^1.3.0",
2521+        "is-wsl": "^2.2.0",
2522+        "semver": "^7.3.5",
2523+        "shellwords": "^0.1.1",
2524+        "uuid": "^8.3.2",
2525+        "which": "^2.0.2"
2526+      }
2527+    },
2528+    "node_modules/nth-check": {
2529+      "version": "2.1.1",
2530+      "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
2531+      "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
2532+      "license": "BSD-2-Clause",
2533+      "dependencies": {
2534+        "boolbase": "^1.0.0"
2535+      },
2536+      "funding": {
2537+        "url": "https://github.com/fb55/nth-check?sponsor=1"
2538+      }
2539+    },
2540+    "node_modules/on-exit-leak-free": {
2541+      "version": "2.1.2",
2542+      "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
2543+      "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==",
2544+      "license": "MIT",
2545+      "engines": {
2546+        "node": ">=14.0.0"
2547+      }
2548+    },
2549+    "node_modules/open": {
2550+      "version": "11.0.0",
2551+      "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz",
2552+      "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==",
2553+      "license": "MIT",
2554+      "dependencies": {
2555+        "default-browser": "^5.4.0",
2556+        "define-lazy-prop": "^3.0.0",
2557+        "is-in-ssh": "^1.0.0",
2558+        "is-inside-container": "^1.0.0",
2559+        "powershell-utils": "^0.1.0",
2560+        "wsl-utils": "^0.3.0"
2561+      },
2562+      "engines": {
2563+        "node": ">=20"
2564+      },
2565+      "funding": {
2566+        "url": "https://github.com/sponsors/sindresorhus"
2567+      }
2568+    },
2569+    "node_modules/optionator": {
2570+      "version": "0.9.4",
2571+      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
2572+      "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
2573+      "license": "MIT",
2574+      "dependencies": {
2575+        "deep-is": "^0.1.3",
2576+        "fast-levenshtein": "^2.0.6",
2577+        "levn": "^0.4.1",
2578+        "prelude-ls": "^1.2.1",
2579+        "type-check": "^0.4.0",
2580+        "word-wrap": "^1.2.5"
2581+      },
2582+      "engines": {
2583+        "node": ">= 0.8.0"
2584+      }
2585+    },
2586+    "node_modules/os-shim": {
2587+      "version": "0.1.3",
2588+      "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz",
2589+      "integrity": "sha512-jd0cvB8qQ5uVt0lvCIexBaROw1KyKm5sbulg2fWOHjETisuCzWyt+eTZKEMs8v6HwzoGs8xik26jg7eCM6pS+A==",
2590+      "engines": {
2591+        "node": ">= 0.4.0"
2592+      }
2593+    },
2594+    "node_modules/p-limit": {
2595+      "version": "3.1.0",
2596+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
2597+      "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
2598+      "license": "MIT",
2599+      "dependencies": {
2600+        "yocto-queue": "^0.1.0"
2601+      },
2602+      "engines": {
2603+        "node": ">=10"
2604+      },
2605+      "funding": {
2606+        "url": "https://github.com/sponsors/sindresorhus"
2607+      }
2608+    },
2609+    "node_modules/p-locate": {
2610+      "version": "5.0.0",
2611+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
2612+      "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
2613+      "license": "MIT",
2614+      "dependencies": {
2615+        "p-limit": "^3.0.2"
2616+      },
2617+      "engines": {
2618+        "node": ">=10"
2619+      },
2620+      "funding": {
2621+        "url": "https://github.com/sponsors/sindresorhus"
2622+      }
2623+    },
2624+    "node_modules/package-json": {
2625+      "version": "10.0.1",
2626+      "resolved": "https://registry.npmjs.org/package-json/-/package-json-10.0.1.tgz",
2627+      "integrity": "sha512-ua1L4OgXSBdsu1FPb7F3tYH0F48a6kxvod4pLUlGY9COeJAJQNX/sNH2IiEmsxw7lqYiAwrdHMjz1FctOsyDQg==",
2628+      "license": "MIT",
2629+      "dependencies": {
2630+        "ky": "^1.2.0",
2631+        "registry-auth-token": "^5.0.2",
2632+        "registry-url": "^6.0.1",
2633+        "semver": "^7.6.0"
2634+      },
2635+      "engines": {
2636+        "node": ">=18"
2637+      },
2638+      "funding": {
2639+        "url": "https://github.com/sponsors/sindresorhus"
2640+      }
2641+    },
2642+    "node_modules/pako": {
2643+      "version": "1.0.11",
2644+      "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
2645+      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
2646+      "license": "(MIT AND Zlib)"
2647+    },
2648+    "node_modules/parent-module": {
2649+      "version": "1.0.1",
2650+      "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
2651+      "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
2652+      "license": "MIT",
2653+      "dependencies": {
2654+        "callsites": "^3.0.0"
2655+      },
2656+      "engines": {
2657+        "node": ">=6"
2658+      }
2659+    },
2660+    "node_modules/parse-json": {
2661+      "version": "8.3.0",
2662+      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz",
2663+      "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==",
2664+      "license": "MIT",
2665+      "dependencies": {
2666+        "@babel/code-frame": "^7.26.2",
2667+        "index-to-position": "^1.1.0",
2668+        "type-fest": "^4.39.1"
2669+      },
2670+      "engines": {
2671+        "node": ">=18"
2672+      },
2673+      "funding": {
2674+        "url": "https://github.com/sponsors/sindresorhus"
2675+      }
2676+    },
2677+    "node_modules/parse5": {
2678+      "version": "7.3.0",
2679+      "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
2680+      "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
2681+      "license": "MIT",
2682+      "dependencies": {
2683+        "entities": "^6.0.0"
2684+      },
2685+      "funding": {
2686+        "url": "https://github.com/inikulin/parse5?sponsor=1"
2687+      }
2688+    },
2689+    "node_modules/parse5-htmlparser2-tree-adapter": {
2690+      "version": "7.1.0",
2691+      "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
2692+      "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
2693+      "license": "MIT",
2694+      "dependencies": {
2695+        "domhandler": "^5.0.3",
2696+        "parse5": "^7.0.0"
2697+      },
2698+      "funding": {
2699+        "url": "https://github.com/inikulin/parse5?sponsor=1"
2700+      }
2701+    },
2702+    "node_modules/parse5-parser-stream": {
2703+      "version": "7.1.2",
2704+      "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
2705+      "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
2706+      "license": "MIT",
2707+      "dependencies": {
2708+        "parse5": "^7.0.0"
2709+      },
2710+      "funding": {
2711+        "url": "https://github.com/inikulin/parse5?sponsor=1"
2712+      }
2713+    },
2714+    "node_modules/parse5/node_modules/entities": {
2715+      "version": "6.0.1",
2716+      "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
2717+      "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
2718+      "license": "BSD-2-Clause",
2719+      "engines": {
2720+        "node": ">=0.12"
2721+      },
2722+      "funding": {
2723+        "url": "https://github.com/fb55/entities?sponsor=1"
2724+      }
2725+    },
2726+    "node_modules/path-exists": {
2727+      "version": "4.0.0",
2728+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
2729+      "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
2730+      "license": "MIT",
2731+      "engines": {
2732+        "node": ">=8"
2733+      }
2734+    },
2735+    "node_modules/path-key": {
2736+      "version": "3.1.1",
2737+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
2738+      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
2739+      "license": "MIT",
2740+      "engines": {
2741+        "node": ">=8"
2742+      }
2743+    },
2744+    "node_modules/pend": {
2745+      "version": "1.2.0",
2746+      "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
2747+      "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
2748+      "license": "MIT"
2749+    },
2750+    "node_modules/picocolors": {
2751+      "version": "1.1.1",
2752+      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
2753+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
2754+      "license": "ISC"
2755+    },
2756+    "node_modules/pino": {
2757+      "version": "10.3.1",
2758+      "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz",
2759+      "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==",
2760+      "license": "MIT",
2761+      "dependencies": {
2762+        "@pinojs/redact": "^0.4.0",
2763+        "atomic-sleep": "^1.0.0",
2764+        "on-exit-leak-free": "^2.1.0",
2765+        "pino-abstract-transport": "^3.0.0",
2766+        "pino-std-serializers": "^7.0.0",
2767+        "process-warning": "^5.0.0",
2768+        "quick-format-unescaped": "^4.0.3",
2769+        "real-require": "^0.2.0",
2770+        "safe-stable-stringify": "^2.3.1",
2771+        "sonic-boom": "^4.0.1",
2772+        "thread-stream": "^4.0.0"
2773+      },
2774+      "bin": {
2775+        "pino": "bin.js"
2776+      }
2777+    },
2778+    "node_modules/pino-abstract-transport": {
2779+      "version": "3.0.0",
2780+      "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz",
2781+      "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==",
2782+      "license": "MIT",
2783+      "dependencies": {
2784+        "split2": "^4.0.0"
2785+      }
2786+    },
2787+    "node_modules/pino-std-serializers": {
2788+      "version": "7.1.0",
2789+      "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz",
2790+      "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==",
2791+      "license": "MIT"
2792+    },
2793+    "node_modules/powershell-utils": {
2794+      "version": "0.1.0",
2795+      "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz",
2796+      "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==",
2797+      "license": "MIT",
2798+      "engines": {
2799+        "node": ">=20"
2800+      },
2801+      "funding": {
2802+        "url": "https://github.com/sponsors/sindresorhus"
2803+      }
2804+    },
2805+    "node_modules/prelude-ls": {
2806+      "version": "1.2.1",
2807+      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
2808+      "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
2809+      "license": "MIT",
2810+      "engines": {
2811+        "node": ">= 0.8.0"
2812+      }
2813+    },
2814+    "node_modules/process-nextick-args": {
2815+      "version": "2.0.1",
2816+      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
2817+      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
2818+      "license": "MIT"
2819+    },
2820+    "node_modules/process-warning": {
2821+      "version": "5.0.0",
2822+      "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz",
2823+      "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==",
2824+      "funding": [
2825+        {
2826+          "type": "github",
2827+          "url": "https://github.com/sponsors/fastify"
2828+        },
2829+        {
2830+          "type": "opencollective",
2831+          "url": "https://opencollective.com/fastify"
2832+        }
2833+      ],
2834+      "license": "MIT"
2835+    },
2836+    "node_modules/promise-toolbox": {
2837+      "version": "0.21.0",
2838+      "resolved": "https://registry.npmjs.org/promise-toolbox/-/promise-toolbox-0.21.0.tgz",
2839+      "integrity": "sha512-NV8aTmpwrZv+Iys54sSFOBx3tuVaOBvvrft5PNppnxy9xpU/akHbaWIril22AB22zaPgrgwKdD0KsrM0ptUtpg==",
2840+      "license": "ISC",
2841+      "dependencies": {
2842+        "make-error": "^1.3.2"
2843+      },
2844+      "engines": {
2845+        "node": ">=6"
2846+      }
2847+    },
2848+    "node_modules/proto-list": {
2849+      "version": "1.2.4",
2850+      "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
2851+      "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
2852+      "license": "ISC"
2853+    },
2854+    "node_modules/punycode": {
2855+      "version": "2.3.1",
2856+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
2857+      "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
2858+      "license": "MIT",
2859+      "engines": {
2860+        "node": ">=6"
2861+      }
2862+    },
2863+    "node_modules/pupa": {
2864+      "version": "3.3.0",
2865+      "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.3.0.tgz",
2866+      "integrity": "sha512-LjgDO2zPtoXP2wJpDjZrGdojii1uqO0cnwKoIoUzkfS98HDmbeiGmYiXo3lXeFlq2xvne1QFQhwYXSUCLKtEuA==",
2867+      "license": "MIT",
2868+      "dependencies": {
2869+        "escape-goat": "^4.0.0"
2870+      },
2871+      "engines": {
2872+        "node": ">=12.20"
2873+      },
2874+      "funding": {
2875+        "url": "https://github.com/sponsors/sindresorhus"
2876+      }
2877+    },
2878+    "node_modules/quick-format-unescaped": {
2879+      "version": "4.0.4",
2880+      "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
2881+      "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==",
2882+      "license": "MIT"
2883+    },
2884+    "node_modules/rc": {
2885+      "version": "1.2.8",
2886+      "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
2887+      "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
2888+      "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
2889+      "dependencies": {
2890+        "deep-extend": "^0.6.0",
2891+        "ini": "~1.3.0",
2892+        "minimist": "^1.2.0",
2893+        "strip-json-comments": "~2.0.1"
2894+      },
2895+      "bin": {
2896+        "rc": "cli.js"
2897+      }
2898+    },
2899+    "node_modules/rc/node_modules/ini": {
2900+      "version": "1.3.8",
2901+      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
2902+      "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
2903+      "license": "ISC"
2904+    },
2905+    "node_modules/rc/node_modules/strip-json-comments": {
2906+      "version": "2.0.1",
2907+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
2908+      "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
2909+      "license": "MIT",
2910+      "engines": {
2911+        "node": ">=0.10.0"
2912+      }
2913+    },
2914+    "node_modules/readable-stream": {
2915+      "version": "2.3.8",
2916+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
2917+      "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
2918+      "license": "MIT",
2919+      "dependencies": {
2920+        "core-util-is": "~1.0.0",
2921+        "inherits": "~2.0.3",
2922+        "isarray": "~1.0.0",
2923+        "process-nextick-args": "~2.0.0",
2924+        "safe-buffer": "~5.1.1",
2925+        "string_decoder": "~1.1.1",
2926+        "util-deprecate": "~1.0.1"
2927+      }
2928+    },
2929+    "node_modules/readable-stream/node_modules/safe-buffer": {
2930+      "version": "5.1.2",
2931+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
2932+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
2933+      "license": "MIT"
2934+    },
2935+    "node_modules/real-require": {
2936+      "version": "0.2.0",
2937+      "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
2938+      "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==",
2939+      "license": "MIT",
2940+      "engines": {
2941+        "node": ">= 12.13.0"
2942+      }
2943+    },
2944+    "node_modules/registry-auth-token": {
2945+      "version": "5.1.1",
2946+      "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.1.tgz",
2947+      "integrity": "sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==",
2948+      "license": "MIT",
2949+      "dependencies": {
2950+        "@pnpm/npm-conf": "^3.0.2"
2951+      },
2952+      "engines": {
2953+        "node": ">=14"
2954+      }
2955+    },
2956+    "node_modules/registry-url": {
2957+      "version": "6.0.1",
2958+      "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz",
2959+      "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==",
2960+      "license": "MIT",
2961+      "dependencies": {
2962+        "rc": "1.2.8"
2963+      },
2964+      "engines": {
2965+        "node": ">=12"
2966+      },
2967+      "funding": {
2968+        "url": "https://github.com/sponsors/sindresorhus"
2969+      }
2970+    },
2971+    "node_modules/require-directory": {
2972+      "version": "2.1.1",
2973+      "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
2974+      "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
2975+      "license": "MIT",
2976+      "engines": {
2977+        "node": ">=0.10.0"
2978+      }
2979+    },
2980+    "node_modules/require-from-string": {
2981+      "version": "2.0.2",
2982+      "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
2983+      "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
2984+      "license": "MIT",
2985+      "engines": {
2986+        "node": ">=0.10.0"
2987+      }
2988+    },
2989+    "node_modules/resolve-from": {
2990+      "version": "4.0.0",
2991+      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
2992+      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
2993+      "license": "MIT",
2994+      "engines": {
2995+        "node": ">=4"
2996+      }
2997+    },
2998+    "node_modules/run-applescript": {
2999+      "version": "7.1.0",
3000+      "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz",
3001+      "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==",
3002+      "license": "MIT",
3003+      "engines": {
3004+        "node": ">=18"
3005+      },
3006+      "funding": {
3007+        "url": "https://github.com/sponsors/sindresorhus"
3008+      }
3009+    },
3010+    "node_modules/safe-buffer": {
3011+      "version": "5.2.1",
3012+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
3013+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
3014+      "funding": [
3015+        {
3016+          "type": "github",
3017+          "url": "https://github.com/sponsors/feross"
3018+        },
3019+        {
3020+          "type": "patreon",
3021+          "url": "https://www.patreon.com/feross"
3022+        },
3023+        {
3024+          "type": "consulting",
3025+          "url": "https://feross.org/support"
3026+        }
3027+      ],
3028+      "license": "MIT"
3029+    },
3030+    "node_modules/safe-stable-stringify": {
3031+      "version": "2.5.0",
3032+      "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
3033+      "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
3034+      "license": "MIT",
3035+      "engines": {
3036+        "node": ">=10"
3037+      }
3038+    },
3039+    "node_modules/safer-buffer": {
3040+      "version": "2.1.2",
3041+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
3042+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
3043+      "license": "MIT"
3044+    },
3045+    "node_modules/sax": {
3046+      "version": "1.6.0",
3047+      "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
3048+      "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==",
3049+      "license": "BlueOak-1.0.0",
3050+      "engines": {
3051+        "node": ">=11.0.0"
3052+      }
3053+    },
3054+    "node_modules/semver": {
3055+      "version": "7.7.4",
3056+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
3057+      "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
3058+      "license": "ISC",
3059+      "bin": {
3060+        "semver": "bin/semver.js"
3061+      },
3062+      "engines": {
3063+        "node": ">=10"
3064+      }
3065+    },
3066+    "node_modules/setimmediate": {
3067+      "version": "1.0.5",
3068+      "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
3069+      "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
3070+      "license": "MIT"
3071+    },
3072+    "node_modules/shebang-command": {
3073+      "version": "2.0.0",
3074+      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
3075+      "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
3076+      "license": "MIT",
3077+      "dependencies": {
3078+        "shebang-regex": "^3.0.0"
3079+      },
3080+      "engines": {
3081+        "node": ">=8"
3082+      }
3083+    },
3084+    "node_modules/shebang-regex": {
3085+      "version": "3.0.0",
3086+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
3087+      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
3088+      "license": "MIT",
3089+      "engines": {
3090+        "node": ">=8"
3091+      }
3092+    },
3093+    "node_modules/shell-quote": {
3094+      "version": "1.7.3",
3095+      "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz",
3096+      "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==",
3097+      "license": "MIT"
3098+    },
3099+    "node_modules/shellwords": {
3100+      "version": "0.1.1",
3101+      "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz",
3102+      "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==",
3103+      "license": "MIT"
3104+    },
3105+    "node_modules/sonic-boom": {
3106+      "version": "4.2.1",
3107+      "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz",
3108+      "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==",
3109+      "license": "MIT",
3110+      "dependencies": {
3111+        "atomic-sleep": "^1.0.0"
3112+      }
3113+    },
3114+    "node_modules/source-map": {
3115+      "version": "0.6.1",
3116+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
3117+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
3118+      "license": "BSD-3-Clause",
3119+      "engines": {
3120+        "node": ">=0.10.0"
3121+      }
3122+    },
3123+    "node_modules/source-map-support": {
3124+      "version": "0.5.21",
3125+      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
3126+      "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
3127+      "license": "MIT",
3128+      "dependencies": {
3129+        "buffer-from": "^1.0.0",
3130+        "source-map": "^0.6.0"
3131+      }
3132+    },
3133+    "node_modules/spawn-sync": {
3134+      "version": "1.0.15",
3135+      "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz",
3136+      "integrity": "sha512-9DWBgrgYZzNghseho0JOuh+5fg9u6QWhAWa51QC7+U5rCheZ/j1DrEZnyE0RBBRqZ9uEXGPgSSM0nky6burpVw==",
3137+      "hasInstallScript": true,
3138+      "license": "MIT",
3139+      "dependencies": {
3140+        "concat-stream": "^1.4.7",
3141+        "os-shim": "^0.1.2"
3142+      }
3143+    },
3144+    "node_modules/split": {
3145+      "version": "1.0.1",
3146+      "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz",
3147+      "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==",
3148+      "license": "MIT",
3149+      "dependencies": {
3150+        "through": "2"
3151+      },
3152+      "engines": {
3153+        "node": "*"
3154+      }
3155+    },
3156+    "node_modules/split2": {
3157+      "version": "4.2.0",
3158+      "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
3159+      "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
3160+      "license": "ISC",
3161+      "engines": {
3162+        "node": ">= 10.x"
3163+      }
3164+    },
3165+    "node_modules/string_decoder": {
3166+      "version": "1.1.1",
3167+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
3168+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
3169+      "license": "MIT",
3170+      "dependencies": {
3171+        "safe-buffer": "~5.1.0"
3172+      }
3173+    },
3174+    "node_modules/string_decoder/node_modules/safe-buffer": {
3175+      "version": "5.1.2",
3176+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
3177+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
3178+      "license": "MIT"
3179+    },
3180+    "node_modules/string-width": {
3181+      "version": "7.2.0",
3182+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
3183+      "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
3184+      "license": "MIT",
3185+      "dependencies": {
3186+        "emoji-regex": "^10.3.0",
3187+        "get-east-asian-width": "^1.0.0",
3188+        "strip-ansi": "^7.1.0"
3189+      },
3190+      "engines": {
3191+        "node": ">=18"
3192+      },
3193+      "funding": {
3194+        "url": "https://github.com/sponsors/sindresorhus"
3195+      }
3196+    },
3197+    "node_modules/string-width/node_modules/ansi-regex": {
3198+      "version": "6.2.2",
3199+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
3200+      "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
3201+      "license": "MIT",
3202+      "engines": {
3203+        "node": ">=12"
3204+      },
3205+      "funding": {
3206+        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
3207+      }
3208+    },
3209+    "node_modules/string-width/node_modules/strip-ansi": {
3210+      "version": "7.2.0",
3211+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
3212+      "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
3213+      "license": "MIT",
3214+      "dependencies": {
3215+        "ansi-regex": "^6.2.2"
3216+      },
3217+      "engines": {
3218+        "node": ">=12"
3219+      },
3220+      "funding": {
3221+        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
3222+      }
3223+    },
3224+    "node_modules/strip-ansi": {
3225+      "version": "6.0.1",
3226+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
3227+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
3228+      "license": "MIT",
3229+      "dependencies": {
3230+        "ansi-regex": "^5.0.1"
3231+      },
3232+      "engines": {
3233+        "node": ">=8"
3234+      }
3235+    },
3236+    "node_modules/strip-bom": {
3237+      "version": "5.0.0",
3238+      "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-5.0.0.tgz",
3239+      "integrity": "sha512-p+byADHF7SzEcVnLvc/r3uognM1hUhObuHXxJcgLCfD194XAkaLbjq3Wzb0N5G2tgIjH0dgT708Z51QxMeu60A==",
3240+      "license": "MIT",
3241+      "engines": {
3242+        "node": ">=12"
3243+      },
3244+      "funding": {
3245+        "url": "https://github.com/sponsors/sindresorhus"
3246+      }
3247+    },
3248+    "node_modules/strip-bom-buf": {
3249+      "version": "2.0.0",
3250+      "resolved": "https://registry.npmjs.org/strip-bom-buf/-/strip-bom-buf-2.0.0.tgz",
3251+      "integrity": "sha512-gLFNHucd6gzb8jMsl5QmZ3QgnUJmp7qn4uUSHNwEXumAp7YizoGYw19ZUVfuq4aBOQUtyn2k8X/CwzWB73W2lQ==",
3252+      "license": "MIT",
3253+      "dependencies": {
3254+        "is-utf8": "^0.2.1"
3255+      },
3256+      "engines": {
3257+        "node": ">=8"
3258+      }
3259+    },
3260+    "node_modules/strip-bom-stream": {
3261+      "version": "4.0.0",
3262+      "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-4.0.0.tgz",
3263+      "integrity": "sha512-0ApK3iAkHv6WbgLICw/J4nhwHeDZsBxIIsOD+gHgZICL6SeJ0S9f/WZqemka9cjkTyMN5geId6e8U5WGFAn3cQ==",
3264+      "license": "MIT",
3265+      "dependencies": {
3266+        "first-chunk-stream": "^3.0.0",
3267+        "strip-bom-buf": "^2.0.0"
3268+      },
3269+      "engines": {
3270+        "node": ">=8"
3271+      }
3272+    },
3273+    "node_modules/strip-json-comments": {
3274+      "version": "5.0.3",
3275+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz",
3276+      "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==",
3277+      "license": "MIT",
3278+      "engines": {
3279+        "node": ">=14.16"
3280+      },
3281+      "funding": {
3282+        "url": "https://github.com/sponsors/sindresorhus"
3283+      }
3284+    },
3285+    "node_modules/stubborn-fs": {
3286+      "version": "2.0.0",
3287+      "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-2.0.0.tgz",
3288+      "integrity": "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==",
3289+      "license": "MIT",
3290+      "dependencies": {
3291+        "stubborn-utils": "^1.0.1"
3292+      }
3293+    },
3294+    "node_modules/stubborn-utils": {
3295+      "version": "1.0.2",
3296+      "resolved": "https://registry.npmjs.org/stubborn-utils/-/stubborn-utils-1.0.2.tgz",
3297+      "integrity": "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==",
3298+      "license": "MIT"
3299+    },
3300+    "node_modules/supports-color": {
3301+      "version": "7.2.0",
3302+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
3303+      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
3304+      "license": "MIT",
3305+      "dependencies": {
3306+        "has-flag": "^4.0.0"
3307+      },
3308+      "engines": {
3309+        "node": ">=8"
3310+      }
3311+    },
3312+    "node_modules/thread-stream": {
3313+      "version": "4.0.0",
3314+      "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz",
3315+      "integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==",
3316+      "license": "MIT",
3317+      "dependencies": {
3318+        "real-require": "^0.2.0"
3319+      },
3320+      "engines": {
3321+        "node": ">=20"
3322+      }
3323+    },
3324+    "node_modules/through": {
3325+      "version": "2.3.8",
3326+      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
3327+      "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
3328+      "license": "MIT"
3329+    },
3330+    "node_modules/tmp": {
3331+      "version": "0.2.5",
3332+      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
3333+      "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==",
3334+      "license": "MIT",
3335+      "engines": {
3336+        "node": ">=14.14"
3337+      }
3338+    },
3339+    "node_modules/type-check": {
3340+      "version": "0.4.0",
3341+      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
3342+      "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
3343+      "license": "MIT",
3344+      "dependencies": {
3345+        "prelude-ls": "^1.2.1"
3346+      },
3347+      "engines": {
3348+        "node": ">= 0.8.0"
3349+      }
3350+    },
3351+    "node_modules/type-fest": {
3352+      "version": "4.41.0",
3353+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
3354+      "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
3355+      "license": "(MIT OR CC0-1.0)",
3356+      "engines": {
3357+        "node": ">=16"
3358+      },
3359+      "funding": {
3360+        "url": "https://github.com/sponsors/sindresorhus"
3361+      }
3362+    },
3363+    "node_modules/typedarray": {
3364+      "version": "0.0.6",
3365+      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
3366+      "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
3367+      "license": "MIT"
3368+    },
3369+    "node_modules/undici": {
3370+      "version": "7.24.4",
3371+      "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.4.tgz",
3372+      "integrity": "sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==",
3373+      "license": "MIT",
3374+      "engines": {
3375+        "node": ">=20.18.1"
3376+      }
3377+    },
3378+    "node_modules/undici-types": {
3379+      "version": "7.18.2",
3380+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
3381+      "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
3382+      "license": "MIT"
3383+    },
3384+    "node_modules/universalify": {
3385+      "version": "2.0.1",
3386+      "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
3387+      "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
3388+      "license": "MIT",
3389+      "engines": {
3390+        "node": ">= 10.0.0"
3391+      }
3392+    },
3393+    "node_modules/upath": {
3394+      "version": "2.0.1",
3395+      "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz",
3396+      "integrity": "sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==",
3397+      "license": "MIT",
3398+      "engines": {
3399+        "node": ">=4",
3400+        "yarn": "*"
3401+      }
3402+    },
3403+    "node_modules/update-notifier": {
3404+      "version": "7.3.1",
3405+      "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-7.3.1.tgz",
3406+      "integrity": "sha512-+dwUY4L35XFYEzE+OAL3sarJdUioVovq+8f7lcIJ7wnmnYQV5UD1Y/lcwaMSyaQ6Bj3JMj1XSTjZbNLHn/19yA==",
3407+      "license": "BSD-2-Clause",
3408+      "dependencies": {
3409+        "boxen": "^8.0.1",
3410+        "chalk": "^5.3.0",
3411+        "configstore": "^7.0.0",
3412+        "is-in-ci": "^1.0.0",
3413+        "is-installed-globally": "^1.0.0",
3414+        "is-npm": "^6.0.0",
3415+        "latest-version": "^9.0.0",
3416+        "pupa": "^3.1.0",
3417+        "semver": "^7.6.3",
3418+        "xdg-basedir": "^5.1.0"
3419+      },
3420+      "engines": {
3421+        "node": ">=18"
3422+      },
3423+      "funding": {
3424+        "url": "https://github.com/yeoman/update-notifier?sponsor=1"
3425+      }
3426+    },
3427+    "node_modules/update-notifier/node_modules/chalk": {
3428+      "version": "5.6.2",
3429+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
3430+      "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
3431+      "license": "MIT",
3432+      "engines": {
3433+        "node": "^12.17.0 || ^14.13 || >=16.0.0"
3434+      },
3435+      "funding": {
3436+        "url": "https://github.com/chalk/chalk?sponsor=1"
3437+      }
3438+    },
3439+    "node_modules/uri-js": {
3440+      "version": "4.4.1",
3441+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
3442+      "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
3443+      "license": "BSD-2-Clause",
3444+      "dependencies": {
3445+        "punycode": "^2.1.0"
3446+      }
3447+    },
3448+    "node_modules/util-deprecate": {
3449+      "version": "1.0.2",
3450+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
3451+      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
3452+      "license": "MIT"
3453+    },
3454+    "node_modules/uuid": {
3455+      "version": "8.3.2",
3456+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
3457+      "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
3458+      "license": "MIT",
3459+      "bin": {
3460+        "uuid": "dist/bin/uuid"
3461+      }
3462+    },
3463+    "node_modules/watchpack": {
3464+      "version": "2.5.1",
3465+      "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz",
3466+      "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==",
3467+      "license": "MIT",
3468+      "dependencies": {
3469+        "glob-to-regexp": "^0.4.1",
3470+        "graceful-fs": "^4.1.2"
3471+      },
3472+      "engines": {
3473+        "node": ">=10.13.0"
3474+      }
3475+    },
3476+    "node_modules/wcwidth": {
3477+      "version": "1.0.1",
3478+      "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
3479+      "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
3480+      "license": "MIT",
3481+      "dependencies": {
3482+        "defaults": "^1.0.3"
3483+      }
3484+    },
3485+    "node_modules/web-ext": {
3486+      "version": "10.0.0",
3487+      "resolved": "https://registry.npmjs.org/web-ext/-/web-ext-10.0.0.tgz",
3488+      "integrity": "sha512-Cdr7GELRVOXGdV8NqZAPa9oImXWUMDbzYff+UBx5F6mz4zfIrnF3+iYUsvBfG9VJ/VsqmXKdpt1gBsXbH8Z75A==",
3489+      "license": "MPL-2.0",
3490+      "dependencies": {
3491+        "@babel/runtime": "7.28.6",
3492+        "@devicefarmer/adbkit": "3.3.8",
3493+        "addons-linter": "10.1.0",
3494+        "camelcase": "8.0.0",
3495+        "chrome-launcher": "1.2.0",
3496+        "debounce": "1.2.1",
3497+        "decamelize": "6.0.1",
3498+        "es6-error": "4.1.1",
3499+        "firefox-profile": "4.7.0",
3500+        "fx-runner": "1.4.0",
3501+        "https-proxy-agent": "^7.0.0",
3502+        "jose": "5.9.6",
3503+        "jszip": "3.10.1",
3504+        "multimatch": "6.0.0",
3505+        "node-notifier": "10.0.1",
3506+        "open": "11.0.0",
3507+        "parse-json": "8.3.0",
3508+        "pino": "10.3.1",
3509+        "promise-toolbox": "0.21.0",
3510+        "source-map-support": "0.5.21",
3511+        "strip-bom": "5.0.0",
3512+        "strip-json-comments": "5.0.3",
3513+        "tmp": "0.2.5",
3514+        "update-notifier": "7.3.1",
3515+        "watchpack": "2.5.1",
3516+        "yargs": "17.7.2",
3517+        "zip-dir": "2.0.0"
3518+      },
3519+      "bin": {
3520+        "web-ext": "bin/web-ext.js"
3521+      },
3522+      "engines": {
3523+        "node": ">=20.0.0",
3524+        "npm": ">=8.0.0"
3525+      }
3526+    },
3527+    "node_modules/whatwg-encoding": {
3528+      "version": "3.1.1",
3529+      "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
3530+      "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
3531+      "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
3532+      "license": "MIT",
3533+      "dependencies": {
3534+        "iconv-lite": "0.6.3"
3535+      },
3536+      "engines": {
3537+        "node": ">=18"
3538+      }
3539+    },
3540+    "node_modules/whatwg-mimetype": {
3541+      "version": "4.0.0",
3542+      "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
3543+      "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
3544+      "license": "MIT",
3545+      "engines": {
3546+        "node": ">=18"
3547+      }
3548+    },
3549+    "node_modules/when": {
3550+      "version": "3.7.7",
3551+      "resolved": "https://registry.npmjs.org/when/-/when-3.7.7.tgz",
3552+      "integrity": "sha512-9lFZp/KHoqH6bPKjbWqa+3Dg/K/r2v0X/3/G2x4DBGchVS2QX2VXL3cZV994WQVnTM1/PD71Az25nAzryEUugw==",
3553+      "license": "MIT"
3554+    },
3555+    "node_modules/when-exit": {
3556+      "version": "2.1.5",
3557+      "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.5.tgz",
3558+      "integrity": "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==",
3559+      "license": "MIT"
3560+    },
3561+    "node_modules/which": {
3562+      "version": "2.0.2",
3563+      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
3564+      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
3565+      "license": "ISC",
3566+      "dependencies": {
3567+        "isexe": "^2.0.0"
3568+      },
3569+      "bin": {
3570+        "node-which": "bin/node-which"
3571+      },
3572+      "engines": {
3573+        "node": ">= 8"
3574+      }
3575+    },
3576+    "node_modules/widest-line": {
3577+      "version": "5.0.0",
3578+      "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz",
3579+      "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==",
3580+      "license": "MIT",
3581+      "dependencies": {
3582+        "string-width": "^7.0.0"
3583+      },
3584+      "engines": {
3585+        "node": ">=18"
3586+      },
3587+      "funding": {
3588+        "url": "https://github.com/sponsors/sindresorhus"
3589+      }
3590+    },
3591+    "node_modules/winreg": {
3592+      "version": "0.0.12",
3593+      "resolved": "https://registry.npmjs.org/winreg/-/winreg-0.0.12.tgz",
3594+      "integrity": "sha512-typ/+JRmi7RqP1NanzFULK36vczznSNN8kWVA9vIqXyv8GhghUlwhGp1Xj3Nms1FsPcNnsQrJOR10N58/nQ9hQ==",
3595+      "license": "BSD"
3596+    },
3597+    "node_modules/word-wrap": {
3598+      "version": "1.2.5",
3599+      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
3600+      "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
3601+      "license": "MIT",
3602+      "engines": {
3603+        "node": ">=0.10.0"
3604+      }
3605+    },
3606+    "node_modules/wrap-ansi": {
3607+      "version": "9.0.2",
3608+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
3609+      "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
3610+      "license": "MIT",
3611+      "dependencies": {
3612+        "ansi-styles": "^6.2.1",
3613+        "string-width": "^7.0.0",
3614+        "strip-ansi": "^7.1.0"
3615+      },
3616+      "engines": {
3617+        "node": ">=18"
3618+      },
3619+      "funding": {
3620+        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
3621+      }
3622+    },
3623+    "node_modules/wrap-ansi/node_modules/ansi-regex": {
3624+      "version": "6.2.2",
3625+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
3626+      "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
3627+      "license": "MIT",
3628+      "engines": {
3629+        "node": ">=12"
3630+      },
3631+      "funding": {
3632+        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
3633+      }
3634+    },
3635+    "node_modules/wrap-ansi/node_modules/ansi-styles": {
3636+      "version": "6.2.3",
3637+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
3638+      "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
3639+      "license": "MIT",
3640+      "engines": {
3641+        "node": ">=12"
3642+      },
3643+      "funding": {
3644+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
3645+      }
3646+    },
3647+    "node_modules/wrap-ansi/node_modules/strip-ansi": {
3648+      "version": "7.2.0",
3649+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
3650+      "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
3651+      "license": "MIT",
3652+      "dependencies": {
3653+        "ansi-regex": "^6.2.2"
3654+      },
3655+      "engines": {
3656+        "node": ">=12"
3657+      },
3658+      "funding": {
3659+        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
3660+      }
3661+    },
3662+    "node_modules/wsl-utils": {
3663+      "version": "0.3.1",
3664+      "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz",
3665+      "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==",
3666+      "license": "MIT",
3667+      "dependencies": {
3668+        "is-wsl": "^3.1.0",
3669+        "powershell-utils": "^0.1.0"
3670+      },
3671+      "engines": {
3672+        "node": ">=20"
3673+      },
3674+      "funding": {
3675+        "url": "https://github.com/sponsors/sindresorhus"
3676+      }
3677+    },
3678+    "node_modules/wsl-utils/node_modules/is-wsl": {
3679+      "version": "3.1.1",
3680+      "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz",
3681+      "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==",
3682+      "license": "MIT",
3683+      "dependencies": {
3684+        "is-inside-container": "^1.0.0"
3685+      },
3686+      "engines": {
3687+        "node": ">=16"
3688+      },
3689+      "funding": {
3690+        "url": "https://github.com/sponsors/sindresorhus"
3691+      }
3692+    },
3693+    "node_modules/xdg-basedir": {
3694+      "version": "5.1.0",
3695+      "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz",
3696+      "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==",
3697+      "license": "MIT",
3698+      "engines": {
3699+        "node": ">=12"
3700+      },
3701+      "funding": {
3702+        "url": "https://github.com/sponsors/sindresorhus"
3703+      }
3704+    },
3705+    "node_modules/xml2js": {
3706+      "version": "0.6.2",
3707+      "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
3708+      "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
3709+      "license": "MIT",
3710+      "dependencies": {
3711+        "sax": ">=0.6.0",
3712+        "xmlbuilder": "~11.0.0"
3713+      },
3714+      "engines": {
3715+        "node": ">=4.0.0"
3716+      }
3717+    },
3718+    "node_modules/xmlbuilder": {
3719+      "version": "11.0.1",
3720+      "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
3721+      "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
3722+      "license": "MIT",
3723+      "engines": {
3724+        "node": ">=4.0"
3725+      }
3726+    },
3727+    "node_modules/y18n": {
3728+      "version": "5.0.8",
3729+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
3730+      "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
3731+      "license": "ISC",
3732+      "engines": {
3733+        "node": ">=10"
3734+      }
3735+    },
3736+    "node_modules/yargs": {
3737+      "version": "17.7.2",
3738+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
3739+      "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
3740+      "license": "MIT",
3741+      "dependencies": {
3742+        "cliui": "^8.0.1",
3743+        "escalade": "^3.1.1",
3744+        "get-caller-file": "^2.0.5",
3745+        "require-directory": "^2.1.1",
3746+        "string-width": "^4.2.3",
3747+        "y18n": "^5.0.5",
3748+        "yargs-parser": "^21.1.1"
3749+      },
3750+      "engines": {
3751+        "node": ">=12"
3752+      }
3753+    },
3754+    "node_modules/yargs-parser": {
3755+      "version": "21.1.1",
3756+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
3757+      "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
3758+      "license": "ISC",
3759+      "engines": {
3760+        "node": ">=12"
3761+      }
3762+    },
3763+    "node_modules/yargs/node_modules/emoji-regex": {
3764+      "version": "8.0.0",
3765+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
3766+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
3767+      "license": "MIT"
3768+    },
3769+    "node_modules/yargs/node_modules/string-width": {
3770+      "version": "4.2.3",
3771+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
3772+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
3773+      "license": "MIT",
3774+      "dependencies": {
3775+        "emoji-regex": "^8.0.0",
3776+        "is-fullwidth-code-point": "^3.0.0",
3777+        "strip-ansi": "^6.0.1"
3778+      },
3779+      "engines": {
3780+        "node": ">=8"
3781+      }
3782+    },
3783+    "node_modules/yauzl": {
3784+      "version": "3.2.1",
3785+      "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.2.1.tgz",
3786+      "integrity": "sha512-k1isifdbpNSFEHFJ1ZY4YDewv0IH9FR61lDetaRMD3j2ae3bIXGV+7c+LHCqtQGofSd8PIyV4X6+dHMAnSr60A==",
3787+      "license": "MIT",
3788+      "dependencies": {
3789+        "buffer-crc32": "~0.2.3",
3790+        "pend": "~1.2.0"
3791+      },
3792+      "engines": {
3793+        "node": ">=12"
3794+      }
3795+    },
3796+    "node_modules/yocto-queue": {
3797+      "version": "0.1.0",
3798+      "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
3799+      "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
3800+      "license": "MIT",
3801+      "engines": {
3802+        "node": ">=10"
3803+      },
3804+      "funding": {
3805+        "url": "https://github.com/sponsors/sindresorhus"
3806+      }
3807+    },
3808+    "node_modules/zip-dir": {
3809+      "version": "2.0.0",
3810+      "resolved": "https://registry.npmjs.org/zip-dir/-/zip-dir-2.0.0.tgz",
3811+      "integrity": "sha512-uhlsJZWz26FLYXOD6WVuq+fIcZ3aBPGo/cFdiLlv3KNwpa52IF3ISV8fLhQLiqVu5No3VhlqlgthN6gehil1Dg==",
3812+      "license": "MIT",
3813+      "dependencies": {
3814+        "async": "^3.2.0",
3815+        "jszip": "^3.2.2"
3816+      }
3817+    }
3818+  }
3819+}
A plugins/baa-firefox/package.json
+10, -0
 1@@ -0,0 +1,10 @@
 2+{
 3+  "name": "baa-firefox",
 4+  "private": true,
 5+  "scripts": {
 6+    "run:persistent": "bash ./scripts/run-persistent.sh"
 7+  },
 8+  "dependencies": {
 9+    "web-ext": "^10.0.0"
10+  }
11+}
A plugins/baa-firefox/page-interceptor.js
+555, -0
  1@@ -0,0 +1,555 @@
  2+(function () {
  3+  if (window.__baaFirefoxIntercepted__) return;
  4+  window.__baaFirefoxIntercepted__ = true;
  5+
  6+  const BODY_LIMIT = 5000;
  7+  const originalFetch = window.fetch;
  8+  const originalXhrOpen = XMLHttpRequest.prototype.open;
  9+  const originalXhrSend = XMLHttpRequest.prototype.send;
 10+  const originalXhrSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
 11+
 12+  function hostnameMatches(hostname, hosts) {
 13+    return hosts.some((host) => hostname === host || hostname.endsWith(`.${host}`));
 14+  }
 15+
 16+  function isLikelyStaticPath(pathname = "") {
 17+    const lower = pathname.toLowerCase();
 18+    return lower.startsWith("/_next/")
 19+      || lower.startsWith("/assets/")
 20+      || lower.startsWith("/static/")
 21+      || lower.startsWith("/images/")
 22+      || lower.startsWith("/fonts/")
 23+      || lower.startsWith("/favicon")
 24+      || /\.(?:js|mjs|css|map|png|jpe?g|gif|svg|webp|ico|woff2?|ttf|otf|mp4|webm|txt)$/i.test(lower);
 25+  }
 26+
 27+  const PLATFORM_RULES = [
 28+    {
 29+      platform: "claude",
 30+      pageHosts: ["claude.ai"],
 31+      requestHosts: ["claude.ai"],
 32+      matchesPageHost(hostname) {
 33+        return hostnameMatches(hostname, this.pageHosts);
 34+      },
 35+      matchesRequestHost(hostname) {
 36+        return hostnameMatches(hostname, this.requestHosts);
 37+      },
 38+      shouldTrack(pathname) {
 39+        return pathname.includes("/api/");
 40+      },
 41+      isSse(pathname, contentType) {
 42+        return contentType.includes("text/event-stream") || pathname.endsWith("/completion");
 43+      }
 44+    },
 45+    {
 46+      platform: "chatgpt",
 47+      pageHosts: ["chatgpt.com", "chat.openai.com"],
 48+      requestHosts: ["chatgpt.com", "chat.openai.com", "openai.com", "oaiusercontent.com"],
 49+      matchesPageHost(hostname) {
 50+        return hostnameMatches(hostname, this.pageHosts);
 51+      },
 52+      matchesRequestHost(hostname) {
 53+        return hostnameMatches(hostname, this.requestHosts);
 54+      },
 55+      shouldTrack(pathname) {
 56+        const lower = pathname.toLowerCase();
 57+        return pathname.includes("/backend-api/")
 58+          || pathname.includes("/backend-anon/")
 59+          || pathname.includes("/public-api/")
 60+          || lower.includes("/conversation")
 61+          || lower.includes("/models")
 62+          || lower.includes("/files")
 63+          || (!isLikelyStaticPath(pathname) && (lower.includes("/api/") || lower.includes("/backend")));
 64+      },
 65+      isSse(pathname, contentType) {
 66+        const lower = pathname.toLowerCase();
 67+        return contentType.includes("text/event-stream")
 68+          || lower.includes("/conversation")
 69+          || lower.includes("/backend-api/conversation");
 70+      }
 71+    },
 72+    {
 73+      platform: "gemini",
 74+      pageHosts: ["gemini.google.com"],
 75+      requestHosts: ["gemini.google.com"],
 76+      matchesPageHost(hostname) {
 77+        return hostnameMatches(hostname, this.pageHosts);
 78+      },
 79+      matchesRequestHost(hostname) {
 80+        return hostnameMatches(hostname, this.requestHosts);
 81+      },
 82+      shouldTrack(pathname) {
 83+        const lower = pathname.toLowerCase();
 84+        return pathname.startsWith("/_/")
 85+          || pathname.includes("/api/")
 86+          || lower.includes("bardchatui")
 87+          || lower.includes("streamgenerate")
 88+          || lower.includes("generatecontent")
 89+          || lower.includes("modelresponse")
 90+          || (!isLikelyStaticPath(pathname) && lower.includes("assistant"));
 91+      },
 92+      isSse(pathname, contentType) {
 93+        return contentType.includes("text/event-stream") || pathname.toLowerCase().includes("streamgenerate");
 94+      }
 95+    }
 96+  ];
 97+
 98+  function findPageRule(hostname) {
 99+    return PLATFORM_RULES.find((rule) => rule.matchesPageHost(hostname)) || null;
100+  }
101+
102+  function findRequestRule(hostname) {
103+    return PLATFORM_RULES.find((rule) => rule.matchesRequestHost(hostname)) || null;
104+  }
105+
106+  const pageRule = findPageRule(location.hostname);
107+  if (!pageRule) return;
108+
109+  function getRequestContext(url) {
110+    try {
111+      const parsed = new URL(url, location.href);
112+      const rule = findRequestRule(parsed.hostname);
113+      if (!rule || rule.platform !== pageRule.platform) return null;
114+      return { parsed, rule };
115+    } catch (_) {
116+      return null;
117+    }
118+  }
119+
120+  function shouldTrack(url) {
121+    const context = getRequestContext(url);
122+    return context ? context.rule.shouldTrack(context.parsed.pathname) : false;
123+  }
124+
125+  function readHeaders(headersLike) {
126+    const out = {};
127+    try {
128+      const headers = new Headers(headersLike || {});
129+      headers.forEach((value, key) => {
130+        out[key] = value;
131+      });
132+    } catch (_) {}
133+    return out;
134+  }
135+
136+  function readRawHeaders(rawHeaders) {
137+    const out = {};
138+    for (const line of String(rawHeaders || "").split(/\r?\n/)) {
139+      const index = line.indexOf(":");
140+      if (index <= 0) continue;
141+      const name = line.slice(0, index).trim().toLowerCase();
142+      const value = line.slice(index + 1).trim();
143+      if (name) out[name] = value;
144+    }
145+    return out;
146+  }
147+
148+  function trim(text) {
149+    if (text == null) return null;
150+    return text.length > BODY_LIMIT ? text.slice(0, BODY_LIMIT) : text;
151+  }
152+
153+  function emit(type, detail, rule = pageRule) {
154+    window.dispatchEvent(new CustomEvent(type, {
155+      detail: {
156+        platform: rule.platform,
157+        ...detail
158+      }
159+    }));
160+  }
161+
162+  function emitNet(detail, rule = pageRule) {
163+    emit("__baa_net__", detail, rule);
164+  }
165+
166+  function emitSse(detail, rule = pageRule) {
167+    emit("__baa_sse__", detail, rule);
168+  }
169+
170+  function isForbiddenProxyHeader(name) {
171+    const lower = String(name || "").toLowerCase();
172+    return lower === "accept-encoding"
173+      || lower === "connection"
174+      || lower === "content-length"
175+      || lower === "cookie"
176+      || lower === "host"
177+      || lower === "origin"
178+      || lower === "referer"
179+      || lower === "user-agent"
180+      || lower.startsWith("sec-");
181+  }
182+
183+  emit("__baa_ready__", {
184+    platform: pageRule.platform,
185+    url: location.href,
186+    source: "page-interceptor"
187+  }, pageRule);
188+
189+  function trimBodyValue(body) {
190+    try {
191+      if (body == null) return null;
192+      if (typeof body === "string") return trim(body);
193+      if (body instanceof URLSearchParams) return trim(body.toString());
194+      if (body instanceof FormData) {
195+        const pairs = [];
196+        for (const [key, value] of body.entries()) {
197+          pairs.push([key, typeof value === "string" ? value : `[${value?.constructor?.name || "binary"}]`]);
198+        }
199+        return trim(JSON.stringify(pairs));
200+      }
201+      if (body instanceof Blob) return `[blob ${body.type || "application/octet-stream"} ${body.size}]`;
202+      if (body instanceof ArrayBuffer) return `[arraybuffer ${body.byteLength}]`;
203+      if (ArrayBuffer.isView(body)) return `[typedarray ${body.byteLength}]`;
204+      return trim(JSON.stringify(body));
205+    } catch (_) {
206+      return null;
207+    }
208+  }
209+
210+  async function readRequestBody(input, init) {
211+    try {
212+      if (init && Object.prototype.hasOwnProperty.call(init, "body")) {
213+        return trimBodyValue(init.body);
214+      }
215+      if (input instanceof Request && !input.bodyUsed) {
216+        return trim(await input.clone().text());
217+      }
218+    } catch (_) {}
219+    return null;
220+  }
221+
222+  async function readResponseText(response) {
223+    try {
224+      return trim(await response.clone().text());
225+    } catch (_) {
226+      return null;
227+    }
228+  }
229+
230+  async function streamSse(url, method, requestBody, response, startedAt, rule) {
231+    try {
232+      const clone = response.clone();
233+      if (!clone.body) return;
234+
235+      const reader = clone.body.getReader();
236+      const decoder = new TextDecoder();
237+      let buffer = "";
238+
239+      while (true) {
240+        const { done, value } = await reader.read();
241+        if (done) break;
242+        buffer += decoder.decode(value, { stream: true });
243+
244+        const chunks = buffer.split("\n\n");
245+        buffer = chunks.pop() || "";
246+
247+        for (const chunk of chunks) {
248+          if (!chunk.trim()) continue;
249+          emitSse({
250+            url,
251+            method,
252+            reqBody: requestBody,
253+            chunk,
254+            ts: Date.now()
255+          }, rule);
256+        }
257+      }
258+
259+      if (buffer.trim()) {
260+        emitSse({
261+          url,
262+          method,
263+          reqBody: requestBody,
264+          chunk: buffer,
265+          ts: Date.now()
266+        }, rule);
267+      }
268+
269+      emitSse({
270+        url,
271+        method,
272+        reqBody: requestBody,
273+        done: true,
274+        ts: Date.now(),
275+        duration: Date.now() - startedAt
276+      }, rule);
277+    } catch (error) {
278+      emitSse({
279+        url,
280+        method,
281+        reqBody: requestBody,
282+        error: error.message,
283+        ts: Date.now()
284+      }, rule);
285+    }
286+  }
287+
288+  window.addEventListener("__baa_proxy_request__", async (event) => {
289+    let detail = event.detail || {};
290+    if (typeof detail === "string") {
291+      try {
292+        detail = JSON.parse(detail);
293+      } catch (_) {
294+        detail = {};
295+      }
296+    }
297+
298+    const id = detail.id;
299+    const method = String(detail.method || "GET").toUpperCase();
300+    const rawPath = detail.path || detail.url || location.href;
301+
302+    if (!id) return;
303+
304+    try {
305+      const url = new URL(rawPath, location.origin).href;
306+      const context = getRequestContext(url);
307+      const headers = new Headers();
308+      const startedAt = Date.now();
309+
310+      for (const [name, value] of Object.entries(detail.headers || {})) {
311+        if (!name || value == null || value === "") continue;
312+        if (isForbiddenProxyHeader(name)) continue;
313+        headers.set(String(name).toLowerCase(), String(value));
314+      }
315+
316+      let body = null;
317+      if (method !== "GET" && method !== "HEAD" && Object.prototype.hasOwnProperty.call(detail, "body")) {
318+        if (typeof detail.body === "string") {
319+          body = detail.body;
320+        } else if (detail.body != null) {
321+          if (!headers.has("content-type")) headers.set("content-type", "application/json");
322+          body = JSON.stringify(detail.body);
323+        }
324+      }
325+
326+      const response = await originalFetch.call(window, url, {
327+        method,
328+        headers,
329+        body,
330+        credentials: "include"
331+      });
332+
333+      const responseBody = await response.text();
334+      const resHeaders = readHeaders(response.headers);
335+      const contentType = response.headers.get("content-type") || "";
336+      const isSse = context ? context.rule.isSse(context.parsed.pathname, contentType) : false;
337+      const reqHeaders = readHeaders(headers);
338+      const reqBody = typeof body === "string" ? trim(body) : null;
339+      const trimmedResponseBody = trim(responseBody);
340+
341+      emitNet({
342+        url,
343+        method,
344+        reqHeaders,
345+        reqBody,
346+        status: response.status,
347+        resHeaders,
348+        resBody: isSse && pageRule.platform !== "gemini" ? null : trimmedResponseBody,
349+        duration: Date.now() - startedAt,
350+        sse: isSse,
351+        source: "proxy"
352+      }, pageRule);
353+
354+      if (isSse && trimmedResponseBody) {
355+        emitSse({
356+          url,
357+          method,
358+          reqBody,
359+          chunk: trimmedResponseBody,
360+          ts: Date.now()
361+        }, pageRule);
362+        emitSse({
363+          url,
364+          method,
365+          reqBody,
366+          done: true,
367+          ts: Date.now(),
368+          duration: Date.now() - startedAt
369+        }, pageRule);
370+      }
371+
372+      emit("__baa_proxy_response__", {
373+        id,
374+        platform: pageRule.platform,
375+        url,
376+        method,
377+        ok: response.ok,
378+        status: response.status,
379+        body: responseBody
380+      }, pageRule);
381+    } catch (error) {
382+      emitNet({
383+        url: rawPath,
384+        method,
385+        reqHeaders: readHeaders(detail.headers || {}),
386+        reqBody: typeof detail.body === "string" ? trim(detail.body) : trimBodyValue(detail.body),
387+        error: error.message,
388+        source: "proxy"
389+      }, pageRule);
390+      emit("__baa_proxy_response__", {
391+        id,
392+        platform: pageRule.platform,
393+        url: rawPath,
394+        method,
395+        ok: false,
396+        error: error.message
397+      }, pageRule);
398+    }
399+  });
400+
401+  window.fetch = async function patchedFetch(input, init) {
402+    const url = input instanceof Request ? input.url : String(input);
403+    const context = getRequestContext(url);
404+    if (!context || !context.rule.shouldTrack(context.parsed.pathname)) {
405+      return originalFetch.apply(this, arguments);
406+    }
407+
408+    const method = ((init && init.method) || (input instanceof Request ? input.method : "GET")).toUpperCase();
409+    const startedAt = Date.now();
410+    const reqHeaders = readHeaders(init && init.headers ? init.headers : (input instanceof Request ? input.headers : null));
411+    const reqBody = await readRequestBody(input, init);
412+
413+    try {
414+      const response = await originalFetch.apply(this, arguments);
415+      const resHeaders = readHeaders(response.headers);
416+      const contentType = response.headers.get("content-type") || "";
417+      const isSse = context.rule.isSse(context.parsed.pathname, contentType);
418+
419+      if (isSse) {
420+        emitNet({
421+          url,
422+          method,
423+          reqHeaders,
424+          reqBody,
425+          status: response.status,
426+          resHeaders,
427+          duration: Date.now() - startedAt,
428+          sse: true,
429+          source: "page"
430+        }, context.rule);
431+        streamSse(url, method, reqBody, response, startedAt, context.rule);
432+        return response;
433+      }
434+
435+      const resBody = await readResponseText(response);
436+      emitNet({
437+        url,
438+        method,
439+        reqHeaders,
440+        reqBody,
441+        status: response.status,
442+        resHeaders,
443+        resBody,
444+        duration: Date.now() - startedAt,
445+        source: "page"
446+      }, context.rule);
447+      return response;
448+    } catch (error) {
449+      emitNet({
450+        url,
451+        method,
452+        reqHeaders,
453+        reqBody,
454+        error: error.message,
455+        duration: Date.now() - startedAt,
456+        source: "page"
457+      }, context.rule);
458+      throw error;
459+    }
460+  };
461+
462+  XMLHttpRequest.prototype.open = function patchedOpen(method, url) {
463+    this.__baaMethod = String(method || "GET").toUpperCase();
464+    this.__baaUrl = typeof url === "string" ? url : String(url);
465+    this.__baaRequestHeaders = {};
466+    return originalXhrOpen.apply(this, arguments);
467+  };
468+
469+  XMLHttpRequest.prototype.setRequestHeader = function patchedSetRequestHeader(name, value) {
470+    if (this.__baaRequestHeaders && name) {
471+      this.__baaRequestHeaders[String(name).toLowerCase()] = String(value || "");
472+    }
473+    return originalXhrSetRequestHeader.apply(this, arguments);
474+  };
475+
476+  XMLHttpRequest.prototype.send = function patchedSend(body) {
477+    const url = this.__baaUrl;
478+    const context = getRequestContext(url);
479+    if (!context || !context.rule.shouldTrack(context.parsed.pathname)) {
480+      return originalXhrSend.apply(this, arguments);
481+    }
482+
483+    const method = this.__baaMethod || "GET";
484+    const reqBody = trimBodyValue(body);
485+    const reqHeaders = { ...(this.__baaRequestHeaders || {}) };
486+    const startedAt = Date.now();
487+    let finalized = false;
488+    let terminalError = null;
489+
490+    const finalize = () => {
491+      if (finalized) return;
492+      finalized = true;
493+
494+      const resHeaders = readRawHeaders(this.getAllResponseHeaders());
495+      const contentType = String(this.getResponseHeader("content-type") || "");
496+      const duration = Date.now() - startedAt;
497+      const error = terminalError || (this.status === 0 ? "xhr_failed" : null);
498+      const isSse = context.rule.isSse(context.parsed.pathname, contentType);
499+      let responseText = null;
500+
501+      try {
502+        responseText = typeof this.responseText === "string" ? trim(this.responseText) : null;
503+      } catch (_) {
504+        responseText = null;
505+      }
506+
507+      emitNet({
508+        url,
509+        method,
510+        reqHeaders,
511+        reqBody,
512+        status: this.status || null,
513+        resHeaders,
514+        resBody: isSse && context.rule.platform !== "gemini" ? null : responseText,
515+        error,
516+        duration,
517+        sse: isSse,
518+        source: "xhr"
519+      }, context.rule);
520+
521+      if (isSse && responseText) {
522+        emitSse({
523+          url,
524+          method,
525+          reqBody,
526+          chunk: responseText,
527+          ts: Date.now()
528+        }, context.rule);
529+      }
530+
531+      if (isSse) {
532+        emitSse({
533+          url,
534+          method,
535+          reqBody,
536+          done: true,
537+          ts: Date.now(),
538+          duration
539+        }, context.rule);
540+      }
541+    };
542+
543+    this.addEventListener("error", () => {
544+      terminalError = "xhr_error";
545+    }, { once: true });
546+    this.addEventListener("abort", () => {
547+      terminalError = "xhr_aborted";
548+    }, { once: true });
549+    this.addEventListener("timeout", () => {
550+      terminalError = "xhr_timeout";
551+    }, { once: true });
552+    this.addEventListener("loadend", finalize, { once: true });
553+
554+    return originalXhrSend.apply(this, arguments);
555+  };
556+})();
A plugins/baa-firefox/scripts/run-persistent.sh
+29, -0
 1@@ -0,0 +1,29 @@
 2+#!/usr/bin/env bash
 3+set -euo pipefail
 4+
 5+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
 6+REPO_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
 7+
 8+WEB_EXT_BIN="${BAA_WEB_EXT_BIN:-web-ext}"
 9+FIREFOX_BIN="${BAA_FIREFOX_BIN:-/Applications/Firefox.app/Contents/MacOS/firefox}"
10+PROFILE_NAME="${BAA_FIREFOX_PROFILE:-baa-firefox-persistent}"
11+
12+if ! command -v "${WEB_EXT_BIN}" >/dev/null 2>&1; then
13+  echo "web-ext not found: ${WEB_EXT_BIN}" >&2
14+  exit 1
15+fi
16+
17+if [ ! -x "${FIREFOX_BIN}" ]; then
18+  echo "Firefox binary not found: ${FIREFOX_BIN}" >&2
19+  exit 1
20+fi
21+
22+exec "${WEB_EXT_BIN}" run \
23+  --source-dir "${REPO_DIR}" \
24+  --firefox "${FIREFOX_BIN}" \
25+  --target firefox-desktop \
26+  --firefox-profile "${PROFILE_NAME}" \
27+  --keep-profile-changes \
28+  --pref=network.proxy.allow_hijacking_localhost=false \
29+  --pref=network.proxy.no_proxies_on=localhost,127.0.0.1 \
30+  "$@"