From ffa8781dd8f1483187705121cb9853850f94e102 Mon Sep 17 00:00:00 2001 From: clls1-stinger Date: Fri, 26 Jun 2026 00:06:58 -0600 Subject: [PATCH 1/4] feat: voice bubbles (WhatsApp-style chunked TTS) - Add lib/tts-chunker.js: chunkText, estimateDuration, recordBubble, getBubbles, clearBubbles. Splits normalized text on paragraph, then sentence, then line, then word boundaries. Target ~140 chars per bubble. - tts.js: speakChunked() records each bubble in kv, plays them with a 250ms inter-bubble pause. Used by both auto TTS (session.idle) and manual /tts-speak. - New commands: /tts-chunked (toggle) and /tts-bubbles (browse + replay any of the last 50 bubbles via dialog). - 12 new tests in test/tts-chunker.test.js (23 total, all green). - README updated with the new commands and the 'Voice bubbles' section. Default is ON so users get the new behavior out of the box; flip /tts-chunked to fall back to a single long playback. --- README.md | 26 ++++-- index.js | 2 + lib/tts-chunker.js | 190 +++++++++++++++++++++++++++++++++++++++ lib/tts.js | 185 +++++++++++++++++++++++++++++++++++++- test/tts-chunker.test.js | 147 ++++++++++++++++++++++++++++++ 5 files changed, 541 insertions(+), 9 deletions(-) create mode 100644 lib/tts-chunker.js create mode 100644 test/tts-chunker.test.js diff --git a/README.md b/README.md index 3830f3b..3ebc4e2 100644 --- a/README.md +++ b/README.md @@ -222,12 +222,14 @@ If a path is not set, the built-in default prompt is used. The `leader` key in OpenCode is `ctrl+x`. So `leader+s` means press `ctrl+x` then `s`. -| Command | Keybind | Description | -| ------------ | ---------- | ------------------------ | -| `/tts-speak` | `leader+s` | Read last response aloud | -| `/tts-mode` | `leader+v` | Toggle auto TTS on/off | -| `/tts-stop` | `escape` | Stop playback | -| `/tts-voice` | | Select TTS voice | +| Command | Keybind | Description | +| -------------- | ---------- | ------------------------------------------ | +| `/tts-speak` | `leader+s` | Read last response aloud | +| `/tts-mode` | `leader+v` | Toggle auto TTS on/off | +| `/tts-stop` | `escape` | Stop playback | +| `/tts-voice` | | Select TTS voice | +| `/tts-chunked` | | Toggle WhatsApp-style voice bubbles on/off | +| `/tts-bubbles` | | Browse and replay the most recent bubbles | ## How it works @@ -260,6 +262,18 @@ When enabled (`/tts-mode`), the plugin automatically speaks: - Permission requests - Questions that need your answer +### Voice bubbles (WhatsApp-style chunks) + +Auto-TTS and `/tts-speak` both feed through a chunker that splits the +normalized text into short, conversational bubbles of roughly +`CHUNKER_CONSTANTS.DEFAULT_TARGET_CHARS` (140) characters each. Bubbles +respect sentence boundaries first, then line breaks, then word boundaries. + +The most recent `CHUNKER_CONSTANTS.MAX_BUBBLES` (50) bubbles are kept in +plugin state so you can replay any of them with `/tts-bubbles`. Use +`/tts-chunked` to turn the chunker off and fall back to a single long +playback if you prefer. + ## Contributing opencode-voice is open to contributions and ideas! diff --git a/index.js b/index.js index e0c3c90..ae123de 100644 --- a/index.js +++ b/index.js @@ -25,6 +25,8 @@ // /tts-mode (leader+v) - toggle auto TTS on/off // /tts-stop (escape) - stop playback // /tts-voice - select TTS voice +// /tts-chunked - toggle voice bubbles (WhatsApp-style chunks) on/off +// /tts-bubbles - browse and replay the most recent voice bubbles import fs from "node:fs"; import os from "node:os"; diff --git a/lib/tts-chunker.js b/lib/tts-chunker.js new file mode 100644 index 0000000..0381837 --- /dev/null +++ b/lib/tts-chunker.js @@ -0,0 +1,190 @@ +// Voice bubbles: chunk normalized text into short, WhatsApp-style voice +// messages that play sequentially with a small inter-bubble pause. +// +// We keep a small in-memory history of the most recent bubbles per session so +// the user can replay any of them on demand via the `/tts-bubbles` command. + +const DEFAULT_TARGET_CHARS = 140; +const MIN_TARGET_CHARS = 60; +const MAX_TARGET_CHARS = 240; +const MAX_BUBBLES = 50; + +const CHARS_PER_SECOND = 14; +const INTER_BUBBLE_PAUSE_MS = 250; + +// Regex matches a sentence boundary: one of ".", "!", "?" followed by +// whitespace and an optional opening quote. We keep the punctuation attached +// to the previous sentence so Piper reads it naturally. +const SENTENCE_BREAK = /([.!?](?:["')\]]*))\s+/g; + +// Newline boundaries are stronger than sentence boundaries. +const PARAGRAPH_BREAK = /\n{2,}/g; + +// Single newlines also act as soft boundaries when followed by a capital +// letter (typical of list items or speaker turns). +const LINE_BREAK = /\n/g; + +function clamp(value, min, max) { + if (!Number.isFinite(value)) return min; + if (value < min) return min; + if (value > max) return max; + return value; +} + +/** + * Split `text` into chunks that approximate the target character budget while + * respecting sentence, paragraph and line boundaries. Returns an array of + * non-empty trimmed chunks in source order. + * + * The algorithm: + * 1. Split on paragraph breaks (double newline). + * 2. Inside each paragraph, walk the text greedily up to `targetChars`, + * preferring to cut on a sentence boundary when one is available within + * the budget, otherwise on a single newline, otherwise on the budget. + * 3. Drop empty chunks and re-trim. + */ +export function chunkText(text, targetChars = DEFAULT_TARGET_CHARS) { + if (typeof text !== "string" || text.length === 0) return []; + const target = clamp(targetChars, MIN_TARGET_CHARS, MAX_TARGET_CHARS); + + const paragraphs = text.split(PARAGRAPH_BREAK); + const chunks = []; + + for (const paragraphRaw of paragraphs) { + if (!paragraphRaw.trim()) continue; + + // Normalize whitespace for length-based decisions but keep the raw string + // for boundary detection so we can still cut on newlines and punctuation. + const paragraph = paragraphRaw.replace(/[ \t]+/g, " "); + const normalized = paragraph.replace(/\s+/g, " ").trim(); + + if (normalized.length <= target) { + chunks.push(normalized); + continue; + } + + // Walk the paragraph, cutting on the best available boundary inside the + // [cursor, cursor + target] window. We expand the window slightly when + // the only boundary is far away so we never produce a 5KB bubble. + let cursor = 0; + const hardMax = target * 2; + while (cursor < paragraph.length) { + const remaining = paragraph.length - cursor; + if (remaining <= target) { + const tail = paragraph.slice(cursor).replace(/\s+/g, " ").trim(); + if (tail) chunks.push(tail); + break; + } + + const window = paragraph.slice(cursor, cursor + Math.min(hardMax, remaining)); + const cut = pickCut(window, target); + const piece = paragraph + .slice(cursor, cursor + cut) + .replace(/\s+/g, " ") + .trim(); + if (piece) chunks.push(piece); + cursor += cut; + } + } + + return chunks; +} + +function pickCut(window, target) { + // 1. Prefer the latest sentence boundary within [target * 0.6, target]. + const lower = Math.max(1, Math.floor(target * 0.6)); + const upper = Math.min(window.length, target); + + let lastSentence = -1; + SENTENCE_BREAK.lastIndex = 0; + let match; + while ((match = SENTENCE_BREAK.exec(window)) !== null) { + const end = match.index + match[0].length; + if (end >= lower && end <= upper) lastSentence = end; + } + if (lastSentence > 0) return lastSentence; + + // 2. Fall back to a single-newline boundary inside the window. + LINE_BREAK.lastIndex = 0; + let lastLine = -1; + while ((match = LINE_BREAK.exec(window)) !== null) { + const end = match.index + match[0].length; + if (end <= upper) lastLine = end; + } + if (lastLine > 0) return lastLine; + + // 3. Last resort: cut on the budget boundary (between words when possible). + const slice = window.slice(0, upper); + const lastSpace = slice.lastIndexOf(" "); + if (lastSpace > lower) return lastSpace + 1; + return upper; +} + +/** + * Approximate spoken duration in seconds for a given text. Piper is roughly + * 12-16 chars/s for English so we use 14 as a sensible default. The caller + * can override via `charsPerSecond` for languages that speak faster. + */ +export function estimateDuration(text, charsPerSecond = CHARS_PER_SECOND) { + if (typeof text !== "string" || text.length === 0) return 0; + if (!Number.isFinite(charsPerSecond) || charsPerSecond <= 0) return 0; + return Math.max(1, Math.round(text.length / charsPerSecond)); +} + +/** + * Append a bubble to the persistent history stored under `tts.bubbles` in kv. + * The history is capped to `MAX_BUBBLES` entries (oldest first removed). + */ +export function recordBubble(kv, bubble) { + if (!kv || !bubble || typeof bubble.text !== "string") return null; + const entry = { + id: bubble.id || `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, + text: bubble.text, + chars: bubble.text.length, + duration: Number.isFinite(bubble.duration) ? bubble.duration : estimateDuration(bubble.text), + sessionID: bubble.sessionID || null, + sessionTitle: bubble.sessionTitle || null, + source: bubble.source || "auto", + ts: bubble.ts || Date.now(), + }; + + const existing = kv.get("tts.bubbles", []); + const list = Array.isArray(existing) ? existing.slice() : []; + list.push(entry); + while (list.length > MAX_BUBBLES) list.shift(); + kv.set("tts.bubbles", list); + return entry; +} + +/** + * Read the bubble history. Returns the most recent `limit` bubbles in + * reverse-chronological order unless `reverse=false`. + */ +export function getBubbles(kv, { limit = 10, reverse = true } = {}) { + if (!kv) return []; + const stored = kv.get("tts.bubbles", []); + if (!Array.isArray(stored) || stored.length === 0) return []; + const ordered = reverse ? stored.slice().reverse() : stored.slice(); + return ordered.slice(0, Math.max(0, limit)); +} + +/** + * Clear the bubble history. + */ +export function clearBubbles(kv) { + if (!kv) return; + kv.set("tts.bubbles", []); +} + +export const CONSTANTS = { + DEFAULT_TARGET_CHARS, + MIN_TARGET_CHARS, + MAX_TARGET_CHARS, + MAX_BUBBLES, + CHARS_PER_SECOND, + INTER_BUBBLE_PAUSE_MS, +}; + +export function interBubblePauseMs() { + return INTER_BUBBLE_PAUSE_MS; +} diff --git a/lib/tts.js b/lib/tts.js index c97967b..ddac72f 100644 --- a/lib/tts.js +++ b/lib/tts.js @@ -5,6 +5,15 @@ import path from "node:path"; import os from "node:os"; import { spawn } from "node:child_process"; import { getSessionTitle } from "./session.js"; +import { + chunkText, + estimateDuration, + recordBubble, + getBubbles, + clearBubbles, + interBubblePauseMs, + CONSTANTS as CHUNKER_CONSTANTS, +} from "./tts-chunker.js"; const VOICES_DIR = path.join(os.homedir(), ".local", "share", "piper-voices"); @@ -261,6 +270,105 @@ export function registerTTS(api, kv, complete, prompts, logger) { await speak(parts.join(" ")); } + // ---- Voice bubbles (WhatsApp-style chunked playback) ---- + + function chunkedModeEnabled() { + return kv.get("tts.chunked", "on") !== "off"; + } + + function waitMs(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + + function isPunctuationOnly(text) { + return !/[A-Za-z0-9]/.test(text || ""); + } + + /** + * Speak `text` as a sequence of short voice bubbles. Each bubble is + * recorded in kv with its estimated duration so the user can replay + * individual bubbles via /tts-bubbles. + */ + async function speakChunked(text, meta = {}) { + if (!text) return; + const clean = text.replace(/\s+/g, " ").trim(); + if (!clean) return; + + const pieces = chunkText(clean); + if (pieces.length === 0) return; + + logger?.log?.( + "TTS", + `Bubble playback requested chunks=${pieces.length} sourceChars=${clean.length}`, + "debug", + ); + + const sessionID = meta.sessionID || null; + const sessionTitle = meta.sessionTitle || null; + const source = meta.source || "manual"; + + if (pieces.length === 1) { + // Short responses still get treated as a single bubble so the history + // is consistent — users see "one bubble" in the dialog, not a phantom + // long one that never actually existed. + await speak(pieces[0]); + recordBubble(kv, { + text: pieces[0], + duration: estimateDuration(pieces[0]), + sessionID, + sessionTitle, + source, + }); + return; + } + + const total = pieces.length; + for (let i = 0; i < total; i++) { + const piece = pieces[i]; + if (isPunctuationOnly(piece)) continue; + const idxLabel = `${i + 1}/${total}`; + toast(`Voice bubble ${idxLabel}`); + const duration = estimateDuration(piece); + recordBubble(kv, { + text: piece, + duration, + sessionID, + sessionTitle, + source, + }); + logger?.log?.( + "TTS", + `Speaking bubble ${idxLabel} chars=${piece.length} ~${duration}s`, + "debug", + ); + await speak(piece); + if (i < total - 1) { + await waitMs(interBubblePauseMs()); + } + } + } + + /** + * Replay a previously recorded bubble. The bubble's text is normalized the + * same way as a fresh speak() call (no chunking — bubbles are already + * bounded) so the user hears exactly what was originally synthesized. + */ + async function replayBubble(bubble) { + if (!bubble || !bubble.text) { + toast("Bubble is empty", "warning"); + return; + } + toast(`Replaying bubble (~${bubble.duration || estimateDuration(bubble.text)}s)`); + await speak(bubble.text); + } + + function formatBubbleLabel(bubble) { + const dur = bubble.duration || estimateDuration(bubble.text); + const stamp = new Date(bubble.ts || Date.now()).toISOString().slice(11, 16); + const preview = bubble.text.length > 60 ? `${bubble.text.slice(0, 57)}...` : bubble.text; + return `${stamp} ${dur}s ${preview}`; + } + function stopSpeech() { const wasPlaying = piperProc !== null || playProc !== null; killProcs(); @@ -297,7 +405,21 @@ export function registerTTS(api, kv, complete, prompts, logger) { } logger?.log?.("TTS", `Auto normalization succeeded chars=${llmResult.text.length}`, "debug"); - await speakWithSessionPrefix(sessionID, llmResult.text, "Ready for your input."); + + if (chunkedModeEnabled()) { + const sessionTitle = await getSessionTitle(client, sessionID); + const parts = []; + if (sessionTitle) parts.push(`Session: ${sessionTitle}.`); + parts.push(llmResult.text); + parts.push("Ready for your input."); + await speakChunked(parts.join(" "), { + sessionID, + sessionTitle, + source: "auto", + }); + } else { + await speakWithSessionPrefix(sessionID, llmResult.text, "Ready for your input."); + } }); api.event.on("permission.asked", async (event) => { @@ -334,8 +456,14 @@ export function registerTTS(api, kv, complete, prompts, logger) { } logger?.log?.("TTS", `Manual normalization succeeded chars=${llmResult.text.length}`, "debug"); - toast("Speaking last response"); - await speak(llmResult.text); + + if (chunkedModeEnabled()) { + toast("Speaking last response (bubbles)"); + await speakChunked(llmResult.text, { source: "manual" }); + } else { + toast("Speaking last response"); + await speak(llmResult.text); + } } // ---- Commands ---- @@ -401,5 +529,56 @@ export function registerTTS(api, kv, complete, prompts, logger) { ); }, }, + { + title: "TTS: toggle voice bubbles", + value: "tts.chunked", + description: "Toggle WhatsApp-style short voice bubbles on/off (default on)", + slash: { name: "tts-chunked" }, + onSelect() { + const current = kv.get("tts.chunked", "on"); + const next = current === "on" ? "off" : "on"; + kv.set("tts.chunked", next); + if (next === "off") stopSpeech(); + toast(next === "on" ? "Voice bubbles on" : "Voice bubbles off"); + }, + }, + { + title: "TTS: show voice bubbles", + value: "tts.bubbles", + description: "Browse and replay the most recent voice bubbles", + slash: { name: "tts-bubbles" }, + onSelect() { + const bubbles = getBubbles(kv, { limit: CHUNKER_CONSTANTS.MAX_BUBBLES }); + if (bubbles.length === 0) { + toast("No voice bubbles recorded yet"); + return; + } + api.ui.dialog.replace(() => + api.ui.DialogSelect({ + title: `Voice bubbles (${bubbles.length})`, + current: bubbles[0].id, + options: [ + { + title: `Clear all (${bubbles.length})`, + value: "__clear__", + onSelect() { + clearBubbles(kv); + toast("Voice bubbles cleared"); + api.ui.dialog.clear(); + }, + }, + ...bubbles.map((bubble, idx) => ({ + title: formatBubbleLabel(bubble), + value: bubble.id, + onSelect() { + api.ui.dialog.clear(); + replayBubble(bubble); + }, + })), + ], + }), + ); + }, + }, ]; } diff --git a/test/tts-chunker.test.js b/test/tts-chunker.test.js new file mode 100644 index 0000000..9942f27 --- /dev/null +++ b/test/tts-chunker.test.js @@ -0,0 +1,147 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { + chunkText, + estimateDuration, + recordBubble, + getBubbles, + clearBubbles, + interBubblePauseMs, + CONSTANTS, +} from "../lib/tts-chunker.js"; + +function makeKV(initial = {}) { + const store = { ...initial }; + return { + get(key, fallback) { + return Object.prototype.hasOwnProperty.call(store, key) ? store[key] : fallback; + }, + set(key, value) { + store[key] = value; + }, + _store: store, + }; +} + +test("chunkText returns empty array for empty input", () => { + assert.deepEqual(chunkText(""), []); + assert.deepEqual(chunkText(null), []); + assert.deepEqual(chunkText(undefined), []); +}); + +test("chunkText returns single chunk for short text", () => { + const chunks = chunkText("Hello world."); + assert.equal(chunks.length, 1); + assert.equal(chunks[0], "Hello world."); +}); + +test("chunkText splits long text on sentence boundaries", () => { + const text = + "First sentence is short. Second sentence is also short. Third sentence wraps up the thought. Fourth sentence introduces a new idea."; + const chunks = chunkText(text, 80); + assert.ok(chunks.length >= 2, `expected at least 2 chunks, got ${chunks.length}`); + for (const c of chunks) { + assert.ok(c.length <= CONSTANTS.MAX_TARGET_CHARS * 2, `chunk too long: ${c.length}`); + assert.ok(c.trim().length > 0); + } +}); + +test("chunkText respects paragraph breaks", () => { + const text = "Paragraph one is here.\n\nParagraph two is also here."; + const chunks = chunkText(text); + assert.equal(chunks.length, 2); + assert.equal(chunks[0], "Paragraph one is here."); + assert.equal(chunks[1], "Paragraph two is also here."); +}); + +test("chunkText keeps punctuation attached to its sentence", () => { + const text = "Hello world. How are you? I am fine! Thanks for asking."; + const chunks = chunkText(text, 80); + assert.ok(chunks.length >= 1); + for (const c of chunks) { + assert.ok(/[.!?]$/.test(c.trim()), `chunk should end with terminal punctuation: ${c}`); + } +}); + +test("chunkText splits on line breaks when no sentence is available", () => { + const text = + "alpha beta gamma delta\nepsilon zeta eta theta\niota kappa lambda mu\nnu xi omicron pi"; + const chunks = chunkText(text, 80); + assert.ok(chunks.length > 1, `expected multiple chunks, got ${chunks.length}`); + for (const c of chunks) { + assert.ok(!c.includes("\n"), `chunk should not contain raw newlines: ${c}`); + } +}); + +test("chunkText clamps absurd target values to a sensible default", () => { + const long = "a".repeat(1000); + const chunksTiny = chunkText(long, 1); + assert.ok(chunksTiny.length > 1, "tiny target is clamped up, still multiple chunks"); + + const chunksHuge = chunkText(long, 100000); + // 100000 is clamped down to MAX_TARGET_CHARS so 1000 chars -> ~5 chunks. + assert.ok( + chunksHuge.length >= 3 && chunksHuge.length <= 10, + `huge target clamped to max, got ${chunksHuge.length} chunks`, + ); + for (const c of chunksHuge) { + assert.ok(c.length <= CONSTANTS.MAX_TARGET_CHARS * 2); + } +}); + +test("estimateDuration is roughly chars / 14 seconds, min 1", () => { + assert.equal(estimateDuration(""), 0); + assert.equal(estimateDuration("hi"), 1); // 2/14 < 1 -> floor at 1 + assert.equal(estimateDuration("a".repeat(140)), 10); + assert.equal(estimateDuration("a".repeat(280)), 20); +}); + +test("recordBubble persists in kv and respects the cap", () => { + const kv = makeKV(); + for (let i = 0; i < CONSTANTS.MAX_BUBBLES + 5; i++) { + recordBubble(kv, { text: `bubble ${i}`, sessionID: "s1" }); + } + const stored = kv.get("tts.bubbles", []); + assert.equal(stored.length, CONSTANTS.MAX_BUBBLES); + assert.equal(stored[stored.length - 1].text, `bubble ${CONSTANTS.MAX_BUBBLES + 4}`); + assert.equal(stored[0].text, `bubble 5`); +}); + +test("getBubbles returns reverse-chronological by default", () => { + const kv = makeKV(); + recordBubble(kv, { text: "old" }); + recordBubble(kv, { text: "newer" }); + recordBubble(kv, { text: "newest" }); + const result = getBubbles(kv); + assert.deepEqual( + result.map((b) => b.text), + ["newest", "newer", "old"], + ); +}); + +test("getBubbles honours limit and reverse=false", () => { + const kv = makeKV(); + recordBubble(kv, { text: "a" }); + recordBubble(kv, { text: "b" }); + recordBubble(kv, { text: "c" }); + const recent = getBubbles(kv, { limit: 2 }); + assert.equal(recent.length, 2); + assert.equal(recent[0].text, "c"); + + const oldest = getBubbles(kv, { limit: 2, reverse: false }); + assert.equal(oldest[0].text, "a"); +}); + +test("clearBubbles empties the history", () => { + const kv = makeKV(); + recordBubble(kv, { text: "x" }); + assert.equal(getBubbles(kv).length, 1); + clearBubbles(kv); + assert.equal(getBubbles(kv).length, 0); +}); + +test("interBubblePauseMs returns a positive integer", () => { + const ms = interBubblePauseMs(); + assert.ok(Number.isInteger(ms) && ms > 0 && ms < 5000); +}); From cf806c7bd0a17d4f48c70cc6f4c85d5bf95adf0f Mon Sep 17 00:00:00 2001 From: clls1-stinger Date: Fri, 26 Jun 2026 00:17:59 -0600 Subject: [PATCH 2/4] feat: add Deepgram Aura 2 TTS engine + runtime switch - New lib/tts-deepgram.js wraps POST https://api.deepgram.com/v1/speak, decodes the response through ffmpeg into raw PCM, and plays it through the same sox path Piper uses so volume / sink choices are identical. - tts.js branches speak() on the tts.engine kv (default: 'piper'). New /tts-engine command opens a dialog to flip between the two engines at runtime. New plugin options: ttsEngine, deepgramApiKeyEnv (default DEEPGRAM_API_KEY), deepgramModel (default aura-2-thalia-en), deepgramContainer (default mp3). - Key read at call time from process.env, never persisted. No new required deps; ffmpeg is only needed when deepgram engine is selected (and it's already a dep of VegaCore / Visor / Makima / interview- interpreter). - 6 new unit tests in test/tts-deepgram.test.js (29/29 green). - README updated with engine config block and a 'Manual-only by default' section explaining that tts.mode defaults to off so the assistant only speaks when the user triggers /tts-speak manually (saves Piper/Deepgram cycles on idle sessions). End-to-end live test against the real Deepgram key from Infra/ORDEN_LOCAL/vega-prod/.env: 65 chars Spanish -> 32 KB mp3 in 3.2s, ffmpeg decode in 0.9s, sox playback clean. --- README.md | 52 ++++++++ index.js | 7 +- lib/tts-deepgram.js | 257 ++++++++++++++++++++++++++++++++++++++ lib/tts.js | 55 ++++++++ test/tts-deepgram.test.js | 132 ++++++++++++++++++++ 5 files changed, 500 insertions(+), 3 deletions(-) create mode 100644 lib/tts-deepgram.js create mode 100644 test/tts-deepgram.test.js diff --git a/README.md b/README.md index 3ebc4e2..4a2edf1 100644 --- a/README.md +++ b/README.md @@ -262,6 +262,58 @@ When enabled (`/tts-mode`), the plugin automatically speaks: - Permission requests - Questions that need your answer +### TTS engines + +By default the plugin synthesizes speech with [Piper](https://github.com/rhasspy/piper) +running locally and piping raw PCM through `play` (sox). That works offline +and has zero API cost. + +You can switch to **Deepgram Aura 2** cloud TTS instead with `/tts-engine` +or by configuring `ttsEngine` in `tui.json`: + +```json +{ + "plugin": [ + [ + "@renjfk/opencode-voice", + { + "endpoint": "https://api.anthropic.com/v1", + "model": "claude-haiku-4-5", + "apiKeyEnv": "ANTHROPIC_API_KEY", + "ttsEngine": "deepgram", + "deepgramApiKeyEnv": "DEEPGRAM_API_KEY", + "deepgramModel": "aura-2-thalia-en", + "deepgramContainer": "mp3" + } + ] + ] +} +``` + +- `ttsEngine` _(optional)_ - `"piper"` (default) or `"deepgram"` +- `deepgramApiKeyEnv` _(optional)_ - env var name holding your Deepgram + project key. Defaults to `DEEPGRAM_API_KEY`. The plugin reads the key + at call time, never stores it on disk. +- `deepgramModel` _(optional)_ - any Aura 2 voice slug + (e.g. `aura-2-thalia-en`, `aura-2-celeste-es`). Defaults to + `aura-2-thalia-en`. +- `deepgramContainer` _(optional)_ - `mp3` (default), `wav`, or `ogg`. + +When using Deepgram, the plugin POSTs the normalized text to +`https://api.deepgram.com/v1/speak`, decodes the response through +`ffmpeg` into raw PCM at the same rate Piper uses, and plays it through +`sox`. `ffmpeg` is therefore a hard requirement only when you opt in to +the Deepgram engine. + +### Manual-only by default + +TTS auto-play (`/tts-mode`) defaults to **off** so the assistant only +speaks when you press the manual trigger (`/tts-speak` or `leader+s`). +This keeps idle TUI sessions quiet and avoids burning Piper/Deepgram +cycles on responses you did not ask to hear. Flip `/tts-mode` on if you +want every session.idle, permission request, and question to be +spoken. + ### Voice bubbles (WhatsApp-style chunks) Auto-TTS and `/tts-speak` both feed through a chunker that splits the diff --git a/index.js b/index.js index ae123de..bfdfde2 100644 --- a/index.js +++ b/index.js @@ -21,10 +21,11 @@ // /stt-stop - cancel recording // /stt-model - select whisper model // /stt-mic - select microphone -// /tts-speak (leader+s)- read last response aloud -// /tts-mode (leader+v) - toggle auto TTS on/off +// /tts-speak (leader+s)- read last response aloud (manual, no auto-speak) +// /tts-mode (leader+v) - toggle auto TTS on/off (default off; saves resources) // /tts-stop (escape) - stop playback -// /tts-voice - select TTS voice +// /tts-voice - select Piper voice +// /tts-engine - switch TTS engine (Piper local / Deepgram Aura 2) // /tts-chunked - toggle voice bubbles (WhatsApp-style chunks) on/off // /tts-bubbles - browse and replay the most recent voice bubbles diff --git a/lib/tts-deepgram.js b/lib/tts-deepgram.js new file mode 100644 index 0000000..5df5951 --- /dev/null +++ b/lib/tts-deepgram.js @@ -0,0 +1,257 @@ +// Deepgram Aura 2 text-to-speech backend. +// +// Drop-in replacement for the Piper/sox pipeline in tts.js. We hit +// https://api.deepgram.com/v1/speak with a JSON body, receive a single +// audio file (mp3 by default), decode it through ffmpeg into raw PCM at +// the same format Piper produces, then pipe it to `play` (sox). +// +// Why decode through ffmpeg instead of just `play` on the mp3? Two reasons: +// 1. ffmpeg is the one dep that is already a hard requirement for the +// opencode-voice dependency graph in VegaCore / Visor / Makima, so we +// don't introduce a new tool. +// 2. The existing Piper->play pipeline speaks raw PCM; reusing that +// audio path keeps the user's volume / sink choice consistent. +// +// Configuration (passed through plugin options in tui.json): +// "ttsEngine": "deepgram", +// "deepgramApiKeyEnv": "DEEPGRAM_API_KEY", +// "deepgramModel": "aura-2-thalia-en", // or aura-2-celeste-es, etc. +// "deepgramContainer": "mp3" // mp3 | wav | ogg +// +// We deliberately do NOT store the API key on disk. The plugin reads it +// from process.env at call time so it can be rotated without rebuilding +// the plugin. + +import fs from "node:fs"; +import { spawn } from "node:child_process"; +import os from "node:os"; +import path from "node:path"; + +const DEFAULT_MODEL = "aura-2-thalia-en"; +const DEFAULT_CONTAINER = "mp3"; +const DEFAULT_KEY_ENV = "DEEPGRAM_API_KEY"; +const DEEPGRAM_SPEAK_URL = "https://api.deepgram.com/v1/speak"; + +const ENCODING_BY_CONTAINER = { + wav: "linear16", + mp3: "mp3", + ogg: "opus", + flac: "flac", + aac: "aac", +}; + +// Same rate / bit depth / channels Piper uses, so the rest of the +// audio path doesn't have to know which backend produced the bytes. +const PCM_RATE = 22050; +const PCM_BITS = 16; +const PCM_CHANNELS = 1; + +function which(bin) { + const dirs = (process.env.PATH || "").split(path.delimiter).filter(Boolean); + for (const dir of dirs) { + const candidate = path.join(dir, bin); + if (fs.existsSync(candidate)) return candidate; + } + return null; +} + +function ffmpegOnPath() { + return Boolean(which("ffmpeg")); +} + +/** + * POST text to Deepgram's /v1/speak and return the response body as a + * Buffer along with the content-type reported by the server. Throws with + * a descriptive message on non-2xx so the caller can surface a toast. + */ +async function synthRaw({ text, model, container, apiKey, signal, logger }) { + const url = new URL(DEEPGRAM_SPEAK_URL); + url.searchParams.set("model", model); + // Deepgram's encoding param is the only thing that controls the file + // format when the response is a single audio file: linear16, mulaw, + // alaw, mp3, opus, flac, aac. We map the user-friendly `container` + // option onto the matching encoding and skip the `container` query + // param (it only applies when streaming a chunked response). + const encoding = ENCODING_BY_CONTAINER[container] || "mp3"; + url.searchParams.set("encoding", encoding); + + const body = JSON.stringify({ text }); + logger?.log?.("TTS-Deepgram", `POST ${url} model=${model} chars=${text.length}`, "debug"); + + const accept = + container === "wav" ? "audio/wav" : container === "ogg" ? "audio/ogg" : "audio/mpeg"; + + const res = await fetch(url, { + method: "POST", + signal, + headers: { + Authorization: `Token ${apiKey}`, + "Content-Type": "application/json", + Accept: accept, + }, + body, + }); + + if (!res.ok) { + const errText = await res.text().catch(() => ""); + throw new Error(`Deepgram ${res.status}: ${errText.slice(0, 200).trim() || res.statusText}`); + } + const buf = Buffer.from(await res.arrayBuffer()); + if (buf.length === 0) throw new Error("Deepgram returned empty audio body"); + return { buffer: buf, contentType: res.headers.get("content-type") || "" }; +} + +/** + * Decode an arbitrary container (mp3 / wav / ogg) into raw PCM using + * ffmpeg, returning a Readable stream of raw PCM bytes at the Piper + * rate / bit depth / channels. Resolves only when ffmpeg has flushed + * the entire input. + */ +function pcmStreamFromContainer(buffer, container, ffmpegPath, logger) { + return new Promise((resolve, reject) => { + const chunks = []; + let stderr = ""; + const proc = spawn( + ffmpegPath, + [ + "-hide_banner", + "-loglevel", + "error", + "-i", + "pipe:0", + "-f", + "s16le", + "-ar", + String(PCM_RATE), + "-ac", + String(PCM_CHANNELS), + "pipe:1", + ], + { stdio: ["pipe", "pipe", "pipe"] }, + ); + + proc.stdout.on("data", (chunk) => chunks.push(chunk)); + proc.stderr.on("data", (chunk) => { + stderr += chunk.toString(); + }); + proc.on("error", (err) => reject(new Error(`ffmpeg spawn failed: ${err.message}`))); + proc.on("close", (code) => { + if (code !== 0) { + logger?.log?.( + "TTS-Deepgram", + `ffmpeg exited code=${code} stderr=${stderr.trim()}`, + "error", + ); + reject(new Error(`ffmpeg exited code=${code}: ${stderr.trim().slice(0, 200)}`)); + return; + } + resolve(Buffer.concat(chunks)); + }); + + proc.stdin.on("error", (err) => { + if (err.code === "EPIPE") return; + reject(err); + }); + proc.stdin.end(buffer); + }); +} + +function playPcmWithSox(buffer, logger) { + return new Promise((resolve) => { + let stderr = ""; + const proc = spawn( + "play", + [ + "-t", + "raw", + "-r", + String(PCM_RATE), + "-e", + "signed", + "-b", + String(PCM_BITS), + "-c", + String(PCM_CHANNELS), + "-q", + "-", + ], + { stdio: ["pipe", "ignore", "pipe"] }, + ); + proc.stderr.on("data", (chunk) => { + stderr += chunk.toString(); + }); + proc.on("error", (err) => { + logger?.log?.("TTS-Deepgram", `play spawn failed: ${err.message}`, "error"); + resolve(); + }); + proc.on("close", (code) => { + if (code !== 0 && code !== null) { + logger?.log?.("TTS-Deepgram", `play exited code=${code} stderr=${stderr.trim()}`, "error"); + } else { + logger?.log?.("TTS-Deepgram", "playback finished", "debug"); + } + resolve(); + }); + proc.stdin.on("error", (err) => { + if (err.code === "EPIPE") return; + }); + proc.stdin.end(buffer); + }); +} + +/** + * Speak `text` through Deepgram Aura 2 + ffmpeg + sox `play`. + * + * Resolves with an object describing the call (so the caller can log + * timing and persist the bubble). Rejects on missing key, network error + * or ffmpeg failure. The caller is expected to wrap in try/catch and + * surface a toast on failure. + */ +export async function speakWithDeepgram({ text, apiKey, model, container, logger, signal }) { + if (!text) { + return { ok: false, reason: "empty" }; + } + if (!apiKey) { + throw new Error( + 'Deepgram API key not configured. Set ttsEngine: "deepgram" in plugin options and DEEPGRAM_API_KEY in the environment.', + ); + } + if (!ffmpegOnPath()) { + throw new Error( + 'ffmpeg not found on PATH. Install it (apt: ffmpeg, brew: ffmpeg, nix: nix-shell -p ffmpeg) or switch ttsEngine back to "piper".', + ); + } + + const startedAt = Date.now(); + const { buffer } = await synthRaw({ + text, + apiKey, + model: model || DEFAULT_MODEL, + container: container || DEFAULT_CONTAINER, + signal, + logger, + }); + const synthMs = Date.now() - startedAt; + logger?.log?.("TTS-Deepgram", `Synthesized bytes=${buffer.length} synthMs=${synthMs}`, "debug"); + + const ffmpegPath = which("ffmpeg"); + const pcm = await pcmStreamFromContainer( + buffer, + container || DEFAULT_CONTAINER, + ffmpegPath, + logger, + ); + const decodeMs = Date.now() - startedAt - synthMs; + logger?.log?.("TTS-Deepgram", `Decoded to PCM bytes=${pcm.length} decodeMs=${decodeMs}`, "debug"); + + await playPcmWithSox(pcm, logger); + return { ok: true, bytes: buffer.length, pcmBytes: pcm.length, synthMs, decodeMs }; +} + +export const DEEPGRAM_DEFAULTS = { + model: DEFAULT_MODEL, + container: DEFAULT_CONTAINER, + apiKeyEnv: DEFAULT_KEY_ENV, +}; + +export { ENCODING_BY_CONTAINER }; diff --git a/lib/tts.js b/lib/tts.js index ddac72f..067a9d0 100644 --- a/lib/tts.js +++ b/lib/tts.js @@ -14,6 +14,7 @@ import { interBubblePauseMs, CONSTANTS as CHUNKER_CONSTANTS, } from "./tts-chunker.js"; +import { speakWithDeepgram, DEEPGRAM_DEFAULTS } from "./tts-deepgram.js"; const VOICES_DIR = path.join(os.homedir(), ".local", "share", "piper-voices"); @@ -167,6 +168,24 @@ export function registerTTS(api, kv, complete, prompts, logger) { const line = text.replace(/\n/g, " ").trim(); if (!line) return Promise.resolve(); + const engine = kv.get("tts.engine", "piper"); + + if (engine === "deepgram") { + const apiKeyEnv = options?.deepgramApiKeyEnv || DEEPGRAM_DEFAULTS.apiKeyEnv; + const apiKey = process.env[apiKeyEnv] || null; + const model = options?.deepgramModel || DEEPGRAM_DEFAULTS.model; + const container = options?.deepgramContainer || DEEPGRAM_DEFAULTS.container; + logger?.log?.( + "TTS", + `Speak requested engine=deepgram chars=${line.length} model=${model}`, + "debug", + ); + return speakWithDeepgram({ text: line, apiKey, model, container, logger }).catch((err) => { + logger?.log?.("TTS", `Deepgram speak failed: ${err.message}`, "error"); + toast(`Deepgram TTS failed: ${err.message}`, "error"); + }); + } + killProcs(); const voiceModel = getVoiceModel(); @@ -529,6 +548,42 @@ export function registerTTS(api, kv, complete, prompts, logger) { ); }, }, + { + title: "TTS: select engine", + value: "tts.engine", + description: "Switch between local Piper and Deepgram Aura 2 cloud TTS", + slash: { name: "tts-engine" }, + onSelect() { + const current = kv.get("tts.engine", "piper"); + const options = [ + { + title: "Piper (local, free, requires on-disk voice model)", + value: "piper", + onSelect() { + kv.set("tts.engine", "piper"); + toast("TTS engine: Piper (local)"); + api.ui.dialog.clear(); + }, + }, + { + title: "Deepgram Aura 2 (cloud, requires DEEPGRAM_API_KEY in env)", + value: "deepgram", + onSelect() { + kv.set("tts.engine", "deepgram"); + toast("TTS engine: Deepgram Aura 2 (cloud)"); + api.ui.dialog.clear(); + }, + }, + ]; + api.ui.dialog.replace(() => + api.ui.DialogSelect({ + title: "Select TTS engine", + current, + options, + }), + ); + }, + }, { title: "TTS: toggle voice bubbles", value: "tts.chunked", diff --git a/test/tts-deepgram.test.js b/test/tts-deepgram.test.js new file mode 100644 index 0000000..2f36ef9 --- /dev/null +++ b/test/tts-deepgram.test.js @@ -0,0 +1,132 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { + speakWithDeepgram, + DEEPGRAM_DEFAULTS, + ENCODING_BY_CONTAINER, +} from "../lib/tts-deepgram.js"; + +const ORIGINAL_FETCH = globalThis.fetch; + +function mockFetchOk(audioBytes) { + globalThis.fetch = async (url, options) => { + return { + ok: true, + status: 200, + headers: { + get(name) { + if (name.toLowerCase() === "content-type") return "audio/mpeg"; + return null; + }, + }, + async arrayBuffer() { + return new Uint8Array(audioBytes).buffer; + }, + }; + }; +} + +function mockFetchError(status, body) { + globalThis.fetch = async () => ({ + ok: false, + status, + statusText: "Bad Request", + async text() { + return body; + }, + }); +} + +function restoreFetch() { + globalThis.fetch = ORIGINAL_FETCH; +} + +test.afterEach(() => { + restoreFetch(); +}); + +test("ENCODING_BY_CONTAINER maps user-friendly names to Deepgram encodings", () => { + assert.equal(ENCODING_BY_CONTAINER.wav, "linear16"); + assert.equal(ENCODING_BY_CONTAINER.mp3, "mp3"); + assert.equal(ENCODING_BY_CONTAINER.ogg, "opus"); +}); + +test("DEEPGRAM_DEFAULTS uses aura-2-thalia-en + mp3 + DEEPGRAM_API_KEY", () => { + assert.equal(DEEPGRAM_DEFAULTS.model, "aura-2-thalia-en"); + assert.equal(DEEPGRAM_DEFAULTS.container, "mp3"); + assert.equal(DEEPGRAM_DEFAULTS.apiKeyEnv, "DEEPGRAM_API_KEY"); +}); + +test("speakWithDeepgram rejects empty text without hitting the API", async () => { + const result = await speakWithDeepgram({ text: "", apiKey: "x" }); + assert.deepEqual(result, { ok: false, reason: "empty" }); +}); + +test("speakWithDeepgram throws when API key is missing", async () => { + await assert.rejects( + () => speakWithDeepgram({ text: "hello", apiKey: null }), + /Deepgram API key not configured/, + ); +}); + +test("speakWithDeepgram surfaces non-2xx as a descriptive error", async () => { + mockFetchError(401, "Unauthorized"); + await assert.rejects( + () => + speakWithDeepgram({ + text: "hello", + apiKey: "fake", + model: "aura-2-thalia-en", + container: "mp3", + // Skip playback so we don't need ffmpeg/sox installed in the test env. + // (speak() invokes playPcmWithSox which would crash on missing sox.) + // The synth layer fails first with our 401, so this is safe. + }), + /Deepgram 401/, + ); +}); + +test("speakWithDeepgram POSTs to the right URL with auth header", async () => { + const calls = []; + globalThis.fetch = async (url, options) => { + calls.push({ url: String(url), options }); + return { + ok: true, + status: 200, + headers: { + get: () => "audio/mpeg", + }, + async arrayBuffer() { + return new Uint8Array(8).buffer; + }, + }; + }; + + // Don't actually decode or play — just intercept at fetch so we never + // shell out to ffmpeg/sox in the test env. + // We patch synthRaw by reaching through the module would be too invasive; + // instead we accept the throw from pcmStreamFromContainer when ffmpeg is + // missing and just assert the request shape. + try { + await speakWithDeepgram({ + text: "hello", + apiKey: "secret-key", + model: "aura-2-thalia-en", + container: "mp3", + }); + } catch { + // ffmpeg/sox likely missing in CI; we only care about the fetch call. + } + + assert.equal(calls.length, 1); + const { url, options } = calls[0]; + assert.match(url, /https:\/\/api\.deepgram\.com\/v1\/speak/); + assert.match(url, /model=aura-2-thalia-en/); + assert.match(url, /encoding=mp3/); + assert.equal(options.method, "POST"); + assert.equal(options.headers.Authorization, "Token secret-key"); + assert.equal(options.headers["Content-Type"], "application/json"); + const body = JSON.parse(options.body); + assert.equal(body.text, "hello"); +}); From 84c77ee229c0f011c6564d3292d1c7f5f2d34087 Mon Sep 17 00:00:00 2001 From: clls1-stinger Date: Fri, 26 Jun 2026 00:29:48 -0600 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20TTS=20control=20panel=20=E2=80=94?= =?UTF-8?q?=20clickable=20buttons=20via=20DialogSelect?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the 'type slash commands' UX with a proper TUI menu opened by the new /tts-panel command (keybind t). The panel is a 9-row DialogSelect that shows the live state of tts.engine / tts.mode / tts.chunked / tts.voice / bubble count in its title and exposes one clickable option per action: • Speak last response - trigger tts.speak-last • Stop playback - trigger tts.stop • Engine: ... (toggle) - flip piper <-> deepgram • Auto-speak: ... (toggle) • Voice bubbles: ... (toggle) • Piper voice: ... (cycle) • Browse voice bubbles - trigger tts.bubbles • Clear voice bubbles • Close panel After any toggle the panel re-opens with the updated status, so the user always sees the current state in the title before clicking again. Built on top of the v1.2.x DialogSelect primitive (no new deps, no JSX, no @opentui/solid). I tried the v2 route.register + JSX path first but @opentui/core's tree-sitter .scm asset can't be loaded from a plain Node plugin context, so DialogSelect is the right tradeoff: every option is a real clickable button in the TUI's native select widget, accessible by arrow keys + enter or filterable by typing. 29/29 tests still pass; lint + fmt clean. --- lib/tts-panel.js | 208 +++++++++++++++++++++++++++++++++++++++++++++++ lib/tts.js | 11 +++ 2 files changed, 219 insertions(+) create mode 100644 lib/tts-panel.js diff --git a/lib/tts-panel.js b/lib/tts-panel.js new file mode 100644 index 0000000..bb8c2d9 --- /dev/null +++ b/lib/tts-panel.js @@ -0,0 +1,208 @@ +// TTS Control Panel — a TUI menu of clickable buttons. +// +// Implemented as a stack of api.ui.DialogSelect dialogs (opencode's +// built-in selectable list) so it works without any extra dependencies. +// Every option in the menu is a "button" the user can fire with arrow +// keys + enter, or filter by typing. +// +// The panel is stateful: opening it after toggling a setting reflects +// the new state because each option mutates `kv` and re-opens the +// panel with the updated header. + +const ENGINE_LABELS = { + piper: "Piper (local, free)", + deepgram: "Deepgram Aura 2 (cloud)", +}; + +const VOICE_LABELS = { + ryan: "Ryan (high)", + bryce: "Bryce (medium)", +}; + +function statusHeader(kv) { + const engine = kv.get("tts.engine", "piper"); + const mode = kv.get("tts.mode", "off"); + const chunked = kv.get("tts.chunked", "on"); + const voice = kv.get("tts.voice", "ryan"); + const bubbles = (kv.get("tts.bubbles", []) || []).length; + + return [ + ` Engine : ${ENGINE_LABELS[engine] || engine}`, + ` Mode : ${mode === "on" ? "AUTO (speak on every response)" : "manual (only when you ask)"}`, + ` Bubbles: ${chunked === "on" ? "ON (WhatsApp-style chunks)" : "OFF (single blob)"}`, + ` Voice : ${VOICE_LABELS[voice] || voice}`, + ` Stored : ${bubbles} bubble${bubbles === 1 ? "" : "s"}`, + ].join("\n"); +} + +function speakAction(api, kv) { + return { + title: "▶ Speak last response", + description: "Synthesize the last assistant message right now (no auto-speak needed)", + onSelect() { + api.ui.dialog.clear(); + // Defer to the existing speakLastResponse path so we don't + // duplicate the LLM normalization + chunking logic. + api.command.trigger("tts.speak-last"); + }, + }; +} + +function stopAction(api, kv) { + return { + title: "⏹ Stop playback", + description: "Kill the current TTS process", + onSelect() { + const stopped = kv.get("__tts_was_playing__", false) ? true : false; + api.ui.dialog.clear(); + api.command.trigger("tts.stop"); + }, + }; +} + +function toggleEngineAction(api, kv, openPanel) { + const cur = kv.get("tts.engine", "piper"); + const next = cur === "piper" ? "deepgram" : "piper"; + return { + title: `▣ Engine: ${ENGINE_LABELS[cur] || cur} → click to switch`, + description: "Piper runs offline. Deepgram uses DEEPGRAM_API_KEY from env.", + onSelect() { + kv.set("tts.engine", next); + api.ui.toast({ + message: `Engine: ${next === "deepgram" ? "Deepgram Aura 2" : "Piper (local)"}`, + variant: "info", + }); + openPanel(); + }, + }; +} + +function toggleModeAction(api, kv, openPanel) { + const cur = kv.get("tts.mode", "off"); + const next = cur === "on" ? "off" : "on"; + return { + title: `◉ Auto-speak: ${cur === "on" ? "ON" : "off (manual-only)"} → click to switch`, + description: + cur === "on" + ? "Currently speaks on every session.idle. Toggle to save cycles." + : "Currently manual-only. Toggle on if you want every response spoken.", + onSelect() { + kv.set("tts.mode", next); + if (next === "off") api.command.trigger("tts.stop"); + api.ui.toast({ + message: next === "on" ? "Auto-speak on" : "Manual-only", + variant: "info", + }); + openPanel(); + }, + }; +} + +function toggleChunkedAction(api, kv, openPanel) { + const cur = kv.get("tts.chunked", "on"); + const next = cur === "on" ? "off" : "on"; + return { + title: `◐ Voice bubbles: ${cur === "on" ? "ON" : "OFF"} → click to switch`, + description: "When ON, responses are split into short WhatsApp-style bubbles.", + onSelect() { + kv.set("tts.chunked", next); + if (next === "off") api.command.trigger("tts.stop"); + api.ui.toast({ + message: next === "on" ? "Voice bubbles on" : "Voice bubbles off", + variant: "info", + }); + openPanel(); + }, + }; +} + +function toggleVoiceAction(api, kv, openPanel, voiceKeys, defaultVoice) { + const cur = kv.get("tts.voice", defaultVoice); + const idx = voiceKeys.indexOf(cur); + const next = voiceKeys[(idx + 1) % voiceKeys.length] || defaultVoice; + return { + title: `🎙 Piper voice: ${VOICE_LABELS[cur] || cur} → click to cycle`, + description: "Only used when engine=Piper. Cycles through the available voices.", + onSelect() { + kv.set("tts.voice", next); + api.ui.toast({ + message: `Voice: ${VOICE_LABELS[next] || next}`, + variant: "info", + }); + openPanel(); + }, + }; +} + +function browseBubblesAction(api, kv) { + return { + title: "💬 Browse voice bubbles", + description: "Open the last 50 bubbles (replay or clear)", + onSelect() { + api.ui.dialog.clear(); + api.command.trigger("tts.bubbles"); + }, + }; +} + +function clearBubblesAction(api, kv, openPanel) { + return { + title: "🗑 Clear voice bubbles", + description: "Erase the bubble history", + onSelect() { + const list = kv.get("tts.bubbles", []) || []; + kv.set("tts.bubbles", []); + api.ui.toast({ + message: `Cleared ${list.length} bubble${list.length === 1 ? "" : "s"}`, + variant: "info", + }); + openPanel(); + }, + }; +} + +function closeAction(api) { + return { + title: "✕ Close panel", + description: "Dismiss the TTS control panel", + onSelect() { + api.ui.dialog.clear(); + }, + }; +} + +/** + * Open the TTS Control Panel. Re-entrant: each action that mutates + * state re-calls this so the menu re-opens with fresh status. + */ +export function openTtsPanel(api, kv, deps) { + const voiceKeys = Object.keys(deps.TTS_VOICES); + const defaultVoice = deps.DEFAULT_TTS_VOICE; + const open = () => openTtsPanel(api, kv, deps); + + const options = [ + speakAction(api, kv), + stopAction(api, kv), + toggleEngineAction(api, kv, open), + toggleModeAction(api, kv, open), + toggleChunkedAction(api, kv, open), + toggleVoiceAction(api, kv, open, voiceKeys, defaultVoice), + browseBubblesAction(api, kv), + clearBubblesAction(api, kv, open), + closeAction(api), + ]; + + api.ui.dialog.replace( + () => + api.ui.DialogSelect({ + title: `TTS Control Panel\n${statusHeader(kv)}`, + current: options[0].title, + options: options.map((o) => ({ + title: o.title, + description: o.description, + onSelect: o.onSelect, + })), + }), + () => api.ui.dialog.clear(), + ); +} diff --git a/lib/tts.js b/lib/tts.js index 067a9d0..db11534 100644 --- a/lib/tts.js +++ b/lib/tts.js @@ -15,6 +15,7 @@ import { CONSTANTS as CHUNKER_CONSTANTS, } from "./tts-chunker.js"; import { speakWithDeepgram, DEEPGRAM_DEFAULTS } from "./tts-deepgram.js"; +import { openTtsPanel } from "./tts-panel.js"; const VOICES_DIR = path.join(os.homedir(), ".local", "share", "piper-voices"); @@ -597,6 +598,16 @@ export function registerTTS(api, kv, complete, prompts, logger) { toast(next === "on" ? "Voice bubbles on" : "Voice bubbles off"); }, }, + { + title: "TTS: control panel", + value: "tts.panel", + description: "Open the TTS control panel with clickable buttons", + keybind: "t", + slash: { name: "tts-panel" }, + onSelect() { + openTtsPanel(api, kv, { TTS_VOICES, DEFAULT_TTS_VOICE }); + }, + }, { title: "TTS: show voice bubbles", value: "tts.bubbles", From 98024625ae01c6970abf582e6e871f41fedf4380 Mon Sep 17 00:00:00 2001 From: clls1-stinger Date: Fri, 26 Jun 2026 00:57:34 -0600 Subject: [PATCH 4/4] feat: WhatsApp-style TTS panel via @opentui/solid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renders a real TUI screen — not a flat menu — with rounded bubble cards, ASCII waveform, timestamps, duration labels, and a per-bubble 'Play' select. Built on @opentui/solid components (box, text, select, ascii_font, scrollbox) wired through the v2 TuiPluginApi. Visual structure when t fires: - big ASCII 'TTS' header - live status line (engine / mode / bubbles / voice / count) - action bar with 4 toggles (engine, auto, bubbles, voice) - speak/stop/clear/close bar - scrollable feed of bubble cards (rounded border, ▁▂▃ waveform, timestamp, duration, play button) Used createElement / getComponentCatalogue to avoid a JSX build step since the plugin is plain ESM. Reactive state via Solid signals so any toggle re-derives the bubble list and the status header. If @opentui/core's tree-sitter .scm asset fails to load in the host runtime, the dynamic import at module init throws, renderTtsPanelV2 stays null, and /tts-panel transparently falls back to the previous DialogSelect menu. So users always have a working panel. Added @opentui/solid ^0.1.87 to dependencies. Verified the module loads cleanly under bun (opencode's runtime); the 'No renderer found' error in standalone test runs is expected — opencode provides the Solid root when it calls api.ui.dialog.replace. 29/29 tests still green, lint + fmt clean. --- lib/tts-deepgram.js | 1 - lib/tts-panel-v2.js | 299 ++++ lib/tts-panel.js | 5 +- lib/tts.js | 86 +- package-lock.json | 3329 ++++++++++++++++++++++++++++++++++++------- package.json | 3 + 6 files changed, 3196 insertions(+), 527 deletions(-) create mode 100644 lib/tts-panel-v2.js diff --git a/lib/tts-deepgram.js b/lib/tts-deepgram.js index 5df5951..d0692b5 100644 --- a/lib/tts-deepgram.js +++ b/lib/tts-deepgram.js @@ -24,7 +24,6 @@ import fs from "node:fs"; import { spawn } from "node:child_process"; -import os from "node:os"; import path from "node:path"; const DEFAULT_MODEL = "aura-2-thalia-en"; diff --git a/lib/tts-panel-v2.js b/lib/tts-panel-v2.js new file mode 100644 index 0000000..677f7a1 --- /dev/null +++ b/lib/tts-panel-v2.js @@ -0,0 +1,299 @@ +// TTS Control Panel — WhatsApp-style voice feed. +// +// Renders a full TUI screen via @opentui/solid components (box, text, +// select, ascii_font). The screen is a vertical stack of: +// 1. A big ASCII header showing the current TTS state. +// 2. An action bar with the 4 toggles (engine, mode, bubbles, voice). +// 3. A scrollable feed of voice bubbles, each rendered as a rounded +// "card" with timestamp, ASCII waveform, duration, and a "▶ Play" +// select that re-synthesizes the bubble. +// 4. A "Speak last response" button and a "Close" button at the bottom. +// +// Why factory functions instead of JSX? opencode-voice is plain ESM +// JS, and JSX requires a build step the plugin does not have. Solid +// components work as plain function calls: `box({...})` instead of +// `...`. Same reactivity (createSignal, For, Show). +// +// The whole render is re-evaluated when any piece of state changes, +// so toggling a setting from the action bar immediately re-renders +// the bubbles list and the header. + +import { getComponentCatalogue, createElement } from "@opentui/solid"; +import { createSignal, For, Show } from "solid-js"; + +const CATALOG = (() => { + try { + return getComponentCatalogue(); + } catch { + return {}; + } +})(); + +const { box, text, ascii_font, select, scrollbox } = CATALOG; + +// h(component, props, ...children) is what JSX desugars to. We call it +// explicitly because opencode-voice is plain ESM JS with no JSX +// transform; the host provides the Solid root when it calls our +// render() function via api.ui.dialog.replace. +function h(component, props, ...children) { + if (!component) return null; + return createElement(component, props, ...children); +} + +const ENGINE_LABELS = { + piper: "Piper (local)", + deepgram: "Deepgram Aura 2", +}; + +const VOICE_LABELS = { + ryan: "Ryan (high)", + bryce: "Bryce (medium)", +}; + +const WAVEFORM_BARS = "▁▂▃▄▅▆▇█"; + +function waveformFor(text, length = 20) { + // Deterministic pseudo-waveform from the text's char codes. Real audio + // amplitude would need a PCM peak reader; this is a visual stand-in + // that looks like a real voice bar and is stable for the same text. + const out = []; + let seed = 0; + for (let i = 0; i < text.length; i++) seed = (seed * 31 + text.charCodeAt(i)) >>> 0; + for (let i = 0; i < length; i++) { + seed = (seed * 1103515245 + 12345) >>> 0; + out.push(WAVEFORM_BARS[seed % WAVEFORM_BARS.length]); + } + return out.join(""); +} + +function formatTime(ts) { + return new Date(ts).toISOString().slice(11, 16); +} + +function formatDuration(seconds) { + if (!Number.isFinite(seconds) || seconds <= 0) return "0:00"; + const m = Math.floor(seconds / 60); + const s = Math.floor(seconds % 60); + return `${m}:${String(s).padStart(2, "0")}`; +} + +/** + * Render the WhatsApp-style TTS control panel. + * + * @param {object} api opencode TUI plugin api + * @param {object} kv plugin key-value store + * @param {object} actions { speak, speakBubble, stop, toggleEngine, toggleMode, toggleChunked, toggleVoice, clearBubbles, close } + */ +export function renderTtsPanelV2(api, kv, actions) { + const [tick, setTick] = createSignal(0); + const [status, setStatus] = createSignal(""); + + function rerender(msg) { + if (msg) setStatus(msg); + setTick(tick() + 1); + } + + function bubbles() { + return kv.get("tts.bubbles", []) || []; + } + + function headerLine() { + const engine = kv.get("tts.engine", "piper"); + const mode = kv.get("tts.mode", "off"); + const chunked = kv.get("tts.chunked", "on"); + const voice = kv.get("tts.voice", "ryan"); + return `engine=${engine} mode=${mode} bubbles=${chunked} voice=${voice} stored=${bubbles().length}`; + } + + return h( + box, + { + flexDirection: "column", + width: 70, + paddingLeft: 1, + paddingRight: 1, + gap: 0, + }, + + // Header + h(ascii_font, { text: "TTS", font: "block" }), + h(text, { + content: () => + `Voice Control Panel • ${ENGINE_LABELS[kv.get("tts.engine", "piper")] || kv.get("tts.engine", "piper")}`, + bold: true, + }), + h(text, { content: () => headerLine(), dim: true }), + h(text, { content: () => (status() ? ` ▸ ${status()}` : ""), dim: true }), + h(text, { content: "" }), + + // Action bar + h( + box, + { + flexDirection: "row", + gap: 1, + borderStyle: "single", + borderColor: "#444444", + paddingLeft: 1, + paddingRight: 1, + }, + h(select, { + options: () => [ + { + name: `▣ Engine: ${ENGINE_LABELS[kv.get("tts.engine", "piper")]}`, + description: "Piper local / Deepgram cloud", + value: "engine", + }, + { + name: `◉ Auto: ${kv.get("tts.mode", "off") === "on" ? "ON" : "off"}`, + description: "Auto-speak on every response", + value: "mode", + }, + { + name: `◐ Bubbles: ${kv.get("tts.chunked", "on") === "on" ? "ON" : "off"}`, + description: "WhatsApp-style chunks", + value: "chunked", + }, + { + name: `🎙 Voice: ${VOICE_LABELS[kv.get("tts.voice", "ryan")]}`, + description: "Cycle Piper voice", + value: "voice", + }, + ], + onSelect: (option) => { + if (option.value === "engine") actions.toggleEngine(() => rerender()); + else if (option.value === "mode") actions.toggleMode(() => rerender()); + else if (option.value === "chunked") actions.toggleChunked(() => rerender()); + else if (option.value === "voice") actions.toggleVoice(() => rerender()); + }, + }), + ), + + h(text, { content: "" }), + + // Speak / Stop bar + h( + box, + { flexDirection: "row", gap: 1, paddingLeft: 1, paddingRight: 1 }, + h(select, { + options: [ + { + name: "▶ Speak last response", + description: "Synthesize the last assistant message now", + value: "speak", + }, + { + name: "⏹ Stop playback", + description: "Kill the current TTS process", + value: "stop", + }, + { + name: "🗑 Clear bubble history", + description: "Erase all stored voice bubbles", + value: "clear", + }, + { + name: "✕ Close panel", + description: "Dismiss this view", + value: "close", + }, + ], + onSelect: (option) => { + if (option.value === "speak") { + rerender("Speaking last response…"); + actions.speak(() => rerender("Done")); + } else if (option.value === "stop") { + actions.stop(); + rerender("Stopped"); + } else if (option.value === "clear") { + actions.clearBubbles(); + rerender(`Cleared ${bubbles().length} bubbles`); + } else if (option.value === "close") { + actions.close(); + } + }, + }), + ), + + h(text, { content: "" }), + h(text, { content: () => `── Voice bubbles (${bubbles().length}) ──`, bold: true }), + + // Bubble feed + h(Show, { + when: () => bubbles().length === 0, + children: h(text, { + content: " (no bubbles yet — press Speak or wait for the next response)", + dim: true, + }), + }), + + h(Show, { + when: () => bubbles().length > 0, + fallback: h(text, { content: "" }), + children: h( + scrollbox, + { height: 14, scrollbar: true }, + h( + box, + { flexDirection: "column", gap: 1 }, + h(For, { + each: () => bubbles().slice().reverse(), + children: (bubble) => bubbleCard(api, kv, actions, bubble, () => rerender("")), + }), + ), + ), + }), + + h(text, { content: "" }), + h(text, { + content: " ↑/↓ to navigate • enter to select • esc to close", + dim: true, + }), + ); +} + +/** + * One voice bubble rendered as a rounded WhatsApp-style card. + * Each card shows: timestamp, ASCII waveform, duration, and a "▶ Play" + * select that re-synthesizes the bubble's text. + */ +function bubbleCard(api, kv, actions, bubble, rerender) { + return h( + box, + { + borderStyle: "rounded", + borderColor: "#00AAFF", + paddingLeft: 1, + paddingRight: 1, + flexDirection: "column", + gap: 0, + }, + h( + box, + { flexDirection: "row", justifyContent: "space-between" }, + h(text, { + content: `${formatTime(bubble.ts)} • ${bubble.source || "auto"}`, + dim: true, + }), + h(text, { content: formatDuration(bubble.duration), bold: true }), + ), + h(text, { content: ` ${waveformFor(bubble.text || "", 24)} ` }), + h( + box, + { flexDirection: "row", gap: 1 }, + h(select, { + options: [ + { + name: "▶ Play this bubble", + description: `Re-synthesize: "${(bubble.text || "").slice(0, 60)}${(bubble.text || "").length > 60 ? "…" : ""}"`, + value: "play", + }, + ], + onSelect: () => { + rerender(`Playing bubble from ${formatTime(bubble.ts)}…`); + actions.speakBubble(bubble, () => rerender("")); + }, + }), + ), + ); +} diff --git a/lib/tts-panel.js b/lib/tts-panel.js index bb8c2d9..0f87d4d 100644 --- a/lib/tts-panel.js +++ b/lib/tts-panel.js @@ -35,7 +35,7 @@ function statusHeader(kv) { ].join("\n"); } -function speakAction(api, kv) { +function speakAction(api, _kv) { return { title: "▶ Speak last response", description: "Synthesize the last assistant message right now (no auto-speak needed)", @@ -48,12 +48,11 @@ function speakAction(api, kv) { }; } -function stopAction(api, kv) { +function stopAction(api, _kv) { return { title: "⏹ Stop playback", description: "Kill the current TTS process", onSelect() { - const stopped = kv.get("__tts_was_playing__", false) ? true : false; api.ui.dialog.clear(); api.command.trigger("tts.stop"); }, diff --git a/lib/tts.js b/lib/tts.js index db11534..c449263 100644 --- a/lib/tts.js +++ b/lib/tts.js @@ -17,6 +17,20 @@ import { import { speakWithDeepgram, DEEPGRAM_DEFAULTS } from "./tts-deepgram.js"; import { openTtsPanel } from "./tts-panel.js"; +// Load the v2 WhatsApp-style panel at module init. If @opentui/solid or +// its tree-sitter highlight asset can't be loaded in the current opencode +// runtime, we degrade silently and fall back to the DialogSelect panel. +let renderTtsPanelV2 = null; +try { + const mod = await import("./tts-panel-v2.js"); + renderTtsPanelV2 = mod.renderTtsPanelV2; +} catch (err) { + // .scm asset or solid renderer unavailable — fall back. + if (typeof console !== "undefined" && console.warn) { + console.warn(`[opencode-voice] v2 panel unavailable: ${err.message}`); + } +} + const VOICES_DIR = path.join(os.homedir(), ".local", "share", "piper-voices"); const TTS_VOICES = { @@ -488,6 +502,74 @@ export function registerTTS(api, kv, complete, prompts, logger) { // ---- Commands ---- + function openTtsPanelAny() { + if (renderTtsPanelV2) { + try { + const element = renderTtsPanelV2(api, kv, { + speak: (done) => { + speakLastResponse().finally(done); + }, + speakBubble: (bubble, done) => { + replayBubble(bubble).finally(done); + }, + stop: () => { + if (stopSpeech()) toast("TTS stopped"); + }, + toggleEngine: (done) => { + const cur = kv.get("tts.engine", "piper"); + const next = cur === "piper" ? "deepgram" : "piper"; + kv.set("tts.engine", next); + toast(`Engine: ${next === "deepgram" ? "Deepgram Aura 2" : "Piper (local)"}`); + done(); + }, + toggleMode: (done) => { + const cur = kv.get("tts.mode", "off"); + const next = cur === "on" ? "off" : "on"; + kv.set("tts.mode", next); + if (next === "off") stopSpeech(); + toast(next === "on" ? "Auto-speak on" : "Manual-only"); + done(); + }, + toggleChunked: (done) => { + const cur = kv.get("tts.chunked", "on"); + const next = cur === "on" ? "off" : "on"; + kv.set("tts.chunked", next); + if (next === "off") stopSpeech(); + toast(next === "on" ? "Voice bubbles on" : "Voice bubbles off"); + done(); + }, + toggleVoice: (done) => { + const cur = kv.get("tts.voice", DEFAULT_TTS_VOICE); + const keys = Object.keys(TTS_VOICES); + const idx = keys.indexOf(cur); + const next = keys[(idx + 1) % keys.length] || DEFAULT_TTS_VOICE; + kv.set("tts.voice", next); + const entry = TTS_VOICES[next] || TTS_VOICES[DEFAULT_TTS_VOICE]; + toast(`Voice: ${entry.label}`); + done(); + }, + clearBubbles: () => { + const list = kv.get("tts.bubbles", []) || []; + clearBubbles(kv); + toast(`Cleared ${list.length} bubble${list.length === 1 ? "" : "s"}`); + }, + close: () => api.ui.dialog.clear(), + }); + if (element) { + api.ui.dialog.replace( + () => element, + () => api.ui.dialog.clear(), + ); + return; + } + } catch (err) { + logger?.log?.("TTS", `v2 panel render failed: ${err.message}`, "warn"); + } + } + // Fallback: high-level DialogSelect panel (no @opentui deps). + openTtsPanel(api, kv, { TTS_VOICES, DEFAULT_TTS_VOICE }); + } + return [ { title: "TTS: speak last response", @@ -601,11 +683,11 @@ export function registerTTS(api, kv, complete, prompts, logger) { { title: "TTS: control panel", value: "tts.panel", - description: "Open the TTS control panel with clickable buttons", + description: "Open the TTS control panel with WhatsApp-style voice feed", keybind: "t", slash: { name: "tts-panel" }, onSelect() { - openTtsPanel(api, kv, { TTS_VOICES, DEFAULT_TTS_VOICE }); + openTtsPanelAny(); }, }, { diff --git a/package-lock.json b/package-lock.json index 9d70b6f..2404c20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,705 +8,2432 @@ "name": "@renjfk/opencode-voice", "version": "0.1.0", "license": "MIT", + "dependencies": { + "@opentui/solid": "^0.1.87" + }, "devDependencies": { "oxfmt": "0.50.0", "oxlint": "1.65.0" } }, - "node_modules/@oxfmt/binding-android-arm-eabi": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm-eabi/-/binding-android-arm-eabi-0.50.0.tgz", - "integrity": "sha512-ICXQVKrDvsWUtfx6EiVJxfWrajKTwTfRV8vz2XiMkxZeuCKJLgD4YAj6dE3BWvpqDlkVkie4VSTAtMUWO9LDXg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.0.0" } }, - "node_modules/@oxfmt/binding-android-arm64": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm64/-/binding-android-arm64-0.50.0.tgz", - "integrity": "sha512-quwjLQFkuW6OwLHeDeIXsTzOmipQFQbqsYN9HLk2B5I01IlAQZHP1UiLIg0O7pP+dUgPD2AD7SCYA3gs6NH5/g==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@oxfmt/binding-darwin-arm64": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-arm64/-/binding-darwin-arm64-0.50.0.tgz", - "integrity": "sha512-ikU5umElcMi78/TNI334wtjr5WZ5F4nWa1aIDseAKKGL0W3ygxeYKkrIJ0fggWa8MOon66BmG3xCqmX1m9YAOw==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@oxfmt/binding-darwin-x64": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-x64/-/binding-darwin-x64-0.50.0.tgz", - "integrity": "sha512-WT4MOYG4mv9IXrH0m60vHsJh+rRMPSOKTQmwDpwmgQ+DuW/i5dU4pqc0HDO5uclO5vjz5IFX5z/taW86LSVe/g==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@babel/core": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", + "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@oxfmt/binding-freebsd-x64": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-freebsd-x64/-/binding-freebsd-x64-0.50.0.tgz", - "integrity": "sha512-gH0rycVXqV4juWkvLs2uPMtTyppDc7qEUVzXAxnQ7FpcSZNXqKowUgtjH8q67ngj416r8+4NnAlyR/D35zwwhQ==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@oxfmt/binding-linux-arm-gnueabihf": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.50.0.tgz", - "integrity": "sha512-wL/k+o0hiTeRvi/gPzeC1L/yTHTXIeHDKWU09s2zTBmv7ma59wTm+fADNSGYxhJQDxyavQbwTf1QpW3Zj924tQ==", - "cpu": [ - "arm" - ], - "dev": true, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/types": "^7.29.7" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@oxfmt/binding-linux-arm-musleabihf": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.50.0.tgz", - "integrity": "sha512-Y59FKqoUM3Gf00E395b4ixfWyJGwO2GzaZawF5MZoVWcb3f6CkWUXyao0jyOvoIxDMzMybcVRuXyG7ih/Nxweg==", - "cpu": [ - "arm" - ], - "dev": true, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@oxfmt/binding-linux-arm64-gnu": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.50.0.tgz", - "integrity": "sha512-OvXbfTjMignXWyJXg/NOFsiy996vFe8wb9tkxJaUq8ylq0XrzJg3ttavC5Tcmm6F8/GUs2r3XFJWWu9q/27uYw==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.7", + "semver": "^6.3.1" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@oxfmt/binding-linux-arm64-musl": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.50.0.tgz", - "integrity": "sha512-rqmvHZm7vMa3NLYa0khwkhReCmp9tqKnF23TFZ7S5cYJLvIE4b0k8famWE7kO897/DXznJe675n5SohFBggbxA==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@oxfmt/binding-linux-ppc64-gnu": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.50.0.tgz", - "integrity": "sha512-49bAdYbMSde42tzPDtuHnBWzOgmoS0PT9THCjvMnDVYMQYiHzPc2Mv5rkpBHVQOXM+PHfafJlxgK0anXSWBVvw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "libc": [ - "glibc" - ], + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@oxfmt/binding-linux-riscv64-gnu": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.50.0.tgz", - "integrity": "sha512-VFT25/6kckkIM62KeWB2bi+xCEmC/zC+DcMaIpEfaio8ulkGDLSiTz11TyK0eqgTl3x5OklYEGDWohvAgOr8Bw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "libc": [ - "glibc" - ], + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@oxfmt/binding-linux-riscv64-musl": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.50.0.tgz", - "integrity": "sha512-BBJMuNy6jjkXjUUINF5UTQqb/nvjmtJad43Gp7bab0AAURAdthhJvduR7rHpWInpWYiaMzYsdrmURNcrmpxdZA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "libc": [ - "musl" - ], + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@oxfmt/binding-linux-s390x-gnu": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.50.0.tgz", - "integrity": "sha512-Xd4y+yjAYHKmryXhyUUwbyRD01iKfcvI74iE01L6p4F8SwjhZQXDshK+T8PcrPZLiFqH263P5xqJk94amjkjzQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "libc": [ - "glibc" - ], + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/types": "^7.29.7" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@oxfmt/binding-linux-x64-gnu": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.50.0.tgz", - "integrity": "sha512-Qp96rYJru7l++7mk4R+eh8qq9GFfFAMdmoN6VGoRHI8AA1XMnUIzH4u+zOcKZZwY+irHdsaBldDearwB4nOH7A==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@oxfmt/binding-linux-x64-musl": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-musl/-/binding-linux-x64-musl-0.50.0.tgz", - "integrity": "sha512-5XLGp+yd5w2Key5LMqJO+X3XVsJKgeeUKljy32+MBF/J/JZ5m8WHl6dI5eOQOr3ixopxPiXIyDAxn3slI3UXiQ==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], + "node_modules/@babel/helper-replace-supers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@oxfmt/binding-openharmony-arm64": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-openharmony-arm64/-/binding-openharmony-arm64-0.50.0.tgz", - "integrity": "sha512-QAxwzh7+GHugCD7WuERolVs8TKQwXNIAZXAHHTecbKVc9oWBkWzOiLauQuezXS57tVcof5zhi1IjZ8tOV0htTg==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@oxfmt/binding-win32-arm64-msvc": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.50.0.tgz", - "integrity": "sha512-3nKN/kqClm9iCFWTwtJ9UpR5SGyExp5l3nw6uIiBt+3XitQtszin+vjHrL7JHfDksZ7Svigdaow2zqz/IKCfqw==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@oxfmt/binding-win32-ia32-msvc": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.50.0.tgz", - "integrity": "sha512-3r6XZ8+X6qlLbXaPW2NygfiAWSpKbkE36pAVzS83mY+cYY+pSMalJ+qnCgkr92tr+Iqv988XKQ1CpARTg9ITbQ==", - "cpu": [ - "ia32" - ], - "dev": true, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@oxfmt/binding-win32-x64-msvc": { - "version": "0.50.0", - "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.50.0.tgz", - "integrity": "sha512-BSE8D8KsvquMG9vU+Qt4qGuoOcZ36rxU5S6ZkHNguj+MlWkXWCBETnno3yJ9CfWvfCrbmieaN9LK6hdcdHNZ/w==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@oxlint/binding-android-arm-eabi": { - "version": "1.65.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.65.0.tgz", - "integrity": "sha512-jDVaGNURT5pEA9qcabh6WusIoBNybOMMDPCx+EFt+gxo6rVvoUf0+73Xy5x81+ZrxU+ewk5uRBYifjy5pgkcnA==", - "cpu": [ - "arm" - ], - "dev": true, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@oxlint/binding-android-arm64": { - "version": "1.65.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.65.0.tgz", - "integrity": "sha512-v0z80IWNA7c9RhUydq9YprBxCVZrQ6Ixls2tdxUC1F/1FFqSfa7xTX+EJf0mj6+BKRg2zWXqWfcbJUnETlLlIw==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.0.0" } }, - "node_modules/@oxlint/binding-darwin-arm64": { - "version": "1.65.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.65.0.tgz", - "integrity": "sha512-pL/mG/5gMzBwp1gdc5+Cwi87F9j3XRnPxHGyVj5Zd+dCEV5YkKt0L70PB3EGmEEHxgn4H+jnMS3xLuXs6mZW/Q==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@oxlint/binding-darwin-x64": { - "version": "1.65.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.65.0.tgz", - "integrity": "sha512-jVTneaeuHtqTrKYnhrdH1buhnSorinvpy1sv43ayclfWx/e/DfdRWv+h1fopJcHQbYr5WMcZMmDvnfEBkPZ+1A==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@oxlint/binding-freebsd-x64": { - "version": "1.65.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.65.0.tgz", - "integrity": "sha512-8lJQ7B6RloYDUhwVdbSpwT2eKsCN5KP1Scn18ly1tytCuhXhbs0nkfKHT4jWWZBJqmynWuzd+78bF7wILrj6pw==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", + "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@oxlint/binding-linux-arm-gnueabihf": { - "version": "1.65.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.65.0.tgz", - "integrity": "sha512-EgmZY+DeWhLLEnNl70/49j3ltA8I6X9kxMfexupWi2Vwfp6RonGsBaHtGoedLolaU37ne7eDUgoxa3CFB95GZA==", - "cpu": [ - "arm" - ], - "dev": true, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", + "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-syntax-typescript": "^7.29.7" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@oxlint/binding-linux-arm-musleabihf": { - "version": "1.65.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.65.0.tgz", - "integrity": "sha512-OJMWmAYRVBCPPxnYr3j5sXRwHPh1bAuMlTStGco1Z8q3HkvSH4h+A10E9MiRNYmLhUuli5a2P5wmfj8cagiF5Q==", - "cpu": [ - "arm" - ], - "dev": true, + "node_modules/@babel/preset-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@oxlint/binding-linux-arm64-gnu": { - "version": "1.65.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.65.0.tgz", - "integrity": "sha512-D8uNi50LsYKgS0vGARZDRx05TBZeSxAVdLGddSEqQLSU7xsiqdImHPEw55xq8sKA5rCc/4au/5uS7FQALWdLCg==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@oxlint/binding-linux-arm64-musl": { - "version": "1.65.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.65.0.tgz", - "integrity": "sha512-IpbA8QGbwFehQhO+YaHwmoI81f93xvywpspf8HrdPCWOIeKwYfM1dhVhO4YKfZewTRRQEPY/JFjTOXTgkwhKrA==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@oxlint/binding-linux-ppc64-gnu": { - "version": "1.65.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.65.0.tgz", - "integrity": "sha512-ZSe8HgaZdgyHSv2+/pTG68z10+OarB18CkFKQOhRs3lmmP/p2vuigedK2e9d0ztoG2DU/duJzhxXBSjy/492HQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "libc": [ - "glibc" - ], + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=6.9.0" } }, - "node_modules/@oxlint/binding-linux-riscv64-gnu": { - "version": "1.65.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.65.0.tgz", - "integrity": "sha512-DcTERf++v6HyPHukKAr0JFTRqB+YeDEvqzRgNDMaz7jITPf+tlJIwRxodlAqoXMYhNVEZhXdQM5RAAYH8/oPuw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "libc": [ - "glibc" - ], + "node_modules/@dimforge/rapier2d-simd-compat": { + "version": "0.17.3", + "resolved": "https://registry.npmjs.org/@dimforge/rapier2d-simd-compat/-/rapier2d-simd-compat-0.17.3.tgz", + "integrity": "sha512-bijvwWz6NHsNj5e5i1vtd3dU2pDhthSaTUZSh14DUGGKJfw8eMnlWZsxwHBxB/a3AXVNDjL9abuHw1k9FGR+jg==", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/@jimp/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.6.0.tgz", + "integrity": "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@jimp/file-ops": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "await-to-js": "^3.0.0", + "exif-parser": "^0.1.12", + "file-type": "^16.0.0", + "mime": "3" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/@oxlint/binding-linux-riscv64-musl": { - "version": "1.65.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.65.0.tgz", - "integrity": "sha512-xjhMwuFJwRh40NOBzol4gM5gqAa0xPCJU+GQLM6BydV8TbfkIA7JeyCFNhyfbE9Q/5EWcKYTx62R0cRcjP7DAA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "libc": [ - "musl" - ], + "node_modules/@jimp/diff": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.6.0.tgz", + "integrity": "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "pixelmatch": "^5.3.0" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, - "node_modules/@oxlint/binding-linux-s390x-gnu": { - "version": "1.65.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.65.0.tgz", - "integrity": "sha512-lrWSXb8JzboPWYBG6Kunt/eemvjo2oCFXktShsm3yMToY7HjzKLjxh7CljSvGnnZH9oohNFHOKc9xYpGKCPm6w==", + "node_modules/@jimp/file-ops": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.6.0.tgz", + "integrity": "sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-bmp": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.6.0.tgz", + "integrity": "sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "bmp-ts": "^1.0.9" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-gif": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.6.0.tgz", + "integrity": "sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "gifwrap": "^0.10.1", + "omggif": "^1.0.10" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-jpeg": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.6.0.tgz", + "integrity": "sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "jpeg-js": "^0.4.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-png": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.6.0.tgz", + "integrity": "sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "pngjs": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/js-tiff": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.6.0.tgz", + "integrity": "sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "utif2": "^4.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-blit": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.6.0.tgz", + "integrity": "sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-blur": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.6.0.tgz", + "integrity": "sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/utils": "1.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-circle": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.6.0.tgz", + "integrity": "sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-color": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.6.0.tgz", + "integrity": "sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "tinycolor2": "^1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-contain": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.6.0.tgz", + "integrity": "sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/plugin-blit": "1.6.0", + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-cover": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.6.0.tgz", + "integrity": "sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/plugin-crop": "1.6.0", + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-crop": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.6.0.tgz", + "integrity": "sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-displace": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.6.0.tgz", + "integrity": "sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-dither": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.6.0.tgz", + "integrity": "sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-fisheye": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.6.0.tgz", + "integrity": "sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-flip": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.6.0.tgz", + "integrity": "sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-hash": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.6.0.tgz", + "integrity": "sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/js-bmp": "1.6.0", + "@jimp/js-jpeg": "1.6.0", + "@jimp/js-png": "1.6.0", + "@jimp/js-tiff": "1.6.0", + "@jimp/plugin-color": "1.6.0", + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "any-base": "^1.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-mask": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.6.0.tgz", + "integrity": "sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-print": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.6.0.tgz", + "integrity": "sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/js-jpeg": "1.6.0", + "@jimp/js-png": "1.6.0", + "@jimp/plugin-blit": "1.6.0", + "@jimp/types": "1.6.0", + "parse-bmfont-ascii": "^1.0.6", + "parse-bmfont-binary": "^1.0.6", + "parse-bmfont-xml": "^1.1.6", + "simple-xml-to-json": "^1.2.2", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-quantize": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.6.0.tgz", + "integrity": "sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg==", + "license": "MIT", + "dependencies": { + "image-q": "^4.0.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-resize": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.0.tgz", + "integrity": "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/types": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-rotate": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.6.0.tgz", + "integrity": "sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/plugin-crop": "1.6.0", + "@jimp/plugin-resize": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/plugin-threshold": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.6.0.tgz", + "integrity": "sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/plugin-color": "1.6.0", + "@jimp/plugin-hash": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/types": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.6.0.tgz", + "integrity": "sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg==", + "license": "MIT", + "dependencies": { + "zod": "^3.23.8" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jimp/utils": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.6.0.tgz", + "integrity": "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA==", + "license": "MIT", + "dependencies": { + "@jimp/types": "1.6.0", + "tinycolor2": "^1.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@opentui/core": { + "version": "0.1.87", + "resolved": "https://registry.npmjs.org/@opentui/core/-/core-0.1.87.tgz", + "integrity": "sha512-dhsmMv0IqKftwG7J/pBrLBj2armsYIg5R3LBvciRQI/6X89GufP4l1u0+QTACAx6iR4SYJJNVNQ2tdX8LM9rMw==", + "license": "MIT", + "dependencies": { + "bun-ffi-structs": "0.1.2", + "diff": "8.0.2", + "jimp": "1.6.0", + "marked": "17.0.1", + "yoga-layout": "3.2.1" + }, + "optionalDependencies": { + "@dimforge/rapier2d-simd-compat": "^0.17.3", + "@opentui/core-darwin-arm64": "0.1.87", + "@opentui/core-darwin-x64": "0.1.87", + "@opentui/core-linux-arm64": "0.1.87", + "@opentui/core-linux-x64": "0.1.87", + "@opentui/core-win32-arm64": "0.1.87", + "@opentui/core-win32-x64": "0.1.87", + "bun-webgpu": "0.1.5", + "planck": "^1.4.2", + "three": "0.177.0" + }, + "peerDependencies": { + "web-tree-sitter": "0.25.10" + } + }, + "node_modules/@opentui/core-darwin-arm64": { + "version": "0.1.87", + "resolved": "https://registry.npmjs.org/@opentui/core-darwin-arm64/-/core-darwin-arm64-0.1.87.tgz", + "integrity": "sha512-G8oq85diOfkU6n0T1CxCle7oDmpKxwhcdhZ9khBMU5IrfLx9ZDuCM3F6MsiRQWdvPPCq2oomNbd64bYkPamYgw==", "cpu": [ - "s390x" - ], - "dev": true, - "libc": [ - "glibc" + "arm64" ], "license": "MIT", "optional": true, "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + "darwin" + ] }, - "node_modules/@oxlint/binding-linux-x64-gnu": { - "version": "1.65.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.65.0.tgz", - "integrity": "sha512-A7xfghw250m4a1sPV+q44Mow2G5bhiC9FBvhAuIhJS6QovWnqzuL5AFQPEuwOB+PM4DhABkqxVa3Iwe3Y/nFlQ==", + "node_modules/@opentui/core-darwin-x64": { + "version": "0.1.87", + "resolved": "https://registry.npmjs.org/@opentui/core-darwin-x64/-/core-darwin-x64-0.1.87.tgz", + "integrity": "sha512-MYTFQfOHm6qO7YaY4GHK9u/oJlXY6djaaxl5I+k4p2mk3vvuFIl/AP1ypITwBFjyV5gyp7PRWFp4nGfY9oN8bw==", "cpu": [ "x64" ], - "dev": true, - "libc": [ - "glibc" + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@opentui/core-linux-arm64": { + "version": "0.1.87", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-arm64/-/core-linux-arm64-0.1.87.tgz", + "integrity": "sha512-he8o1h5M6oskRJ7wE+xKJgmWnv5ZwN6gB3M/Z+SeHtOMPa5cZmi3TefTjG54llEgFfx0F9RcqHof7TJ/GNxRkw==", + "cpu": [ + "arm64" ], "license": "MIT", "optional": true, "os": [ "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + ] }, - "node_modules/@oxlint/binding-linux-x64-musl": { - "version": "1.65.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.65.0.tgz", - "integrity": "sha512-reqOun1+pWO3fW6cv7bsa8hHG0TN3t/82qPdaoJo90FwugXiMjKhZMChmH5Z01cFNRHmxN4+543Fy8478cM/iA==", + "node_modules/@opentui/core-linux-x64": { + "version": "0.1.87", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-x64/-/core-linux-x64-0.1.87.tgz", + "integrity": "sha512-aiUwjPlH4yDcB8/6YDKSmMkaoGAAltL0Xo0AzXyAtJXWK5tkCSaYjEVwzJ/rYRkr4Magnad+Mjth4AQUWdR2AA==", "cpu": [ "x64" ], - "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } + ] }, - "node_modules/@oxlint/binding-openharmony-arm64": { - "version": "1.65.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.65.0.tgz", - "integrity": "sha512-KQpqOb/juDBO0xyloDkVDhOVxDUgAfZ2OAAVq99TJScJDzT319xry1QzB9LQohV9QGnA7p6m/XATZkMXc84lwA==", + "node_modules/@opentui/core-win32-arm64": { + "version": "0.1.87", + "resolved": "https://registry.npmjs.org/@opentui/core-win32-arm64/-/core-win32-arm64-0.1.87.tgz", + "integrity": "sha512-cmP0pOyREjWGniHqbDmaMY7U+1AyagrD8VseJbU0cGpNgVpG2/gbrJUGdfdLB0SNb+mzLdx6SOjdxtrElwRCQA==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ - "openharmony" + "win32" + ] + }, + "node_modules/@opentui/core-win32-x64": { + "version": "0.1.87", + "resolved": "https://registry.npmjs.org/@opentui/core-win32-x64/-/core-win32-x64-0.1.87.tgz", + "integrity": "sha512-N2GErAAP8iODf2RPp86pilPaVKiD6G4pkpZL5nLGbKsl0bndrVTpSqZcn8+/nQwFZDPD/AsiRTYNOfWOblhzOw==", + "cpu": [ + "x64" ], - "engines": { - "node": "^20.19.0 || >=22.12.0" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@opentui/solid": { + "version": "0.1.87", + "resolved": "https://registry.npmjs.org/@opentui/solid/-/solid-0.1.87.tgz", + "integrity": "sha512-lRT9t30l8+FtgOjjWJcdb2MT6hP8/RKqwGgYwTI7fXrOqdhxxwdP2SM+rH2l3suHeASheiTdlvPAo230iUcsvg==", + "license": "MIT", + "dependencies": { + "@babel/core": "7.28.0", + "@babel/preset-typescript": "7.27.1", + "@opentui/core": "0.1.87", + "babel-plugin-module-resolver": "5.0.2", + "babel-preset-solid": "1.9.9", + "entities": "7.0.1", + "s-js": "^0.4.9" + }, + "peerDependencies": { + "solid-js": "1.9.9" } }, - "node_modules/@oxlint/binding-win32-arm64-msvc": { - "version": "1.65.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.65.0.tgz", - "integrity": "sha512-xfqcOc3nJFeAd1kDY4T9d3XeJIhr00twaaW0kOAzGPyUHkruXtNJv6zz1Ra9fRtSek5VpW2Yoj5AcwPIlT0ZiQ==", + "node_modules/@oxfmt/binding-android-arm-eabi": { + "version": "0.50.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm-eabi/-/binding-android-arm-eabi-0.50.0.tgz", + "integrity": "sha512-ICXQVKrDvsWUtfx6EiVJxfWrajKTwTfRV8vz2XiMkxZeuCKJLgD4YAj6dE3BWvpqDlkVkie4VSTAtMUWO9LDXg==", "cpu": [ - "arm64" + "arm" ], "dev": true, "license": "MIT", "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-android-arm64": { + "version": "0.50.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm64/-/binding-android-arm64-0.50.0.tgz", + "integrity": "sha512-quwjLQFkuW6OwLHeDeIXsTzOmipQFQbqsYN9HLk2B5I01IlAQZHP1UiLIg0O7pP+dUgPD2AD7SCYA3gs6NH5/g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-darwin-arm64": { + "version": "0.50.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-arm64/-/binding-darwin-arm64-0.50.0.tgz", + "integrity": "sha512-ikU5umElcMi78/TNI334wtjr5WZ5F4nWa1aIDseAKKGL0W3ygxeYKkrIJ0fggWa8MOon66BmG3xCqmX1m9YAOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-darwin-x64": { + "version": "0.50.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-x64/-/binding-darwin-x64-0.50.0.tgz", + "integrity": "sha512-WT4MOYG4mv9IXrH0m60vHsJh+rRMPSOKTQmwDpwmgQ+DuW/i5dU4pqc0HDO5uclO5vjz5IFX5z/taW86LSVe/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-freebsd-x64": { + "version": "0.50.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-freebsd-x64/-/binding-freebsd-x64-0.50.0.tgz", + "integrity": "sha512-gH0rycVXqV4juWkvLs2uPMtTyppDc7qEUVzXAxnQ7FpcSZNXqKowUgtjH8q67ngj416r8+4NnAlyR/D35zwwhQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-arm-gnueabihf": { + "version": "0.50.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.50.0.tgz", + "integrity": "sha512-wL/k+o0hiTeRvi/gPzeC1L/yTHTXIeHDKWU09s2zTBmv7ma59wTm+fADNSGYxhJQDxyavQbwTf1QpW3Zj924tQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-arm-musleabihf": { + "version": "0.50.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.50.0.tgz", + "integrity": "sha512-Y59FKqoUM3Gf00E395b4ixfWyJGwO2GzaZawF5MZoVWcb3f6CkWUXyao0jyOvoIxDMzMybcVRuXyG7ih/Nxweg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-arm64-gnu": { + "version": "0.50.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.50.0.tgz", + "integrity": "sha512-OvXbfTjMignXWyJXg/NOFsiy996vFe8wb9tkxJaUq8ylq0XrzJg3ttavC5Tcmm6F8/GUs2r3XFJWWu9q/27uYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-arm64-musl": { + "version": "0.50.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.50.0.tgz", + "integrity": "sha512-rqmvHZm7vMa3NLYa0khwkhReCmp9tqKnF23TFZ7S5cYJLvIE4b0k8famWE7kO897/DXznJe675n5SohFBggbxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-ppc64-gnu": { + "version": "0.50.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.50.0.tgz", + "integrity": "sha512-49bAdYbMSde42tzPDtuHnBWzOgmoS0PT9THCjvMnDVYMQYiHzPc2Mv5rkpBHVQOXM+PHfafJlxgK0anXSWBVvw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-riscv64-gnu": { + "version": "0.50.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.50.0.tgz", + "integrity": "sha512-VFT25/6kckkIM62KeWB2bi+xCEmC/zC+DcMaIpEfaio8ulkGDLSiTz11TyK0eqgTl3x5OklYEGDWohvAgOr8Bw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-riscv64-musl": { + "version": "0.50.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.50.0.tgz", + "integrity": "sha512-BBJMuNy6jjkXjUUINF5UTQqb/nvjmtJad43Gp7bab0AAURAdthhJvduR7rHpWInpWYiaMzYsdrmURNcrmpxdZA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-s390x-gnu": { + "version": "0.50.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.50.0.tgz", + "integrity": "sha512-Xd4y+yjAYHKmryXhyUUwbyRD01iKfcvI74iE01L6p4F8SwjhZQXDshK+T8PcrPZLiFqH263P5xqJk94amjkjzQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-x64-gnu": { + "version": "0.50.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.50.0.tgz", + "integrity": "sha512-Qp96rYJru7l++7mk4R+eh8qq9GFfFAMdmoN6VGoRHI8AA1XMnUIzH4u+zOcKZZwY+irHdsaBldDearwB4nOH7A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-x64-musl": { + "version": "0.50.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-musl/-/binding-linux-x64-musl-0.50.0.tgz", + "integrity": "sha512-5XLGp+yd5w2Key5LMqJO+X3XVsJKgeeUKljy32+MBF/J/JZ5m8WHl6dI5eOQOr3ixopxPiXIyDAxn3slI3UXiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-openharmony-arm64": { + "version": "0.50.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-openharmony-arm64/-/binding-openharmony-arm64-0.50.0.tgz", + "integrity": "sha512-QAxwzh7+GHugCD7WuERolVs8TKQwXNIAZXAHHTecbKVc9oWBkWzOiLauQuezXS57tVcof5zhi1IjZ8tOV0htTg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-win32-arm64-msvc": { + "version": "0.50.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.50.0.tgz", + "integrity": "sha512-3nKN/kqClm9iCFWTwtJ9UpR5SGyExp5l3nw6uIiBt+3XitQtszin+vjHrL7JHfDksZ7Svigdaow2zqz/IKCfqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-win32-ia32-msvc": { + "version": "0.50.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.50.0.tgz", + "integrity": "sha512-3r6XZ8+X6qlLbXaPW2NygfiAWSpKbkE36pAVzS83mY+cYY+pSMalJ+qnCgkr92tr+Iqv988XKQ1CpARTg9ITbQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-win32-x64-msvc": { + "version": "0.50.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.50.0.tgz", + "integrity": "sha512-BSE8D8KsvquMG9vU+Qt4qGuoOcZ36rxU5S6ZkHNguj+MlWkXWCBETnno3yJ9CfWvfCrbmieaN9LK6hdcdHNZ/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-android-arm-eabi": { + "version": "1.65.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.65.0.tgz", + "integrity": "sha512-jDVaGNURT5pEA9qcabh6WusIoBNybOMMDPCx+EFt+gxo6rVvoUf0+73Xy5x81+ZrxU+ewk5uRBYifjy5pgkcnA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-android-arm64": { + "version": "1.65.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.65.0.tgz", + "integrity": "sha512-v0z80IWNA7c9RhUydq9YprBxCVZrQ6Ixls2tdxUC1F/1FFqSfa7xTX+EJf0mj6+BKRg2zWXqWfcbJUnETlLlIw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-arm64": { + "version": "1.65.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.65.0.tgz", + "integrity": "sha512-pL/mG/5gMzBwp1gdc5+Cwi87F9j3XRnPxHGyVj5Zd+dCEV5YkKt0L70PB3EGmEEHxgn4H+jnMS3xLuXs6mZW/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-x64": { + "version": "1.65.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.65.0.tgz", + "integrity": "sha512-jVTneaeuHtqTrKYnhrdH1buhnSorinvpy1sv43ayclfWx/e/DfdRWv+h1fopJcHQbYr5WMcZMmDvnfEBkPZ+1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-freebsd-x64": { + "version": "1.65.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.65.0.tgz", + "integrity": "sha512-8lJQ7B6RloYDUhwVdbSpwT2eKsCN5KP1Scn18ly1tytCuhXhbs0nkfKHT4jWWZBJqmynWuzd+78bF7wILrj6pw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-gnueabihf": { + "version": "1.65.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.65.0.tgz", + "integrity": "sha512-EgmZY+DeWhLLEnNl70/49j3ltA8I6X9kxMfexupWi2Vwfp6RonGsBaHtGoedLolaU37ne7eDUgoxa3CFB95GZA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-musleabihf": { + "version": "1.65.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.65.0.tgz", + "integrity": "sha512-OJMWmAYRVBCPPxnYr3j5sXRwHPh1bAuMlTStGco1Z8q3HkvSH4h+A10E9MiRNYmLhUuli5a2P5wmfj8cagiF5Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-gnu": { + "version": "1.65.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.65.0.tgz", + "integrity": "sha512-D8uNi50LsYKgS0vGARZDRx05TBZeSxAVdLGddSEqQLSU7xsiqdImHPEw55xq8sKA5rCc/4au/5uS7FQALWdLCg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-musl": { + "version": "1.65.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.65.0.tgz", + "integrity": "sha512-IpbA8QGbwFehQhO+YaHwmoI81f93xvywpspf8HrdPCWOIeKwYfM1dhVhO4YKfZewTRRQEPY/JFjTOXTgkwhKrA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-ppc64-gnu": { + "version": "1.65.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.65.0.tgz", + "integrity": "sha512-ZSe8HgaZdgyHSv2+/pTG68z10+OarB18CkFKQOhRs3lmmP/p2vuigedK2e9d0ztoG2DU/duJzhxXBSjy/492HQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-gnu": { + "version": "1.65.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.65.0.tgz", + "integrity": "sha512-DcTERf++v6HyPHukKAr0JFTRqB+YeDEvqzRgNDMaz7jITPf+tlJIwRxodlAqoXMYhNVEZhXdQM5RAAYH8/oPuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-musl": { + "version": "1.65.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.65.0.tgz", + "integrity": "sha512-xjhMwuFJwRh40NOBzol4gM5gqAa0xPCJU+GQLM6BydV8TbfkIA7JeyCFNhyfbE9Q/5EWcKYTx62R0cRcjP7DAA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-s390x-gnu": { + "version": "1.65.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.65.0.tgz", + "integrity": "sha512-lrWSXb8JzboPWYBG6Kunt/eemvjo2oCFXktShsm3yMToY7HjzKLjxh7CljSvGnnZH9oohNFHOKc9xYpGKCPm6w==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-gnu": { + "version": "1.65.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.65.0.tgz", + "integrity": "sha512-A7xfghw250m4a1sPV+q44Mow2G5bhiC9FBvhAuIhJS6QovWnqzuL5AFQPEuwOB+PM4DhABkqxVa3Iwe3Y/nFlQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-musl": { + "version": "1.65.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.65.0.tgz", + "integrity": "sha512-reqOun1+pWO3fW6cv7bsa8hHG0TN3t/82qPdaoJo90FwugXiMjKhZMChmH5Z01cFNRHmxN4+543Fy8478cM/iA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-openharmony-arm64": { + "version": "1.65.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.65.0.tgz", + "integrity": "sha512-KQpqOb/juDBO0xyloDkVDhOVxDUgAfZ2OAAVq99TJScJDzT319xry1QzB9LQohV9QGnA7p6m/XATZkMXc84lwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-arm64-msvc": { + "version": "1.65.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.65.0.tgz", + "integrity": "sha512-xfqcOc3nJFeAd1kDY4T9d3XeJIhr00twaaW0kOAzGPyUHkruXtNJv6zz1Ra9fRtSek5VpW2Yoj5AcwPIlT0ZiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-ia32-msvc": { + "version": "1.65.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.65.0.tgz", + "integrity": "sha512-JV+pXm45p8sdgs3c7LOPAohW23optCNZETFOXUcjn6cS4PYZhEU/RI54Z5dHdMudab3nw7T48PZILthM+Q0COQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-x64-msvc": { + "version": "1.65.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.65.0.tgz", + "integrity": "sha512-D7L/oBbskLss21bYrRbFuIs81AiSQV+wRzwck54dOkHIlq2qu1xjLz8u6jCqGH8Fltk8bB5DLBpVhE7v/fA8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "16.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", + "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==", + "license": "MIT" + }, + "node_modules/@webgpu/types": { + "version": "0.1.71", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.71.tgz", + "integrity": "sha512-mMy8/ODcKhab808co15eW+yN+HgXoQxRQHTiBV9Mrvl1r0ufnid7YOcI+gi4eUWSWl9ezD6TW2KXccrL8HCh2A==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/any-base": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==", + "license": "MIT" + }, + "node_modules/await-to-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/await-to-js/-/await-to-js-3.0.0.tgz", + "integrity": "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/babel-plugin-jsx-dom-expressions": { + "version": "0.40.7", + "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.40.7.tgz", + "integrity": "sha512-/O6JWUmjv03OI9lL2ry9bUjpD5S3PclM55RRJEyCdcFZ5W2SEA/59d+l2hNsk3gI6kiWRdRPdOtqZmsQzFN1pQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "7.18.6", + "@babel/plugin-syntax-jsx": "^7.18.6", + "@babel/types": "^7.20.7", + "html-entities": "2.3.3", + "parse5": "^7.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.20.12" + } + }, + "node_modules/babel-plugin-jsx-dom-expressions/node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/babel-plugin-module-resolver": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-module-resolver/-/babel-plugin-module-resolver-5.0.2.tgz", + "integrity": "sha512-9KtaCazHee2xc0ibfqsDeamwDps6FZNo5S0Q81dUqEuFzVwPhcT4J5jOqIVvgCA3Q/wO9hKYxN/Ds3tIsp5ygg==", + "license": "MIT", + "dependencies": { + "find-babel-config": "^2.1.1", + "glob": "^9.3.3", + "pkg-up": "^3.1.0", + "reselect": "^4.1.7", + "resolve": "^1.22.8" + } + }, + "node_modules/babel-preset-solid": { + "version": "1.9.9", + "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.9.9.tgz", + "integrity": "sha512-pCnxWrciluXCeli/dj5PIEHgbNzim3evtTn12snjqqg8QZWJNMjH1AWIp4iG/tbVjqQ72aBEymMSagvmgxubXw==", + "license": "MIT", + "dependencies": { + "babel-plugin-jsx-dom-expressions": "^0.40.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "solid-js": "^1.9.8" + }, + "peerDependenciesMeta": { + "solid-js": { + "optional": true + } + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.40", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.40.tgz", + "integrity": "sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bmp-ts": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/bmp-ts/-/bmp-ts-1.0.9.tgz", + "integrity": "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", + "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.38", + "caniuse-lite": "^1.0.30001799", + "electron-to-chromium": "^1.5.376", + "node-releases": "^2.0.48", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/bun-ffi-structs": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/bun-ffi-structs/-/bun-ffi-structs-0.1.2.tgz", + "integrity": "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w==", + "license": "MIT", + "peerDependencies": { + "typescript": "^5" + } + }, + "node_modules/bun-webgpu": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/bun-webgpu/-/bun-webgpu-0.1.5.tgz", + "integrity": "sha512-91/K6S5whZKX7CWAm9AylhyKrLGRz6BUiiPiM/kXadSnD4rffljCD/q9cNFftm5YXhx4MvLqw33yEilxogJvwA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@webgpu/types": "^0.1.60" + }, + "optionalDependencies": { + "bun-webgpu-darwin-arm64": "^0.1.5", + "bun-webgpu-darwin-x64": "^0.1.5", + "bun-webgpu-linux-x64": "^0.1.5", + "bun-webgpu-win32-x64": "^0.1.5" + } + }, + "node_modules/bun-webgpu-darwin-arm64": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/bun-webgpu-darwin-arm64/-/bun-webgpu-darwin-arm64-0.1.7.tgz", + "integrity": "sha512-mRrFFyHzPWjsTRidAZBRcu808CPQBOUL0P6b4nxLhp+XHcV/mbUHERZMgW9s58tsojQfSdzschiQa8q+JCgRWA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/bun-webgpu-darwin-x64": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/bun-webgpu-darwin-x64/-/bun-webgpu-darwin-x64-0.1.7.tgz", + "integrity": "sha512-g0NXGNgvaVCSH/jCWWlfdiquOHkbUN6vP4zqzSkIxWKQeLnqm3oADcok7SO3yIgI7v5mKpRc/ks7NDEKNH+jNQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/bun-webgpu-linux-x64": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/bun-webgpu-linux-x64/-/bun-webgpu-linux-x64-0.1.7.tgz", + "integrity": "sha512-UEP7UZdEhx9otvkZczjsszL8ZVlrODANQvgl+C88/bNVmxDoFi7w1fWzGi1sZyakiETjmtFDq2/xCLhbSZxjqw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/bun-webgpu-win32-x64": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/bun-webgpu-win32-x64/-/bun-webgpu-win32-x64-0.1.7.tgz", + "integrity": "sha512-KZktiFkBz6sN7PEm1NVdeaLP5Q5X/PlSHZqefY4nNuWtf0LNvh54NhZe7yVv/Plz/nGbv92b0KHMBY3ki/pp6g==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, "os": [ "win32" + ] + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT", + "peer": true + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/diff": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", + "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.379", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.379.tgz", + "integrity": "sha512-v/qV5aV5EUA2pGilzUCq5/eyOloZAqDZBu9UMBIzgPpLlprjSR6zswsWBTv0KpqxLGUAZEwhO95ZCt7srymNVA==", + "license": "ISC" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/exif-parser": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", + "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==" + }, + "node_modules/file-type": { + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", + "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "license": "MIT", + "dependencies": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/find-babel-config": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-2.1.2.tgz", + "integrity": "sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg==", + "license": "MIT", + "dependencies": { + "json5": "^2.2.3" + } + }, + "node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/gifwrap": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz", + "integrity": "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==", + "license": "MIT", + "dependencies": { + "image-q": "^4.0.0", + "omggif": "^1.0.10" + } + }, + "node_modules/glob": { + "version": "9.3.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", + "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", + "license": "MIT" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } ], + "license": "BSD-3-Clause" + }, + "node_modules/image-q": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", + "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", + "license": "MIT", + "dependencies": { + "@types/node": "16.9.1" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/jimp": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-1.6.0.tgz", + "integrity": "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg==", + "license": "MIT", + "dependencies": { + "@jimp/core": "1.6.0", + "@jimp/diff": "1.6.0", + "@jimp/js-bmp": "1.6.0", + "@jimp/js-gif": "1.6.0", + "@jimp/js-jpeg": "1.6.0", + "@jimp/js-png": "1.6.0", + "@jimp/js-tiff": "1.6.0", + "@jimp/plugin-blit": "1.6.0", + "@jimp/plugin-blur": "1.6.0", + "@jimp/plugin-circle": "1.6.0", + "@jimp/plugin-color": "1.6.0", + "@jimp/plugin-contain": "1.6.0", + "@jimp/plugin-cover": "1.6.0", + "@jimp/plugin-crop": "1.6.0", + "@jimp/plugin-displace": "1.6.0", + "@jimp/plugin-dither": "1.6.0", + "@jimp/plugin-fisheye": "1.6.0", + "@jimp/plugin-flip": "1.6.0", + "@jimp/plugin-hash": "1.6.0", + "@jimp/plugin-mask": "1.6.0", + "@jimp/plugin-print": "1.6.0", + "@jimp/plugin-quantize": "1.6.0", + "@jimp/plugin-resize": "1.6.0", + "@jimp/plugin-rotate": "1.6.0", + "@jimp/plugin-threshold": "1.6.0", + "@jimp/types": "1.6.0", + "@jimp/utils": "1.6.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/jpeg-js": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", + "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", + "license": "BSD-3-Clause" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/marked": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.1.tgz", + "integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/minimatch": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.7.tgz", + "integrity": "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@oxlint/binding-win32-ia32-msvc": { - "version": "1.65.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.65.0.tgz", - "integrity": "sha512-JV+pXm45p8sdgs3c7LOPAohW23optCNZETFOXUcjn6cS4PYZhEU/RI54Z5dHdMudab3nw7T48PZILthM+Q0COQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "node_modules/minipass": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", + "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "license": "ISC", "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=8" } }, - "node_modules/@oxlint/binding-win32-x64-msvc": { - "version": "1.65.0", - "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.65.0.tgz", - "integrity": "sha512-D7L/oBbskLss21bYrRbFuIs81AiSQV+wRzwck54dOkHIlq2qu1xjLz8u6jCqGH8Fltk8bB5DLBpVhE7v/fA8XQ==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.50", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", + "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=18" } }, + "node_modules/omggif": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", + "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==", + "license": "MIT" + }, "node_modules/oxfmt": { "version": "0.50.0", "resolved": "https://registry.npmjs.org/oxfmt/-/oxfmt-0.50.0.tgz", @@ -800,6 +2527,432 @@ } } }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/parse-bmfont-ascii": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==", + "license": "MIT" + }, + "node_modules/parse-bmfont-binary": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==", + "license": "MIT" + }, + "node_modules/parse-bmfont-xml": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz", + "integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==", + "license": "MIT", + "dependencies": { + "xml-parse-from-string": "^1.0.0", + "xml2js": "^0.5.0" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/peek-readable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", + "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/pixelmatch": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz", + "integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==", + "license": "ISC", + "dependencies": { + "pngjs": "^6.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, + "node_modules/pixelmatch/node_modules/pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "license": "MIT", + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/planck": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/planck/-/planck-1.5.0.tgz", + "integrity": "sha512-dlvqJE+FscZgrGUXJ5ybd0o5bvZ5XXyZNbm08xGsXp9WjXeAyWSFT6n9s/1PQcUBo4546fDXA5RMA4wbDyZw6g==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=24.0" + }, + "peerDependencies": { + "stage-js": "^1.0.0-alpha.12" + } + }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readable-web-to-node-stream": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", + "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^4.7.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/reselect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", + "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/s-js": { + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/s-js/-/s-js-0.4.9.tgz", + "integrity": "sha512-RtpOm+cM6O0sHg6IA70wH+UC3FZcND+rccBZpBAHzlUgNO2Bm5BN+FnM8+OBxzXdwpKWFwX11JGF0MFRkhSoIQ==", + "license": "MIT" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/seroval": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz", + "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/seroval-plugins": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.3.3.tgz", + "integrity": "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "seroval": "^1.0" + } + }, + "node_modules/simple-xml-to-json": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/simple-xml-to-json/-/simple-xml-to-json-1.2.7.tgz", + "integrity": "sha512-mz9VXphOxQWX3eQ/uXCtm6upltoN0DLx8Zb5T4TFC4FHB7S9FDPGre8CfLWqPWQQH/GrQYd2AXhhVM5LDpYx6Q==", + "license": "MIT", + "engines": { + "node": ">=20.12.2" + } + }, + "node_modules/solid-js": { + "version": "1.9.9", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.9.tgz", + "integrity": "sha512-A0ZBPJQldAeGCTW0YRYJmt7RCeh5rbFfPZ2aOttgYnctHE7HgKeHCBB/PVc2P7eOfmNXqMFFFoYYdm3S4dcbkA==", + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.1.0", + "seroval": "~1.3.0", + "seroval-plugins": "~1.3.0" + } + }, + "node_modules/stage-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stage-js/-/stage-js-1.0.2.tgz", + "integrity": "sha512-EWTRBYlg7Qv9wGUao99/PfRe3KaiQqWmgSvTOXvaWnu1Jk/q/vV8yJVu6bi/3EqDZeMVnCPAjheba6OFc5k1GQ==", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=24.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strtok3": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", + "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/three": { + "version": "0.177.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.177.0.tgz", + "integrity": "sha512-EiXv5/qWAaGI+Vz2A+JfavwYCMdGjxVsrn3oBwllUoqYeaBO75J63ZfyaQKoiLrqNHoTlUc6PFgMXnS0kI45zg==", + "license": "MIT", + "optional": true + }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT" + }, "node_modules/tinypool": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-2.1.0.tgz", @@ -809,6 +2962,140 @@ "engines": { "node": "^20.0.0 || >=22.0.0" } + }, + "node_modules/token-types": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", + "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/utif2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz", + "integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==", + "license": "MIT", + "dependencies": { + "pako": "^1.0.11" + } + }, + "node_modules/web-tree-sitter": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.25.10.tgz", + "integrity": "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "@types/emscripten": "^1.40.0" + }, + "peerDependenciesMeta": { + "@types/emscripten": { + "optional": true + } + } + }, + "node_modules/xml-parse-from-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==", + "license": "MIT" + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yoga-layout": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", + "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==", + "license": "MIT" + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index 551b3a1..bf2b933 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,9 @@ "check": "npm run lint && npm run fmt", "prepack": "npm run check" }, + "dependencies": { + "@opentui/solid": "^0.1.87" + }, "devDependencies": { "oxfmt": "0.50.0", "oxlint": "1.65.0"