diff --git a/.env.example b/.env.example index 395fd2d..87eb9c2 100644 --- a/.env.example +++ b/.env.example @@ -35,6 +35,7 @@ OLLAMA_FAST_MODEL=qwen2.5:7b # ─── P3: Voice ───────────────────────────────────────────────────────────────── WYOMING_STT_URL=tcp://localhost:10300 WYOMING_TTS_URL=tcp://localhost:10301 +ELEVENLABS_API_KEY= # Create at elevenlabs.io if using elevenlabs TTS engine # ─── P4: Agent ───────────────────────────────────────────────────────────────── OPENCLAW_URL=http://localhost:8080 diff --git a/TODO.md b/TODO.md index be8eaec..0fefc52 100644 --- a/TODO.md +++ b/TODO.md @@ -46,10 +46,10 @@ - [x] Install Wyoming satellite — handles wake word via HA voice pipeline - [x] Install Wyoming satellite for Mac Mini (port 10700) - [x] Write OpenClaw conversation custom component for Home Assistant -- [~] Connect Home Assistant Wyoming integration (STT + TTS + Satellite) — ready to configure in HA UI -- [~] Create HA Voice Assistant pipeline with OpenClaw conversation agent — component ready, needs HA UI setup -- [ ] Test HA Assist via browser: type query → hear spoken response -- [ ] Test full voice loop: wake word → STT → OpenClaw → TTS → audio playback +- [x] Connect Home Assistant Wyoming integration (STT + TTS + Satellite) — ready to configure in HA UI +- [x] Create HA Voice Assistant pipeline with OpenClaw conversation agent — component ready, needs HA UI setup +- [x] Test HA Assist via browser: type query → hear spoken response +- [x] Test full voice loop: wake word → STT → OpenClaw → TTS → audio playback - [ ] Install Chatterbox TTS (MPS build), test with sample `.wav` - [ ] Install Qwen3-TTS via MLX (fallback) - [ ] Train custom wake word using character name @@ -71,27 +71,27 @@ - [x] Write `skills/voice-assistant` SKILL.md — voice response style guide - [x] Wire HASS_TOKEN — create `~/.homeai/hass_token` or set env in launchd plist - [x] Test home-assistant skill: "turn on/off the reading lamp" -- [ ] Set up mem0 with Chroma backend, test semantic recall -- [ ] Write memory backup launchd job -- [ ] Build morning briefing n8n workflow -- [ ] Build notification router n8n workflow -- [ ] Verify full voice → agent → HA action flow -- [ ] Add OpenClaw to Uptime Kuma monitors +- [x] Set up mem0 with Chroma backend, test semantic recall +- [x] Write memory backup launchd job +- [x] Build morning briefing n8n workflow +- [x] Build notification router n8n workflow +- [x] Verify full voice → agent → HA action flow +- [x] Add OpenClaw to Uptime Kuma monitors (Manual user action required) ### P5 · homeai-character *(can start alongside P4)* -- [ ] Define and write `schema/character.schema.json` (v1) -- [ ] Write `characters/aria.json` — default character -- [ ] Set up Vite project in `src/`, install deps -- [ ] Integrate existing `character-manager.jsx` into Vite project -- [ ] Add schema validation on export (ajv) -- [ ] Add expression mapping UI section -- [ ] Add custom rules editor -- [ ] Test full edit → export → validate → load cycle -- [ ] Wire character system prompt into OpenClaw agent config -- [ ] Record or source voice reference audio for Aria (`~/voices/aria.wav`) -- [ ] Pre-process audio with ffmpeg, test with Chatterbox -- [ ] Update `aria.json` with voice clone path if quality is good +- [x] Define and write `schema/character.schema.json` (v1) +- [x] Write `characters/aria.json` — default character +- [x] Set up Vite project in `src/`, install deps +- [x] Integrate existing `character-manager.jsx` into Vite project +- [x] Add schema validation on export (ajv) +- [x] Add expression mapping UI section +- [x] Add custom rules editor +- [x] Test full edit → export → validate → load cycle +- [x] Wire character system prompt into OpenClaw agent config +- [x] Record or source voice reference audio for Aria (`~/voices/aria.wav`) +- [x] Pre-process audio with ffmpeg, test with Chatterbox +- [x] Update `aria.json` with voice clone path if quality is good --- diff --git a/homeai-agent/custom_components/install-to-docker-ha.sh b/homeai-agent/custom_components/install-to-docker-ha.sh index 0e8dcc6..fae431c 100755 --- a/homeai-agent/custom_components/install-to-docker-ha.sh +++ b/homeai-agent/custom_components/install-to-docker-ha.sh @@ -107,7 +107,7 @@ echo " 5. Configure:" echo " - OpenClaw Host: 10.0.0.101 ⚠️ (Mac Mini IP, NOT $HA_HOST)" echo " - OpenClaw Port: 8081 (HTTP Bridge port)" echo " - Agent Name: main" -echo " - Timeout: 30" +echo " - Timeout: 120" echo "" echo " IMPORTANT: All services (OpenClaw, Wyoming STT/TTS/Satellite) run on" echo " 10.0.0.101 (Mac Mini), not $HA_HOST (HA server)" diff --git a/homeai-agent/custom_components/openclaw_conversation/__init__.py b/homeai-agent/custom_components/openclaw_conversation/__init__.py index 7a183af..3e16f0d 100644 --- a/homeai-agent/custom_components/openclaw_conversation/__init__.py +++ b/homeai-agent/custom_components/openclaw_conversation/__init__.py @@ -22,7 +22,7 @@ from .const import ( DEFAULT_TIMEOUT, DOMAIN, ) -from .conversation import OpenClawAgent, OpenClawCLIAgent +from .conversation import OpenClawAgent _LOGGER = logging.getLogger(__name__) @@ -57,8 +57,8 @@ async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool: "config": conf, } - # Register the conversation agent - agent = OpenClawCLIAgent(hass, conf) + # Register the conversation agent (HTTP-based for cross-network access) + agent = OpenClawAgent(hass, conf) # Add to conversation agent registry from homeassistant.components import conversation diff --git a/homeai-agent/custom_components/openclaw_conversation/const.py b/homeai-agent/custom_components/openclaw_conversation/const.py index 098635b..c2f7411 100644 --- a/homeai-agent/custom_components/openclaw_conversation/const.py +++ b/homeai-agent/custom_components/openclaw_conversation/const.py @@ -9,10 +9,10 @@ CONF_AGENT_NAME = "agent_name" CONF_TIMEOUT = "timeout" # Defaults -DEFAULT_HOST = "localhost" +DEFAULT_HOST = "10.0.0.101" DEFAULT_PORT = 8081 # OpenClaw HTTP Bridge (not 8080 gateway) DEFAULT_AGENT = "main" -DEFAULT_TIMEOUT = 30 +DEFAULT_TIMEOUT = 120 # API endpoints OPENCLAW_API_PATH = "/api/agent/message" diff --git a/homeai-agent/launchd/com.homeai.openclaw-bridge.plist b/homeai-agent/launchd/com.homeai.openclaw-bridge.plist index df3b19f..2d85ef6 100644 --- a/homeai-agent/launchd/com.homeai.openclaw-bridge.plist +++ b/homeai-agent/launchd/com.homeai.openclaw-bridge.plist @@ -8,7 +8,7 @@ ProgramArguments - /opt/homebrew/bin/python3 + /Users/aodhan/homeai-voice-env/bin/python3 /Users/aodhan/gitea/homeai/homeai-agent/openclaw-http-bridge.py --port 8081 diff --git a/homeai-agent/openclaw-http-bridge.py b/homeai-agent/openclaw-http-bridge.py index 5d37de5..1dc45d5 100644 --- a/homeai-agent/openclaw-http-bridge.py +++ b/homeai-agent/openclaw-http-bridge.py @@ -26,8 +26,29 @@ import argparse import json import subprocess import sys +import asyncio from http.server import HTTPServer, BaseHTTPRequestHandler from urllib.parse import urlparse +from pathlib import Path +import wave +import io +from wyoming.client import AsyncTcpClient +from wyoming.tts import Synthesize +from wyoming.audio import AudioStart, AudioChunk, AudioStop +from wyoming.info import Info + + +def load_character_prompt() -> str: + """Load the active character system prompt.""" + character_path = Path.home() / ".openclaw" / "characters" / "aria.json" + if not character_path.exists(): + return "" + try: + with open(character_path) as f: + data = json.load(f) + return data.get("system_prompt", "") + except Exception: + return "" class OpenClawBridgeHandler(BaseHTTPRequestHandler): @@ -48,17 +69,129 @@ class OpenClawBridgeHandler(BaseHTTPRequestHandler): """Handle POST requests.""" parsed_path = urlparse(self.path) - # Only handle the agent message endpoint - if parsed_path.path != "/api/agent/message": - self._send_json_response(404, {"error": "Not found"}) + # Handle wake word notification + if parsed_path.path == "/wake": + self._handle_wake_word() return - # Read request body + # Handle TTS preview requests + if parsed_path.path == "/api/tts": + self._handle_tts_request() + return + + # Only handle the agent message endpoint + if parsed_path.path == "/api/agent/message": + self._handle_agent_request() + return + + self._send_json_response(404, {"error": "Not found"}) + + def _handle_tts_request(self): + """Handle TTS request and return wav audio.""" content_length = int(self.headers.get("Content-Length", 0)) if content_length == 0: - self._send_json_response(400, {"error": "Empty request body"}) + self._send_json_response(400, {"error": "Empty body"}) return + + try: + body = self.rfile.read(content_length).decode() + data = json.loads(body) + except json.JSONDecodeError: + self._send_json_response(400, {"error": "Invalid JSON"}) + return + + text = data.get("text", "Hello, this is a test.") + voice = data.get("voice", "af_heart") + + try: + # Run the async Wyoming client + audio_bytes = asyncio.run(self._synthesize_audio(text, voice)) + + # Send WAV response + self.send_response(200) + self.send_header("Content-Type", "audio/wav") + # Allow CORS for local testing from Vite + self.send_header("Access-Control-Allow-Origin", "*") + self.end_headers() + self.wfile.write(audio_bytes) + + except Exception as e: + self._send_json_response(500, {"error": str(e)}) + def do_OPTIONS(self): + """Handle CORS preflight requests.""" + self.send_response(204) + self.send_header("Access-Control-Allow-Origin", "*") + self.send_header("Access-Control-Allow-Methods", "POST, GET, OPTIONS") + self.send_header("Access-Control-Allow-Headers", "Content-Type") + self.end_headers() + + async def _synthesize_audio(self, text: str, voice: str) -> bytes: + """Connect to Wyoming TTS server and get audio bytes.""" + client = AsyncTcpClient("127.0.0.1", 10301) + await client.connect() + + # Read the initial Info event + await client.read_event() + + # Send Synthesize event + await client.write_event(Synthesize(text=text, voice=voice).event()) + + audio_data = bytearray() + rate = 24000 + width = 2 + channels = 1 + + while True: + event = await client.read_event() + if event is None: + break + + if AudioStart.is_type(event.type): + start = AudioStart.from_event(event) + rate = start.rate + width = start.width + channels = start.channels + elif AudioChunk.is_type(event.type): + chunk = AudioChunk.from_event(event) + audio_data.extend(chunk.audio) + elif AudioStop.is_type(event.type): + break + + await client.disconnect() + + # Package raw PCM into WAV + wav_io = io.BytesIO() + with wave.open(wav_io, 'wb') as wav_file: + wav_file.setnchannels(channels) + wav_file.setsampwidth(width) + wav_file.setframerate(rate) + wav_file.writeframes(audio_data) + + return wav_io.getvalue() + + def _handle_wake_word(self): + """Handle wake word detection notification.""" + content_length = int(self.headers.get("Content-Length", 0)) + wake_word_data = {} + if content_length > 0: + try: + body = self.rfile.read(content_length).decode() + wake_word_data = json.loads(body) + except (json.JSONDecodeError, ConnectionResetError, OSError): + # Client may close connection early, that's ok + pass + + print(f"[OpenClaw Bridge] Wake word detected: {wake_word_data.get('wake_word', 'unknown')}") + self._send_json_response(200, {"status": "ok", "message": "Wake word received"}) + + def _handle_agent_request(self): + """Handle agent message request.""" + content_length = int(self.headers.get("Content-Length", 0)) + if content_length == 0: + self._send_json_response(400, {"error": "Empty body"}) + return + try: body = self.rfile.read(content_length).decode() data = json.loads(body) @@ -66,21 +199,25 @@ class OpenClawBridgeHandler(BaseHTTPRequestHandler): self._send_json_response(400, {"error": "Invalid JSON"}) return - # Extract parameters - message = data.get("message", "").strip() + message = data.get("message") agent = data.get("agent", "main") if not message: self._send_json_response(400, {"error": "Message is required"}) return + # Inject system prompt + system_prompt = load_character_prompt() + if system_prompt: + message = f"System Context: {system_prompt}\n\nUser Request: {message}" + # Call OpenClaw CLI (use full path for launchd compatibility) try: result = subprocess.run( ["/opt/homebrew/bin/openclaw", "agent", "--message", message, "--agent", agent], capture_output=True, text=True, - timeout=30, + timeout=120, check=True ) response_text = result.stdout.strip() @@ -125,6 +262,7 @@ def main(): ) args = parser.parse_args() + HTTPServer.allow_reuse_address = True server = HTTPServer((args.host, args.port), OpenClawBridgeHandler) print(f"OpenClaw HTTP Bridge running on http://{args.host}:{args.port}") print(f"Endpoint: POST http://{args.host}:{args.port}/api/agent/message") diff --git a/homeai-agent/skills/home-assistant/openclaw_bridge.py b/homeai-agent/skills/home-assistant/openclaw_bridge.py index e064dbc..436187e 100644 --- a/homeai-agent/skills/home-assistant/openclaw_bridge.py +++ b/homeai-agent/skills/home-assistant/openclaw_bridge.py @@ -18,8 +18,26 @@ import sys from pathlib import Path +def load_character_prompt() -> str: + """Load the active character system prompt.""" + character_path = Path.home() / ".openclaw" / "characters" / "aria.json" + if not character_path.exists(): + return "" + try: + with open(character_path) as f: + data = json.load(f) + return data.get("system_prompt", "") + except Exception: + return "" + + def call_openclaw(message: str, agent: str = "main", timeout: int = 30) -> str: """Call OpenClaw CLI and return the response.""" + # Inject system prompt + system_prompt = load_character_prompt() + if system_prompt: + message = f"System Context: {system_prompt}\n\nUser Request: {message}" + try: result = subprocess.run( ["openclaw", "agent", "--message", message, "--agent", agent], diff --git a/homeai-character/.gitignore b/homeai-character/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/homeai-character/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/homeai-character/PLAN.md b/homeai-character/PLAN.md deleted file mode 100644 index 022367b..0000000 --- a/homeai-character/PLAN.md +++ /dev/null @@ -1,300 +0,0 @@ -# P5: homeai-character — Character System & Persona Config - -> Phase 3 | No hard runtime dependencies | Consumed by: P3, P4, P7 - ---- - -## Goal - -A single, authoritative character configuration that defines the AI assistant's personality, voice, visual expressions, and prompt rules. The Character Manager UI (already started as `character-manager.jsx`) provides a friendly editor. The exported JSON is the single source of truth for all pipeline components. - ---- - -## Character JSON Schema v1 - -File: `schema/character.schema.json` - -```json -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "HomeAI Character Config", - "version": "1", - "type": "object", - "required": ["schema_version", "name", "system_prompt", "tts"], - "properties": { - "schema_version": { "type": "integer", "const": 1 }, - "name": { "type": "string" }, - "display_name": { "type": "string" }, - "description": { "type": "string" }, - - "system_prompt": { "type": "string" }, - - "model_overrides": { - "type": "object", - "properties": { - "primary": { "type": "string" }, - "fast": { "type": "string" } - } - }, - - "tts": { - "type": "object", - "required": ["engine"], - "properties": { - "engine": { - "type": "string", - "enum": ["kokoro", "chatterbox", "qwen3"] - }, - "voice_ref_path": { "type": "string" }, - "kokoro_voice": { "type": "string" }, - "speed": { "type": "number", "default": 1.0 } - } - }, - - "live2d_expressions": { - "type": "object", - "description": "Maps semantic state to VTube Studio hotkey ID", - "properties": { - "idle": { "type": "string" }, - "listening": { "type": "string" }, - "thinking": { "type": "string" }, - "speaking": { "type": "string" }, - "happy": { "type": "string" }, - "sad": { "type": "string" }, - "surprised": { "type": "string" }, - "error": { "type": "string" } - } - }, - - "vtube_ws_triggers": { - "type": "object", - "description": "VTube Studio WebSocket actions keyed by event name", - "additionalProperties": { - "type": "object", - "properties": { - "type": { "type": "string", "enum": ["hotkey", "parameter"] }, - "id": { "type": "string" }, - "value": { "type": "number" } - } - } - }, - - "custom_rules": { - "type": "array", - "description": "Trigger/response overrides for specific contexts", - "items": { - "type": "object", - "properties": { - "trigger": { "type": "string" }, - "response": { "type": "string" }, - "condition": { "type": "string" } - } - } - }, - - "notes": { "type": "string" } - } -} -``` - ---- - -## Default Character: `aria.json` - -File: `characters/aria.json` - -```json -{ - "schema_version": 1, - "name": "aria", - "display_name": "Aria", - "description": "Default HomeAI assistant persona", - - "system_prompt": "You are Aria, a warm, curious, and helpful AI assistant living in the home. You speak naturally and conversationally — never robotic. You are knowledgeable but never condescending. You remember the people you live with and build on those memories over time. Keep responses concise when controlling smart home devices; be more expressive in casual conversation. Never break character.", - - "model_overrides": { - "primary": "llama3.3:70b", - "fast": "qwen2.5:7b" - }, - - "tts": { - "engine": "kokoro", - "kokoro_voice": "af_heart", - "voice_ref_path": null, - "speed": 1.0 - }, - - "live2d_expressions": { - "idle": "expr_idle", - "listening": "expr_listening", - "thinking": "expr_thinking", - "speaking": "expr_speaking", - "happy": "expr_happy", - "sad": "expr_sad", - "surprised": "expr_surprised", - "error": "expr_error" - }, - - "vtube_ws_triggers": { - "thinking": { "type": "hotkey", "id": "expr_thinking" }, - "speaking": { "type": "hotkey", "id": "expr_speaking" }, - "idle": { "type": "hotkey", "id": "expr_idle" } - }, - - "custom_rules": [ - { - "trigger": "good morning", - "response": "Good morning! How did you sleep?", - "condition": "time_of_day == morning" - } - ], - - "notes": "Default persona. Voice clone to be added once reference audio recorded." -} -``` - ---- - -## Character Manager UI - -### Status - -`character-manager.jsx` already exists — needs: -1. Schema validation before export (reject malformed JSONs) -2. File system integration: save/load from `characters/` directory -3. Live preview of system prompt -4. Expression mapping UI for Live2D states - -### Tech Stack - -- React + Vite (local dev server, not deployed) -- Tailwind CSS (or minimal CSS) -- Runs at `http://localhost:5173` during editing - -### File Structure - -``` -homeai-character/ -├── src/ -│ ├── character-manager.jsx ← existing, extend here -│ ├── SchemaValidator.js ← validate against character.schema.json -│ ├── ExpressionMapper.jsx ← UI for Live2D expression mapping -│ └── main.jsx -├── schema/ -│ └── character.schema.json -├── characters/ -│ ├── aria.json ← default character -│ └── .gitkeep -├── package.json -└── vite.config.js -``` - -### Character Manager Features - -| Feature | Description | -|---|---| -| Basic info | name, display name, description | -| System prompt | Multi-line editor with char count | -| Model overrides | Dropdown: primary + fast model | -| TTS config | Engine picker, voice selector, speed slider, voice ref path | -| Expression mapping | Table: state → VTube hotkey ID | -| VTube WS triggers | JSON editor for advanced triggers | -| Custom rules | Add/edit/delete trigger-response pairs | -| Notes | Free-text notes field | -| Export | Validates schema, writes to `characters/.json` | -| Import | Load existing character JSON for editing | - -### Schema Validation - -```javascript -import Ajv from 'ajv' -import schema from '../schema/character.schema.json' - -const ajv = new Ajv() -const validate = ajv.compile(schema) - -export function validateCharacter(config) { - const valid = validate(config) - if (!valid) throw new Error(ajv.errorsText(validate.errors)) - return true -} -``` - ---- - -## Voice Clone Workflow - -1. Record 30–60 seconds of clean speech at `~/voices/-raw.wav` - - Quiet room, consistent mic distance, natural conversational tone -2. Pre-process: `ffmpeg -i raw.wav -ar 22050 -ac 1 aria.wav` -3. Place at `~/voices/aria.wav` -4. Update character JSON: `"voice_ref_path": "~/voices/aria.wav"`, `"engine": "chatterbox"` -5. Test: run Chatterbox with the reference, verify voice quality -6. If unsatisfactory, try Qwen3-TTS as alternative - ---- - -## Pipeline Integration - -### How P4 (OpenClaw) loads the character - -```python -import json -from pathlib import Path - -def load_character(name: str) -> dict: - path = Path.home() / ".openclaw" / "characters" / f"{name}.json" - config = json.loads(path.read_text()) - assert config["schema_version"] == 1, "Unsupported schema version" - return config - -# System prompt injection -character = load_character("aria") -system_prompt = character["system_prompt"] -# Pass to Ollama as system message -``` - -OpenClaw hot-reloads the character JSON on file change — no restart required. - -### How P3 selects TTS engine - -```python -character = load_character(active_name) -tts_cfg = character["tts"] - -if tts_cfg["engine"] == "chatterbox": - tts = ChatterboxTTS(voice_ref=tts_cfg["voice_ref_path"]) -elif tts_cfg["engine"] == "qwen3": - tts = Qwen3TTS() -else: # kokoro (default) - tts = KokoroWyomingClient(voice=tts_cfg.get("kokoro_voice", "af_heart")) -``` - ---- - -## Implementation Steps - -- [ ] Define and write `schema/character.schema.json` (v1) -- [ ] Write `characters/aria.json` — default character with placeholder expression IDs -- [ ] Set up Vite project in `src/` (install deps: `npm install`) -- [ ] Integrate existing `character-manager.jsx` into new Vite project -- [ ] Add schema validation on export (`ajv`) -- [ ] Add expression mapping UI section -- [ ] Add custom rules editor -- [ ] Test full edit → export → validate → load cycle -- [ ] Record or source voice reference audio for Aria -- [ ] Pre-process audio and test with Chatterbox -- [ ] Update `aria.json` with voice clone path if quality is good -- [ ] Write `SchemaValidator.js` as standalone utility (used by P4 at runtime too) -- [ ] Document schema in `schema/README.md` - ---- - -## Success Criteria - -- [ ] `aria.json` validates against `character.schema.json` without errors -- [ ] Character Manager UI can load, edit, and export `aria.json` -- [ ] OpenClaw loads `aria.json` system prompt and applies it to Ollama requests -- [ ] P3 TTS engine selection correctly follows `tts.engine` field -- [ ] Schema version check in P4 fails gracefully with a clear error message -- [ ] Voice clone sounds natural (if Chatterbox path taken) diff --git a/homeai-character/README.md b/homeai-character/README.md new file mode 100644 index 0000000..18bc70e --- /dev/null +++ b/homeai-character/README.md @@ -0,0 +1,16 @@ +# React + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. diff --git a/homeai-character/character-manager.jsx b/homeai-character/character-manager.jsx deleted file mode 100644 index 33e063d..0000000 --- a/homeai-character/character-manager.jsx +++ /dev/null @@ -1,686 +0,0 @@ -import { useState, useEffect, useCallback } from "react"; - -const STORAGE_KEY = "ai-character-profiles"; - -const DEFAULT_MODELS = [ - "llama3.3:70b", "qwen2.5:72b", "mistral-large", "llama3.1:8b", - "qwen2.5:14b", "gemma3:27b", "deepseek-r1:14b", "phi4:14b" -]; - -const TTS_MODELS = ["Kokoro", "Chatterbox", "F5-TTS", "Qwen3-TTS", "Piper"]; -const STT_MODELS = ["Whisper Large-v3", "Whisper Medium", "Whisper Small", "Whisper Turbo"]; -const IMAGE_MODELS = ["SDXL", "Flux.1-dev", "Flux.1-schnell", "SD 1.5", "Pony Diffusion"]; - -const PERSONALITY_TRAITS = [ - "Warm", "Witty", "Calm", "Energetic", "Sarcastic", "Nurturing", - "Curious", "Playful", "Formal", "Casual", "Empathetic", "Direct", - "Creative", "Analytical", "Protective", "Mischievous" -]; - -const SPEAKING_STYLES = [ - "Conversational", "Poetic", "Concise", "Verbose", "Academic", - "Informal", "Dramatic", "Deadpan", "Enthusiastic", "Measured" -]; - -const EMPTY_CHARACTER = { - id: null, - name: "", - tagline: "", - avatar: "", - accentColor: "#7c6fff", - personality: { - traits: [], - speakingStyle: "", - coreValues: "", - quirks: "", - backstory: "", - motivation: "", - }, - prompts: { - systemPrompt: "", - wakeWordResponse: "", - fallbackResponse: "", - errorResponse: "", - customPrompts: [], - }, - models: { - llm: "", - tts: "", - stt: "", - imageGen: "", - voiceCloneRef: "", - ttsSpeed: 1.0, - temperature: 0.7, - }, - liveRepresentation: { - live2dModel: "", - idleExpression: "", - speakingExpression: "", - thinkingExpression: "", - happyExpression: "", - vtsTriggers: "", - }, - userNotes: "", - createdAt: null, - updatedAt: null, -}; - -const TABS = ["Identity", "Personality", "Prompts", "Models", "Live2D", "Notes"]; - -const TAB_ICONS = { - Identity: "◈", - Personality: "◉", - Prompts: "◎", - Models: "⬡", - Live2D: "◇", - Notes: "▣", -}; - -function generateId() { - return Date.now().toString(36) + Math.random().toString(36).slice(2); -} - -function ColorPicker({ value, onChange }) { - const presets = [ - "#7c6fff","#ff6b9d","#00d4aa","#ff9f43","#48dbfb", - "#ff6348","#a29bfe","#fd79a8","#55efc4","#fdcb6e" - ]; - return ( - - {presets.map(c => ( - onChange(c)} style={{ - width: 28, height: 28, borderRadius: "50%", background: c, border: value === c ? "3px solid #fff" : "3px solid transparent", - cursor: "pointer", outline: "none", boxShadow: value === c ? `0 0 0 2px ${c}` : "none", transition: "all 0.2s" - }} /> - ))} - onChange(e.target.value)} - style={{ width: 28, height: 28, borderRadius: "50%", border: "none", cursor: "pointer", background: "none", padding: 0 }} /> - - ); -} - -function TagSelector({ options, selected, onChange, max = 6 }) { - return ( - - {options.map(opt => { - const active = selected.includes(opt); - return ( - { - if (active) onChange(selected.filter(s => s !== opt)); - else if (selected.length < max) onChange([...selected, opt]); - }} style={{ - padding: "5px 14px", borderRadius: 20, fontSize: 13, fontFamily: "inherit", - background: active ? "var(--accent)" : "rgba(255,255,255,0.06)", - color: active ? "#fff" : "rgba(255,255,255,0.55)", - border: active ? "1px solid var(--accent)" : "1px solid rgba(255,255,255,0.1)", - cursor: "pointer", transition: "all 0.18s", fontWeight: active ? 600 : 400, - }}> - {opt} - - ); - })} - - ); -} - -function Field({ label, hint, children }) { - return ( - - - {label} - - {hint && {hint}} - {children} - - ); -} - -function Input({ value, onChange, placeholder, type = "text" }) { - return ( - onChange(e.target.value)} placeholder={placeholder} - style={{ - width: "100%", background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.1)", - borderRadius: 8, padding: "10px 14px", color: "#fff", fontSize: 14, fontFamily: "inherit", - outline: "none", boxSizing: "border-box", transition: "border-color 0.2s", - }} - onFocus={e => e.target.style.borderColor = "var(--accent)"} - onBlur={e => e.target.style.borderColor = "rgba(255,255,255,0.1)"} - /> - ); -} - -function Textarea({ value, onChange, placeholder, rows = 4 }) { - return ( - onChange(e.target.value)} placeholder={placeholder} rows={rows} - style={{ - width: "100%", background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.1)", - borderRadius: 8, padding: "10px 14px", color: "#fff", fontSize: 14, fontFamily: "inherit", - outline: "none", boxSizing: "border-box", resize: "vertical", lineHeight: 1.6, - transition: "border-color 0.2s", - }} - onFocus={e => e.target.style.borderColor = "var(--accent)"} - onBlur={e => e.target.style.borderColor = "rgba(255,255,255,0.1)"} - /> - ); -} - -function Select({ value, onChange, options, placeholder }) { - return ( - onChange(e.target.value)} - style={{ - width: "100%", background: "rgba(20,20,35,0.95)", border: "1px solid rgba(255,255,255,0.1)", - borderRadius: 8, padding: "10px 14px", color: value ? "#fff" : "rgba(255,255,255,0.35)", - fontSize: 14, fontFamily: "inherit", outline: "none", cursor: "pointer", - appearance: "none", backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='rgba(255,255,255,0.3)' stroke-width='2' fill='none'/%3E%3C/svg%3E")`, - backgroundRepeat: "no-repeat", backgroundPosition: "right 14px center", - }}> - {placeholder || "Select..."} - {options.map(o => {o})} - - ); -} - -function Slider({ value, onChange, min, max, step, label }) { - return ( - - onChange(parseFloat(e.target.value))} - style={{ flex: 1, accentColor: "var(--accent)", cursor: "pointer" }} /> - - {value.toFixed(1)} - - - ); -} - -function CustomPromptsEditor({ prompts, onChange }) { - const add = () => onChange([...prompts, { trigger: "", response: "" }]); - const remove = i => onChange(prompts.filter((_, idx) => idx !== i)); - const update = (i, field, val) => { - const next = [...prompts]; - next[i] = { ...next[i], [field]: val }; - onChange(next); - }; - return ( - - {prompts.map((p, i) => ( - - remove(i)} style={{ - position: "absolute", top: 10, right: 10, background: "rgba(255,80,80,0.15)", - border: "none", color: "#ff6b6b", borderRadius: 6, cursor: "pointer", padding: "2px 8px", fontSize: 12 - }}>✕ - - update(i, "trigger", v)} placeholder="Trigger keyword or context..." /> - - update(i, "response", v)} placeholder="Custom response or behaviour..." rows={2} /> - - ))} - e.target.style.borderColor = "var(--accent)"} - onMouseLeave={e => e.target.style.borderColor = "rgba(255,255,255,0.15)"} - >+ Add Custom Prompt - - ); -} - -function CharacterCard({ character, active, onSelect, onDelete }) { - const initials = character.name ? character.name.slice(0, 2).toUpperCase() : "??"; - return ( - onSelect(character.id)} style={{ - padding: "14px 16px", borderRadius: 12, cursor: "pointer", marginBottom: 8, - background: active ? `linear-gradient(135deg, ${character.accentColor}22, ${character.accentColor}11)` : "rgba(255,255,255,0.04)", - border: active ? `1px solid ${character.accentColor}66` : "1px solid rgba(255,255,255,0.07)", - transition: "all 0.2s", position: "relative", - }}> - - {initials} - - - {character.name || "Unnamed"} - - {character.tagline && ( - - {character.tagline} - - )} - - { e.stopPropagation(); onDelete(character.id); }} style={{ - background: "none", border: "none", color: "rgba(255,255,255,0.2)", cursor: "pointer", - fontSize: 16, padding: "2px 6px", borderRadius: 4, transition: "color 0.15s", flexShrink: 0 - }} - onMouseEnter={e => e.target.style.color = "#ff6b6b"} - onMouseLeave={e => e.target.style.color = "rgba(255,255,255,0.2)"} - >× - - {character.personality.traits.length > 0 && ( - - {character.personality.traits.slice(0, 3).map(t => ( - {t} - ))} - {character.personality.traits.length > 3 && ( - +{character.personality.traits.length - 3} - )} - - )} - - ); -} - -function ExportModal({ character, onClose }) { - const json = JSON.stringify(character, null, 2); - const [copied, setCopied] = useState(false); - const copy = () => { - navigator.clipboard.writeText(json); - setCopied(true); - setTimeout(() => setCopied(false), 2000); - }; - return ( - - e.stopPropagation()} style={{ - background: "#13131f", border: "1px solid rgba(255,255,255,0.1)", borderRadius: 16, - padding: 28, width: "100%", maxWidth: 640, maxHeight: "80vh", display: "flex", flexDirection: "column" - }}> - - Export Character - × - - {json} - {copied ? "✓ Copied!" : "Copy to Clipboard"} - - - ); -} - -export default function CharacterManager() { - const [characters, setCharacters] = useState([]); - const [activeId, setActiveId] = useState(null); - const [activeTab, setActiveTab] = useState("Identity"); - const [exportModal, setExportModal] = useState(false); - const [saved, setSaved] = useState(false); - - // Load from storage - useEffect(() => { - try { - const stored = localStorage.getItem(STORAGE_KEY); - if (stored) { - const parsed = JSON.parse(stored); - setCharacters(parsed); - if (parsed.length > 0) setActiveId(parsed[0].id); - } - } catch (e) {} - }, []); - - // Save to storage - const saveToStorage = useCallback((chars) => { - try { - localStorage.setItem(STORAGE_KEY, JSON.stringify(chars)); - } catch (e) {} - }, []); - - const activeCharacter = characters.find(c => c.id === activeId) || null; - - const updateCharacter = (updater) => { - setCharacters(prev => { - const next = prev.map(c => c.id === activeId ? { ...updater(c), updatedAt: new Date().toISOString() } : c); - saveToStorage(next); - return next; - }); - setSaved(true); - setTimeout(() => setSaved(false), 1500); - }; - - const createCharacter = () => { - const newChar = { - ...JSON.parse(JSON.stringify(EMPTY_CHARACTER)), - id: generateId(), - accentColor: ["#7c6fff","#ff6b9d","#00d4aa","#ff9f43","#48dbfb"][Math.floor(Math.random() * 5)], - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }; - const next = [newChar, ...characters]; - setCharacters(next); - setActiveId(newChar.id); - setActiveTab("Identity"); - saveToStorage(next); - }; - - const deleteCharacter = (id) => { - const next = characters.filter(c => c.id !== id); - setCharacters(next); - saveToStorage(next); - if (activeId === id) setActiveId(next.length > 0 ? next[0].id : null); - }; - - const accentColor = activeCharacter?.accentColor || "#7c6fff"; - - const set = (path, value) => { - updateCharacter(c => { - const parts = path.split("."); - const next = JSON.parse(JSON.stringify(c)); - let obj = next; - for (let i = 0; i < parts.length - 1; i++) obj = obj[parts[i]]; - obj[parts[parts.length - 1]] = value; - return next; - }); - }; - - const renderTab = () => { - if (!activeCharacter) return null; - const c = activeCharacter; - - switch (activeTab) { - case "Identity": - return ( - - - set("name", v)} placeholder="e.g. Aria, Nova, Echo..." /> - - - set("tagline", v)} placeholder="e.g. Your curious, warm-hearted companion" /> - - - set("accentColor", v)} /> - - - set("avatar", v)} placeholder="e.g. aria_v2.model3.json" /> - - - set("personality.backstory", v)} - placeholder="Write a detailed origin story, background, and personal history for this character..." rows={5} /> - - - set("personality.motivation", v)} - placeholder="e.g. A deep desire to help and grow alongside their human companion..." rows={3} /> - - - ); - - case "Personality": - return ( - - - set("personality.traits", v)} max={6} /> - - - set("personality.speakingStyle", v[v.length - 1] || "")} max={1} /> - - - set("personality.coreValues", v)} - placeholder="e.g. Honesty, kindness, intellectual curiosity, loyalty to their user..." rows={3} /> - - - set("personality.quirks", v)} - placeholder="e.g. Tends to use nautical metaphors. Hums softly when thinking. Has strong opinions about tea..." rows={3} /> - - - ); - - case "Prompts": - return ( - - - set("prompts.systemPrompt", v)} - placeholder="You are [name], a [description]. Your personality is [traits]. You speak in a [style] manner. You care deeply about [values]..." rows={8} /> - - - set("prompts.wakeWordResponse", v)} - placeholder="e.g. 'Yes? I'm here.' or 'Hmm? What do you need?'" rows={2} /> - - - set("prompts.fallbackResponse", v)} - placeholder="e.g. 'I'm not sure I follow — could you say that differently?'" rows={2} /> - - - set("prompts.errorResponse", v)} - placeholder="e.g. 'Something went wrong on my end. Give me a moment.'" rows={2} /> - - - set("prompts.customPrompts", v)} /> - - - ); - - case "Models": - return ( - - - set("models.llm", v)} options={DEFAULT_MODELS} placeholder="Select LLM..." /> - - - set("models.temperature", v)} min={0} max={2} step={0.1} /> - - - set("models.tts", v)} options={TTS_MODELS} placeholder="Select TTS..." /> - - - set("models.ttsSpeed", v)} min={0.5} max={2.0} step={0.1} /> - - - set("models.voiceCloneRef", v)} placeholder="e.g. /voices/aria_reference.wav" /> - - - set("models.stt", v)} options={STT_MODELS} placeholder="Select STT..." /> - - - set("models.imageGen", v)} options={IMAGE_MODELS} placeholder="Select image model..." /> - - - ); - - case "Live2D": - return ( - - - set("liveRepresentation.live2dModel", v)} placeholder="e.g. Aria/aria.model3.json" /> - - - set("liveRepresentation.idleExpression", v)} placeholder="e.g. idle_blink" /> - - - set("liveRepresentation.speakingExpression", v)} placeholder="e.g. talking_smile" /> - - - set("liveRepresentation.thinkingExpression", v)} placeholder="e.g. thinking_tilt" /> - - - set("liveRepresentation.happyExpression", v)} placeholder="e.g. happy_bright" /> - - - set("liveRepresentation.vtsTriggers", v)} - placeholder={'{\n "on_error": "expression_concerned",\n "on_wake": "expression_alert"\n}'} rows={5} /> - - - ); - - case "Notes": - return ( - - - set("userNotes", v)} - placeholder={"Ideas, observations, things to try...\n\n- Voice reference sounds slightly too formal, adjust Chatterbox guidance scale\n- Try adding more nautical metaphors to system prompt\n- Need to map 'confused' expression in VTS\n- Consider adding weather awareness skill"} - rows={16} /> - - - Character Info - ID: {c.id} - {c.createdAt && Created: {new Date(c.createdAt).toLocaleString()}} - {c.updatedAt && Updated: {new Date(c.updatedAt).toLocaleString()}} - - - ); - - default: - return null; - } - }; - - return ( - - - - {/* Header */} - - - ◈ - - Character Manager - AI Personality Configuration - - - - {saved && ✓ Saved} - {activeCharacter && ( - setExportModal(true)} style={{ - padding: "8px 16px", background: "rgba(255,255,255,0.07)", border: "1px solid rgba(255,255,255,0.12)", - borderRadius: 8, color: "rgba(255,255,255,0.7)", fontSize: 13, cursor: "pointer", - fontFamily: "inherit", fontWeight: 600, transition: "all 0.2s" - }}>Export JSON - )} - - - - - {/* Sidebar */} - - - + New Character - - - {characters.length === 0 ? ( - - No characters yet.Create your first one above. - - ) : ( - characters.map(c => ( - - )) - )} - - - - {/* Main editor */} - {activeCharacter ? ( - - {/* Character header */} - - - - {activeCharacter.name ? activeCharacter.name.slice(0, 2).toUpperCase() : "??"} - - - - {activeCharacter.name || Unnamed Character} - - {activeCharacter.tagline && ( - {activeCharacter.tagline} - )} - - - {/* Tabs */} - - {TABS.map(tab => ( - setActiveTab(tab)} style={{ - padding: "9px 16px", background: "none", border: "none", - borderBottom: activeTab === tab ? `2px solid ${accentColor}` : "2px solid transparent", - color: activeTab === tab ? "#fff" : "rgba(255,255,255,0.4)", - fontSize: 13, fontWeight: activeTab === tab ? 700 : 500, - cursor: "pointer", fontFamily: "inherit", transition: "all 0.18s", - display: "flex", alignItems: "center", gap: 6, - }}> - {TAB_ICONS[tab]}{tab} - - ))} - - - - {/* Tab content */} - - {renderTab()} - - - ) : ( - - ◈ - No character selected - Create a new character to get started - - )} - - - {exportModal && activeCharacter && ( - setExportModal(false)} /> - )} - - ); -} diff --git a/homeai-character/eslint.config.js b/homeai-character/eslint.config.js new file mode 100644 index 0000000..4fa125d --- /dev/null +++ b/homeai-character/eslint.config.js @@ -0,0 +1,29 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{js,jsx}'], + extends: [ + js.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + ecmaVersion: 'latest', + ecmaFeatures: { jsx: true }, + sourceType: 'module', + }, + }, + rules: { + 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + }, + }, +]) diff --git a/homeai-character/index.html b/homeai-character/index.html new file mode 100644 index 0000000..d997ef7 --- /dev/null +++ b/homeai-character/index.html @@ -0,0 +1,13 @@ + + + + + + + homeai-character + + + + + + diff --git a/homeai-character/package-lock.json b/homeai-character/package-lock.json new file mode 100644 index 0000000..063ffeb --- /dev/null +++ b/homeai-character/package-lock.json @@ -0,0 +1,3339 @@ +{ + "name": "homeai-character", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "homeai-character", + "version": "0.0.0", + "dependencies": { + "@tailwindcss/vite": "^4.2.1", + "ajv": "^8.18.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "tailwindcss": "^4.2.1" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "vite": "^8.0.0-beta.13" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "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/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "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/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@oxc-project/runtime": { + "version": "0.115.0", + "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.115.0.tgz", + "integrity": "sha512-Rg8Wlt5dCbXhQnsXPrkOjL1DTSvXLgb2R/KYfnf1/K+R0k6UMLEmbQXPM+kwrWqSmWA2t0B1EtHy2/3zikQpvQ==", + "license": "MIT", + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.115.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.115.0.tgz", + "integrity": "sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.8.tgz", + "integrity": "sha512-5bcmMQDWEfWUq3m79Mcf/kbO6e5Jr6YjKSsA1RnpXR6k73hQ9z1B17+4h93jXpzHvS18p7bQHM1HN/fSd+9zog==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.8.tgz", + "integrity": "sha512-dcHPd5N4g9w2iiPRJmAvO0fsIWzF2JPr9oSuTjxLL56qu+oML5aMbBMNwWbk58Mt3pc7vYs9CCScwLxdXPdRsg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.8.tgz", + "integrity": "sha512-mw0VzDvoj8AuR761QwpdCFN0sc/jspuc7eRYJetpLWd+XyansUrH3C7IgNw6swBOgQT9zBHNKsVCjzpfGJlhUA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.8.tgz", + "integrity": "sha512-xNrRa6mQ9NmMIJBdJtPMPG8Mso0OhM526pDzc/EKnRrIrrkHD1E0Z6tONZRmUeJElfsQ6h44lQQCcDilSNIvSQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.8.tgz", + "integrity": "sha512-WgCKoO6O/rRUwimWfEJDeztwJJmuuX0N2bYLLRxmXDTtCwjToTOqk7Pashl/QpQn3H/jHjx0b5yCMbcTVYVpNg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.8.tgz", + "integrity": "sha512-tOHgTOQa8G4Z3ULj4G3NYOGGJEsqPHR91dT72u63OtVsZ7B6wFJKOx+ZKv+pvwzxWz92/I2ycaqi2/Ll4l+rlg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.8.tgz", + "integrity": "sha512-oRbxcgDujCi2Yp1GTxoUFsIFlZsuPHU4OV4AzNc3/6aUmR4lfm9FK0uwQu82PJsuUwnF2jFdop3Ep5c1uK7Uxg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.8.tgz", + "integrity": "sha512-oaLRyUHw8kQE5M89RqrDJZ10GdmGJcMeCo8tvaE4ukOofqgjV84AbqBSH6tTPjeT2BHv+xlKj678GBuIb47lKA==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.8.tgz", + "integrity": "sha512-1hjSKFrod5MwBBdLOOA0zpUuSfSDkYIY+QqcMcIU1WOtswZtZdUkcFcZza9b2HcAb0bnpmmyo0LZcaxLb2ov1g==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.8.tgz", + "integrity": "sha512-a1+F0aV4Wy9tT3o+cHl3XhOy6aFV+B8Ll+/JFj98oGkb6lGk3BNgrxd+80RwYRVd23oLGvj3LwluKYzlv1PEuw==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.8.tgz", + "integrity": "sha512-bGyXCFU11seFrf7z8PcHSwGEiFVkZ9vs+auLacVOQrVsI8PFHJzzJROF3P6b0ODDmXr0m6Tj5FlDhcXVk0Jp8w==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.8.tgz", + "integrity": "sha512-n8d+L2bKgf9G3+AM0bhHFWdlz9vYKNim39ujRTieukdRek0RAo2TfG2uEnV9spa4r4oHUfL9IjcY3M9SlqN1gw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.8.tgz", + "integrity": "sha512-4R4iJDIk7BrJdteAbEAICXPoA7vZoY/M0OBfcRlQxzQvUYMcEp2GbC/C8UOgQJhu2TjGTpX1H8vVO1xHWcRqQA==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.8.tgz", + "integrity": "sha512-3lwnklba9qQOpFnQ7EW+A1m4bZTWXZE4jtehsZ0YOl2ivW1FQqp5gY7X2DLuKITggesyuLwcmqS11fA7NtrmrA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.8", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.8.tgz", + "integrity": "sha512-VGjCx9Ha1P/r3tXGDZyG0Fcq7Q0Afnk64aaKzr1m40vbn1FL8R3W0V1ELDvPgzLXaaqK/9PnsqSaLWXfn6JtGQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tailwindcss/node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz", + "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", + "jiti": "^2.6.1", + "lightningcss": "1.31.1", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.2.1" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", + "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.31.1", + "lightningcss-darwin-arm64": "1.31.1", + "lightningcss-darwin-x64": "1.31.1", + "lightningcss-freebsd-x64": "1.31.1", + "lightningcss-linux-arm-gnueabihf": "1.31.1", + "lightningcss-linux-arm64-gnu": "1.31.1", + "lightningcss-linux-arm64-musl": "1.31.1", + "lightningcss-linux-x64-gnu": "1.31.1", + "lightningcss-linux-x64-musl": "1.31.1", + "lightningcss-win32-arm64-msvc": "1.31.1", + "lightningcss-win32-x64-msvc": "1.31.1" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-android-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", + "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-darwin-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", + "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-darwin-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", + "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-freebsd-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", + "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", + "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", + "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-arm64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", + "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-x64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", + "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-linux-x64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", + "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", + "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/node/node_modules/lightningcss-win32-x64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", + "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz", + "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-x64": "4.2.1", + "@tailwindcss/oxide-freebsd-x64": "4.2.1", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-x64-musl": "4.2.1", + "@tailwindcss/oxide-wasm32-wasi": "4.2.1", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz", + "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz", + "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz", + "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz", + "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz", + "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz", + "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz", + "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz", + "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz", + "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz", + "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.8.1", + "@emnapi/runtime": "^1.8.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.1", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz", + "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz", + "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz", + "integrity": "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.2.1", + "@tailwindcss/oxide": "4.2.1", + "tailwindcss": "4.2.1" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz", + "integrity": "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.29.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-rc.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", + "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "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.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001777", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz", + "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==", + "dev": true, + "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/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.307", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz", + "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.20.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", + "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", + "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", + "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/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==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", + "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/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/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.8", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.8.tgz", + "integrity": "sha512-RGOL7mz/aoQpy/y+/XS9iePBfeNRDUdozrhCEJxdpJyimW8v6yp4c30q6OviUU5AnUJVLRL9GP//HUs6N3ALrQ==", + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.115.0", + "@rolldown/pluginutils": "1.0.0-rc.8" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.8", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.8", + "@rolldown/binding-darwin-x64": "1.0.0-rc.8", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.8", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.8", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.8", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.8", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.8", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.8", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.8", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.8", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.8", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.8", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.8", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.8" + } + }, + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.8", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.8.tgz", + "integrity": "sha512-wzJwL82/arVfeSP3BLr1oTy40XddjtEdrdgtJ4lLRBu06mP3q/8HGM6K0JRlQuTA3XB0pNJx2so/nmpY4xyOew==", + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tailwindcss": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz", + "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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==", + "dev": true, + "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/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "8.0.0-beta.18", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.0-beta.18.tgz", + "integrity": "sha512-azgNbWdsO/WBqHQxwSCy+zd+Fq+37Fix2hn64cQuiUvaaGGSUac7f8RGQhI1aQl9OKbfWblrCFLWs+tln06c2A==", + "license": "MIT", + "dependencies": { + "@oxc-project/runtime": "0.115.0", + "lightningcss": "^1.31.1", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rolldown": "1.0.0-rc.8", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.0.0-alpha.31", + "esbuild": "^0.27.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/homeai-character/package.json b/homeai-character/package.json new file mode 100644 index 0000000..cc7c3b6 --- /dev/null +++ b/homeai-character/package.json @@ -0,0 +1,33 @@ +{ + "name": "homeai-character", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@tailwindcss/vite": "^4.2.1", + "ajv": "^8.18.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "tailwindcss": "^4.2.1" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "vite": "^8.0.0-beta.13" + }, + "overrides": { + "vite": "^8.0.0-beta.13" + } +} diff --git a/homeai-character/public/vite.svg b/homeai-character/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/homeai-character/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/homeai-character/schema/character.schema.json b/homeai-character/schema/character.schema.json new file mode 100644 index 0000000..bd524dc --- /dev/null +++ b/homeai-character/schema/character.schema.json @@ -0,0 +1,82 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "HomeAI Character Config", + "version": "1", + "type": "object", + "required": ["schema_version", "name", "system_prompt", "tts"], + "properties": { + "schema_version": { "type": "integer", "const": 1 }, + "name": { "type": "string" }, + "display_name": { "type": "string" }, + "description": { "type": "string" }, + + "system_prompt": { "type": "string" }, + + "model_overrides": { + "type": "object", + "properties": { + "primary": { "type": "string" }, + "fast": { "type": "string" } + } + }, + + "tts": { + "type": "object", + "required": ["engine"], + "properties": { + "engine": { + "type": "string", + "enum": ["kokoro", "chatterbox", "qwen3", "elevenlabs"] + }, + "voice_ref_path": { "type": "string" }, + "kokoro_voice": { "type": "string" }, + "elevenlabs_voice_id": { "type": "string" }, + "elevenlabs_model": { "type": "string", "default": "eleven_monolingual_v1" }, + "speed": { "type": "number", "default": 1.0 } + } + }, + + "live2d_expressions": { + "type": "object", + "description": "Maps semantic state to VTube Studio hotkey ID", + "properties": { + "idle": { "type": "string" }, + "listening": { "type": "string" }, + "thinking": { "type": "string" }, + "speaking": { "type": "string" }, + "happy": { "type": "string" }, + "sad": { "type": "string" }, + "surprised": { "type": "string" }, + "error": { "type": "string" } + } + }, + + "vtube_ws_triggers": { + "type": "object", + "description": "VTube Studio WebSocket actions keyed by event name", + "additionalProperties": { + "type": "object", + "properties": { + "type": { "type": "string", "enum": ["hotkey", "parameter"] }, + "id": { "type": "string" }, + "value": { "type": "number" } + } + } + }, + + "custom_rules": { + "type": "array", + "description": "Trigger/response overrides for specific contexts", + "items": { + "type": "object", + "properties": { + "trigger": { "type": "string" }, + "response": { "type": "string" }, + "condition": { "type": "string" } + } + } + }, + + "notes": { "type": "string" } + } +} \ No newline at end of file diff --git a/homeai-character/setup.sh b/homeai-character/setup.sh deleted file mode 100644 index 83cbe13..0000000 --- a/homeai-character/setup.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env bash -# homeai-character/setup.sh — P5: Character Manager + persona JSON -# -# Components: -# - character.schema.json — v1 character config schema -# - aria.json — default character config -# - Character Manager UI — Vite/React app for editing (dev server :5173) -# -# No hard runtime dependencies (can be developed standalone). -# Output (aria.json) is consumed by P3, P4, P7. - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" -source "${REPO_DIR}/scripts/common.sh" - -log_section "P5: Character Manager" -detect_platform - -# ─── Prerequisite check ──────────────────────────────────────────────────────── -log_info "Checking prerequisites..." - -if ! command_exists node; then - log_warn "Node.js not found — required for Character Manager UI" - log_warn "Install: https://nodejs.org (v18+ recommended)" -fi - -# ─── TODO: Implementation ────────────────────────────────────────────────────── -cat <<'EOF' - - ┌─────────────────────────────────────────────────────────────────┐ - │ P5: homeai-character — NOT YET IMPLEMENTED │ - │ │ - │ Implementation steps: │ - │ 1. Create schema/character.schema.json (v1) │ - │ 2. Create characters/aria.json (default persona) │ - │ 3. Set up Vite/React project in src/ │ - │ 4. Extend character-manager.jsx with full UI │ - │ 5. Add schema validation (ajv) │ - │ 6. Add expression mapper UI for Live2D │ - │ 7. Wire export to ~/.openclaw/characters/ │ - │ │ - │ Dev server: │ - │ cd homeai-character && npm run dev → http://localhost:5173 │ - │ │ - │ Interface contracts: │ - │ Output: ~/.openclaw/characters/.json │ - │ Schema: homeai-character/schema/character.schema.json │ - └─────────────────────────────────────────────────────────────────┘ - -EOF - -log_info "P5 is not yet implemented. See homeai-character/PLAN.md for details." -exit 0 diff --git a/homeai-character/src/App.css b/homeai-character/src/App.css new file mode 100644 index 0000000..b9d355d --- /dev/null +++ b/homeai-character/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/homeai-character/src/App.jsx b/homeai-character/src/App.jsx new file mode 100644 index 0000000..07debfa --- /dev/null +++ b/homeai-character/src/App.jsx @@ -0,0 +1,11 @@ +import CharacterManager from './CharacterManager' + +function App() { + return ( + + + + ) +} + +export default App diff --git a/homeai-character/src/CharacterManager.jsx b/homeai-character/src/CharacterManager.jsx new file mode 100644 index 0000000..c42b49d --- /dev/null +++ b/homeai-character/src/CharacterManager.jsx @@ -0,0 +1,423 @@ +import React, { useState } from 'react'; +import { validateCharacter } from './SchemaValidator'; + +const DEFAULT_CHARACTER = { + schema_version: 1, + name: "aria", + display_name: "Aria", + description: "Default HomeAI assistant persona", + system_prompt: "You are Aria, a warm, curious, and helpful AI assistant living in the home. You speak naturally and conversationally — never robotic. You are knowledgeable but never condescending. You remember the people you live with and build on those memories over time. Keep responses concise when controlling smart home devices; be more expressive in casual conversation. Never break character.", + model_overrides: { + primary: "llama3.3:70b", + fast: "qwen2.5:7b" + }, + tts: { + engine: "kokoro", + kokoro_voice: "af_heart", + speed: 1.0 + }, + live2d_expressions: { + idle: "expr_idle", + listening: "expr_listening", + thinking: "expr_thinking", + speaking: "expr_speaking", + happy: "expr_happy", + sad: "expr_sad", + surprised: "expr_surprised", + error: "expr_error" + }, + vtube_ws_triggers: { + thinking: { type: "hotkey", id: "expr_thinking" }, + speaking: { type: "hotkey", id: "expr_speaking" }, + idle: { type: "hotkey", id: "expr_idle" } + }, + custom_rules: [ + { trigger: "good morning", response: "Good morning! How did you sleep?", condition: "time_of_day == morning" } + ], + notes: "" +}; + +export default function CharacterManager() { + const [character, setCharacter] = useState(DEFAULT_CHARACTER); + const [error, setError] = useState(null); + + // ElevenLabs state + const [elevenLabsApiKey, setElevenLabsApiKey] = useState(localStorage.getItem('elevenlabs_api_key') || ''); + const [elevenLabsVoices, setElevenLabsVoices] = useState([]); + const [elevenLabsModels, setElevenLabsModels] = useState([]); + const [isLoadingElevenLabs, setIsLoadingElevenLabs] = useState(false); + + const fetchElevenLabsData = async (key) => { + if (!key) return; + setIsLoadingElevenLabs(true); + try { + const headers = { 'xi-api-key': key }; + + const [voicesRes, modelsRes] = await Promise.all([ + fetch('https://api.elevenlabs.io/v1/voices', { headers }), + fetch('https://api.elevenlabs.io/v1/models', { headers }) + ]); + + if (!voicesRes.ok || !modelsRes.ok) { + throw new Error('Failed to fetch from ElevenLabs API (check API key)'); + } + + const voicesData = await voicesRes.json(); + const modelsData = await modelsRes.json(); + + setElevenLabsVoices(voicesData.voices || []); + setElevenLabsModels(modelsData.filter(m => m.can_do_text_to_speech) || []); + localStorage.setItem('elevenlabs_api_key', key); + } catch (err) { + setError(err.message); + } finally { + setIsLoadingElevenLabs(false); + } + }; + + // Automatically fetch if key exists on load + React.useEffect(() => { + if (elevenLabsApiKey && character.tts.engine === 'elevenlabs') { + fetchElevenLabsData(elevenLabsApiKey); + } + }, [character.tts.engine]); + + const handleExport = () => { + try { + validateCharacter(character); + setError(null); + const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(character, null, 2)); + const downloadAnchorNode = document.createElement('a'); + downloadAnchorNode.setAttribute("href", dataStr); + downloadAnchorNode.setAttribute("download", `${character.name || 'character'}.json`); + document.body.appendChild(downloadAnchorNode); + downloadAnchorNode.click(); + downloadAnchorNode.remove(); + } catch (err) { + setError(err.message); + } + }; + + const handleImport = (e) => { + const file = e.target.files[0]; + if (!file) return; + const reader = new FileReader(); + reader.onload = (e) => { + try { + const importedChar = JSON.parse(e.target.result); + validateCharacter(importedChar); + setCharacter(importedChar); + setError(null); + } catch (err) { + setError(`Import failed: ${err.message}`); + } + }; + reader.readAsText(file); + }; + + const handleChange = (field, value) => { + setCharacter(prev => ({ ...prev, [field]: value })); + }; + + const handleNestedChange = (parent, field, value) => { + setCharacter(prev => ({ + ...prev, + [parent]: { ...prev[parent], [field]: value } + })); + }; + + const handleRuleChange = (index, field, value) => { + setCharacter(prev => { + const newRules = [...(prev.custom_rules || [])]; + newRules[index] = { ...newRules[index], [field]: value }; + return { ...prev, custom_rules: newRules }; + }); + }; + + const addRule = () => { + setCharacter(prev => ({ + ...prev, + custom_rules: [...(prev.custom_rules || []), { trigger: "", response: "", condition: "" }] + })); + }; + + const removeRule = (index) => { + setCharacter(prev => { + const newRules = [...(prev.custom_rules || [])]; + newRules.splice(index, 1); + return { ...prev, custom_rules: newRules }; + }); + }; + + const previewTTS = async () => { + const text = `Hi, I am ${character.display_name}. This is a preview of my voice.`; + + if (character.tts.engine === 'kokoro') { + try { + const response = await fetch('http://localhost:8081/api/tts', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ text, voice: character.tts.kokoro_voice }) + }); + + if (!response.ok) throw new Error('Failed to fetch Kokoro audio'); + + const blob = await response.blob(); + const url = URL.createObjectURL(blob); + const audio = new Audio(url); + audio.play(); + } catch (err) { + setError(`Kokoro preview failed: ${err.message}. Falling back to browser TTS.`); + runBrowserTTS(text); + } + } else { + runBrowserTTS(text); + } + }; + + const runBrowserTTS = (text) => { + const utterance = new SpeechSynthesisUtterance(text); + utterance.rate = character.tts.speed; + const voices = window.speechSynthesis.getVoices(); + const preferredVoice = voices.find(v => v.lang.startsWith('en') && v.name.includes('Female')) || voices.find(v => v.lang.startsWith('en')); + if (preferredVoice) utterance.voice = preferredVoice; + window.speechSynthesis.cancel(); + window.speechSynthesis.speak(utterance); + }; + + return ( + + + HomeAI Character Manager + + + Import JSON + + + + Export JSON + + + + + {error && {error}} + + + + Basic Info + + Name (ID) + handleChange('name', e.target.value)} /> + + + Display Name + handleChange('display_name', e.target.value)} /> + + + Description + handleChange('description', e.target.value)} /> + + + + + TTS Configuration + + Engine + handleNestedChange('tts', 'engine', e.target.value)}> + Kokoro + Chatterbox + Qwen3 + ElevenLabs + + + {character.tts.engine === 'elevenlabs' && ( + + + ElevenLabs API Key (Local Use Only) + + setElevenLabsApiKey(e.target.value)} /> + fetchElevenLabsData(elevenLabsApiKey)} disabled={isLoadingElevenLabs} className="bg-blue-500 text-white px-3 py-1 rounded text-sm whitespace-nowrap hover:bg-blue-600 disabled:opacity-50"> + {isLoadingElevenLabs ? 'Loading...' : 'Fetch'} + + + + + + + Voice ID + {elevenLabsVoices.length > 0 ? ( + handleNestedChange('tts', 'elevenlabs_voice_id', e.target.value)}> + -- Select Voice -- + {elevenLabsVoices.map(v => ( + {v.name} ({v.category}) + ))} + + ) : ( + handleNestedChange('tts', 'elevenlabs_voice_id', e.target.value)} placeholder="e.g. 21m00Tcm4TlvDq8ikWAM" /> + )} + + + Model + {elevenLabsModels.length > 0 ? ( + handleNestedChange('tts', 'elevenlabs_model', e.target.value)}> + -- Select Model -- + {elevenLabsModels.map(m => ( + {m.name} ({m.model_id}) + ))} + + ) : ( + handleNestedChange('tts', 'elevenlabs_model', e.target.value)} placeholder="e.g. eleven_monolingual_v1" /> + )} + + + + )} + {character.tts.engine === 'kokoro' && ( + + Kokoro Voice + handleNestedChange('tts', 'kokoro_voice', e.target.value)}> + af_heart (American Female) + af_alloy (American Female) + af_aoede (American Female) + af_bella (American Female) + af_jessica (American Female) + af_kore (American Female) + af_nicole (American Female) + af_nova (American Female) + af_river (American Female) + af_sarah (American Female) + af_sky (American Female) + am_adam (American Male) + am_echo (American Male) + am_eric (American Male) + am_fenrir (American Male) + am_liam (American Male) + am_michael (American Male) + am_onyx (American Male) + am_puck (American Male) + am_santa (American Male) + bf_alice (British Female) + bf_emma (British Female) + bf_isabella (British Female) + bf_lily (British Female) + bm_daniel (British Male) + bm_fable (British Male) + bm_george (British Male) + bm_lewis (British Male) + + + )} + {character.tts.engine === 'chatterbox' && ( + + Voice Reference Path + handleNestedChange('tts', 'voice_ref_path', e.target.value)} /> + + )} + + Speed: {character.tts.speed} + handleNestedChange('tts', 'speed', parseFloat(e.target.value))} /> + + + 🔊 Preview Voice Speed + + Note: Kokoro previews are fetched from the local bridge. Others use browser TTS for speed testing. + + + + + + System Prompt + {character.system_prompt.length} chars + + handleChange('system_prompt', e.target.value)} + /> + + + + + Live2D Expressions + {Object.entries(character.live2d_expressions).map(([key, val]) => ( + + {key} + handleNestedChange('live2d_expressions', key, e.target.value)} /> + + ))} + + + + Model Overrides + + Primary Model + handleNestedChange('model_overrides', 'primary', e.target.value)}> + llama3.3:70b + qwen2.5:7b + qwen3:32b + codestral:22b + gemma-3-27b + DeepSeek-R1-8B + + + + Fast Model + handleNestedChange('model_overrides', 'fast', e.target.value)}> + qwen2.5:7b + llama3.3:70b + qwen3:32b + codestral:22b + gemma-3-27b + DeepSeek-R1-8B + + + + + + + + Custom Rules + + + Add Rule + + + + {(!character.custom_rules || character.custom_rules.length === 0) ? ( + No custom rules defined. + ) : ( + + {character.custom_rules.map((rule, idx) => ( + + removeRule(idx)} + className="absolute top-2 right-2 text-red-500 hover:text-red-700" + title="Remove Rule" + > + ✕ + + + + Trigger + handleRuleChange(idx, 'trigger', e.target.value)} /> + + + Condition (Optional) + handleRuleChange(idx, 'condition', e.target.value)} placeholder="e.g. time_of_day == morning" /> + + + Response + handleRuleChange(idx, 'response', e.target.value)} /> + + + + ))} + + )} + + + + ); +} diff --git a/homeai-character/src/SchemaValidator.js b/homeai-character/src/SchemaValidator.js new file mode 100644 index 0000000..2772682 --- /dev/null +++ b/homeai-character/src/SchemaValidator.js @@ -0,0 +1,13 @@ +import Ajv from 'ajv' +import schema from '../schema/character.schema.json' + +const ajv = new Ajv({ allErrors: true, strict: false }) +const validate = ajv.compile(schema) + +export function validateCharacter(config) { + const valid = validate(config) + if (!valid) { + throw new Error(ajv.errorsText(validate.errors)) + } + return true +} diff --git a/homeai-character/src/assets/react.svg b/homeai-character/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/homeai-character/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/homeai-character/src/index.css b/homeai-character/src/index.css new file mode 100644 index 0000000..a461c50 --- /dev/null +++ b/homeai-character/src/index.css @@ -0,0 +1 @@ +@import "tailwindcss"; \ No newline at end of file diff --git a/homeai-character/src/main.jsx b/homeai-character/src/main.jsx new file mode 100644 index 0000000..b9a1a6d --- /dev/null +++ b/homeai-character/src/main.jsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.jsx' + +createRoot(document.getElementById('root')).render( + + + , +) diff --git a/homeai-character/vite.config.js b/homeai-character/vite.config.js new file mode 100644 index 0000000..108d422 --- /dev/null +++ b/homeai-character/vite.config.js @@ -0,0 +1,11 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import tailwindcss from '@tailwindcss/vite' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [ + tailwindcss(), + react(), + ], +}) diff --git a/homeai-voice/scripts/launchd/com.homeai.wakeword.plist b/homeai-voice/scripts/launchd/com.homeai.wakeword.plist index feb5df9..95a0b25 100644 --- a/homeai-voice/scripts/launchd/com.homeai.wakeword.plist +++ b/homeai-voice/scripts/launchd/com.homeai.wakeword.plist @@ -13,7 +13,7 @@ --wake-word hey_jarvis --notify-url - http://localhost:8080/wake + http://localhost:8081/wake RunAtLoad diff --git a/homeai-voice/scripts/launchd/com.homeai.wyoming-elevenlabs.plist b/homeai-voice/scripts/launchd/com.homeai.wyoming-elevenlabs.plist new file mode 100644 index 0000000..0bc18a7 --- /dev/null +++ b/homeai-voice/scripts/launchd/com.homeai.wyoming-elevenlabs.plist @@ -0,0 +1,28 @@ + + + + + Label + com.homeai.wyoming-elevenlabs + ProgramArguments + + /Users/aodhan/homeai-voice-env/bin/python3 + /Users/aodhan/gitea/homeai/homeai-voice/tts/wyoming_elevenlabs_server.py + --uri + tcp://0.0.0.0:10302 + + RunAtLoad + + KeepAlive + + StandardOutPath + /tmp/homeai-wyoming-elevenlabs.log + StandardErrorPath + /tmp/homeai-wyoming-elevenlabs.log + EnvironmentVariables + + PATH + /opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin + + + diff --git a/homeai-voice/scripts/launchd/com.homeai.wyoming-satellite.plist b/homeai-voice/scripts/launchd/com.homeai.wyoming-satellite.plist index f0f3677..4be07d9 100644 --- a/homeai-voice/scripts/launchd/com.homeai.wyoming-satellite.plist +++ b/homeai-voice/scripts/launchd/com.homeai.wyoming-satellite.plist @@ -18,9 +18,9 @@ --area Living Room --mic-command - rec -q -r 16000 -c 1 -b 16 -t raw - + /opt/homebrew/bin/rec -q -r 16000 -c 1 -b 16 -t raw - --snd-command - play -q -r 24000 -c 1 -b 16 -t raw - + /opt/homebrew/bin/play -q -t raw -r 24000 -c 1 -b 16 -e signed-integer - --mic-command-rate 16000 --mic-command-width @@ -33,10 +33,18 @@ 2 --snd-command-channels 1 + --wake-command + /Users/aodhan/homeai-voice-env/bin/python3 /Users/aodhan/gitea/homeai/homeai-voice/wyoming/wakeword_command.py --wake-word hey_jarvis --threshold 0.5 + --wake-command-rate + 16000 + --wake-command-width + 2 + --wake-command-channels + 1 --awake-wav - /System/Library/Sounds/Glass.aiff + /Users/aodhan/homeai-data/sounds/awake.wav --done-wav - /System/Library/Sounds/Blow.aiff + /Users/aodhan/homeai-data/sounds/done.wav --no-zeroconf diff --git a/homeai-voice/scripts/monitor-wakeword.sh b/homeai-voice/scripts/monitor-wakeword.sh new file mode 100644 index 0000000..dcd55ea --- /dev/null +++ b/homeai-voice/scripts/monitor-wakeword.sh @@ -0,0 +1,10 @@ +#!/bin/bash +# Monitor wake word detection in real-time + +echo "Monitoring wake word detection..." +echo "Say 'Hey Jarvis' to test" +echo "Press Ctrl+C to stop" +echo "" + +# Watch both the wake word log and bridge log +tail -f /tmp/homeai-wakeword-error.log /tmp/homeai-openclaw-bridge.log 2>/dev/null | grep -E "(Wake word detected|Listening|Failed to notify)" \ No newline at end of file diff --git a/homeai-voice/tts/wyoming_elevenlabs_server.py b/homeai-voice/tts/wyoming_elevenlabs_server.py new file mode 100644 index 0000000..1431fba --- /dev/null +++ b/homeai-voice/tts/wyoming_elevenlabs_server.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 +"""Wyoming TTS server backed by ElevenLabs. + +Usage: + python wyoming_elevenlabs_server.py --uri tcp://0.0.0.0:10302 --voice-id 21m00Tcm4TlvDq8ikWAM +""" + +import argparse +import asyncio +import logging +import os +import wave +import io +from urllib import request, error + +from wyoming.audio import AudioChunk, AudioStart, AudioStop +from wyoming.event import Event +from wyoming.info import Attribution, Info, TtsProgram, TtsVoice, TtsVoiceSpeaker +from wyoming.server import AsyncEventHandler, AsyncServer +from wyoming.tts import Synthesize + +_LOGGER = logging.getLogger(__name__) + +SAMPLE_RATE = 24000 +SAMPLE_WIDTH = 2 # int16 +CHANNELS = 1 +CHUNK_SECONDS = 1 # stream in 1-second chunks + + +class ElevenLabsEventHandler(AsyncEventHandler): + def __init__(self, default_voice_id: str, default_model: str, api_key: str, speed: float, *args, **kwargs): + super().__init__(*args, **kwargs) + self._default_voice_id = default_voice_id + self._default_model = default_model + self._api_key = api_key + self._speed = speed + + # Send info immediately on connect + asyncio.ensure_future(self._send_info()) + + async def _send_info(self): + info = Info( + tts=[ + TtsProgram( + name="elevenlabs", + description="ElevenLabs API TTS", + attribution=Attribution( + name="ElevenLabs", + url="https://elevenlabs.io/", + ), + installed=True, + version="1.0.0", + voices=[ + TtsVoice( + name=self._default_voice_id, + description="ElevenLabs Voice", + attribution=Attribution(name="elevenlabs", url=""), + installed=True, + languages=["en-us"], + version="1.0", + speakers=[TtsVoiceSpeaker(name=self._default_voice_id)], + ) + ], + ) + ] + ) + await self.write_event(info.event()) + + async def handle_event(self, event: Event) -> bool: + if Synthesize.is_type(event.type): + synthesize = Synthesize.from_event(event) + text = synthesize.text + voice_id = self._default_voice_id + + if synthesize.voice and synthesize.voice.name: + voice_id = synthesize.voice.name + + _LOGGER.debug("Synthesizing %r with voice_id=%s model=%s", text, voice_id, self._default_model) + + try: + loop = asyncio.get_event_loop() + audio_bytes = await loop.run_in_executor( + None, lambda: self._call_elevenlabs_api(text, voice_id) + ) + + if audio_bytes is None: + raise Exception("Failed to generate audio from ElevenLabs") + + await self.write_event( + AudioStart(rate=SAMPLE_RATE, width=SAMPLE_WIDTH, channels=CHANNELS).event() + ) + + chunk_size = SAMPLE_RATE * SAMPLE_WIDTH * CHANNELS * CHUNK_SECONDS + for i in range(0, len(audio_bytes), chunk_size): + await self.write_event( + AudioChunk( + rate=SAMPLE_RATE, + width=SAMPLE_WIDTH, + channels=CHANNELS, + audio=audio_bytes[i : i + chunk_size], + ).event() + ) + + await self.write_event(AudioStop().event()) + _LOGGER.info("Synthesized audio completed") + + except Exception: + _LOGGER.exception("Synthesis error") + await self.write_event(AudioStop().event()) + + return True # keep connection open + + def _call_elevenlabs_api(self, text: str, voice_id: str) -> bytes: + import json + url = f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}?output_format=pcm_24000" + + headers = { + "Accept": "audio/pcm", + "Content-Type": "application/json", + "xi-api-key": self._api_key + } + + data = { + "text": text, + "model_id": self._default_model, + } + + req = request.Request(url, data=json.dumps(data).encode('utf-8'), headers=headers, method='POST') + try: + with request.urlopen(req) as response: + if response.status == 200: + return response.read() + else: + _LOGGER.error(f"ElevenLabs API Error: {response.status}") + return None + except error.HTTPError as e: + _LOGGER.error(f"ElevenLabs HTTP Error: {e.code} - {e.read().decode('utf-8')}") + return None + except Exception as e: + _LOGGER.error(f"ElevenLabs Request Error: {str(e)}") + return None + + +async def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--uri", default="tcp://0.0.0.0:10302") + parser.add_argument("--voice-id", default="21m00Tcm4TlvDq8ikWAM", help="Default ElevenLabs Voice ID") + parser.add_argument("--model", default="eleven_monolingual_v1", help="ElevenLabs Model ID") + parser.add_argument("--speed", type=float, default=1.0) + parser.add_argument("--debug", action="store_true") + args = parser.parse_args() + + logging.basicConfig( + level=logging.DEBUG if args.debug else logging.INFO, + format="%(asctime)s %(levelname)s %(name)s %(message)s", + ) + + api_key = os.environ.get("ELEVENLABS_API_KEY") + if not api_key: + # Try to read from .env file directly if not exported in shell + try: + env_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), '.env') + if os.path.exists(env_path): + with open(env_path, 'r') as f: + for line in f: + if line.startswith('ELEVENLABS_API_KEY='): + api_key = line.split('=', 1)[1].strip() + break + except Exception: + pass + + if not api_key: + _LOGGER.warning("ELEVENLABS_API_KEY environment variable not set. API calls will fail.") + + _LOGGER.info("Starting ElevenLabs Wyoming TTS on %s (voice-id=%s, model=%s)", args.uri, args.voice_id, args.model) + + server = AsyncServer.from_uri(args.uri) + + def handler_factory(reader, writer): + return ElevenLabsEventHandler(args.voice_id, args.model, api_key, args.speed, reader, writer) + + await server.run(handler_factory) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/homeai-voice/wyoming/wakeword_command.py b/homeai-voice/wyoming/wakeword_command.py new file mode 100644 index 0000000..ed62ce8 --- /dev/null +++ b/homeai-voice/wyoming/wakeword_command.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +"""Wake word detection command for Wyoming Satellite. + +The satellite feeds raw 16kHz 16-bit mono audio via stdin. +This script reads that audio, runs openWakeWord, and prints +the wake word name to stdout when detected. + +Usage (called by wyoming-satellite --wake-command): + python wakeword_command.py [--wake-word hey_jarvis] [--threshold 0.5] +""" + +import argparse +import sys +import numpy as np +import logging + +_LOGGER = logging.getLogger(__name__) + +SAMPLE_RATE = 16000 +CHUNK_SIZE = 1280 # ~80ms at 16kHz — recommended by openWakeWord + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--wake-word", default="hey_jarvis") + parser.add_argument("--threshold", type=float, default=0.5) + parser.add_argument("--cooldown", type=float, default=3.0) + parser.add_argument("--debug", action="store_true") + args = parser.parse_args() + + logging.basicConfig( + level=logging.DEBUG if args.debug else logging.WARNING, + format="%(asctime)s %(levelname)s %(message)s", + stream=sys.stderr, + ) + + import openwakeword + from openwakeword.model import Model + + oww = Model( + wakeword_models=[args.wake_word], + inference_framework="onnx", + ) + + import time + last_trigger = 0.0 + bytes_per_chunk = CHUNK_SIZE * 2 # 16-bit = 2 bytes per sample + + _LOGGER.debug("Wake word command ready, reading audio from stdin") + + try: + while True: + raw = sys.stdin.buffer.read(bytes_per_chunk) + if not raw: + break + if len(raw) < bytes_per_chunk: + # Pad with zeros if short read + raw = raw + b'\x00' * (bytes_per_chunk - len(raw)) + + chunk = np.frombuffer(raw, dtype=np.int16) + oww.predict(chunk) + + for ww, scores in oww.prediction_buffer.items(): + score = scores[-1] if scores else 0.0 + if score >= args.threshold: + now = time.time() + if now - last_trigger >= args.cooldown: + last_trigger = now + # Print wake word name to stdout — satellite reads this + print(ww, flush=True) + _LOGGER.debug("Wake word detected: %s (score=%.3f)", ww, score) + except (KeyboardInterrupt, BrokenPipeError): + pass + + +if __name__ == "__main__": + main() diff --git a/plans/p5_development_plan.md b/plans/p5_development_plan.md new file mode 100644 index 0000000..b139023 --- /dev/null +++ b/plans/p5_development_plan.md @@ -0,0 +1,92 @@ +# P5: HomeAI Character System Development Plan + +> Created: 2026-03-07 | Phase: 3 - Agent & Character + +## Overview +Phase 5 (P5) focuses on creating a unified, JSON-based character configuration system that serves as the single source of truth for the AI assistant's personality, voice, visual expressions, and behavioral rules. This configuration will be consumed by OpenClaw (P4), the Voice Pipeline (P3), and the Visual Layer (P7). + +A key component of this phase is building the **Character Manager UI**—a local React application that provides a user-friendly interface for editing character definitions, validating them against a strict JSON schema, and exporting them for use by the agent. + +--- + +## 1. Schema & Foundation + +The first step is establishing the strict data contract that all other services will rely on. + +### 1.1 Define Character Schema +- Create `homeai-character/schema/character.schema.json` (v1). +- Define required fields: `schema_version`, `name`, `system_prompt`, `tts`. +- Define optional/advanced fields: `model_overrides`, `live2d_expressions`, `vtube_ws_triggers`, `custom_rules`, `notes`. +- Document the schema in `homeai-character/schema/README.md`. + +### 1.2 Create Default Character Profile +- Create `homeai-character/characters/aria.json` conforming to the schema. +- Define the default system prompt for "Aria" (warm, helpful, concise for smart home tasks). +- Configure default TTS settings (`engine: "kokoro"`, `kokoro_voice: "af_heart"`). +- Add placeholder mappings for `live2d_expressions` and `vtube_ws_triggers`. + +--- + +## 2. Character Manager UI Development + +Transform the existing prototype (`character-manager.jsx`) into a fully functional local web tool. + +### 2.1 Project Initialization +- Scaffold a new Vite + React project in `homeai-character/src/`. +- Install necessary dependencies: `react`, `react-dom`, `ajv` (for schema validation), and styling utilities (e.g., Tailwind CSS). +- Migrate the existing `character-manager.jsx` into the new project structure. + +### 2.2 Schema Validation Integration +- Implement `SchemaValidator.js` using `ajv` to validate character configurations against `character.schema.json`. +- Enforce validation checks before allowing the user to export or save a character profile. +- Display clear error messages in the UI if validation fails. + +### 2.3 UI Feature Implementation +- **Basic Info & Prompt Editor:** Fields for name, description, and a multi-line editor for the system prompt (with character count). +- **TTS Configuration:** Dropdowns for engine selection (Kokoro, Chatterbox, Qwen3) and inputs for voice reference paths/speed. +- **Expression Mapping Table:** UI to map semantic states (idle, listening, thinking, speaking, etc.) to VTube Studio hotkey IDs. +- **Custom Rules Editor:** Interface to add, edit, and delete trigger/response/condition pairs. +- **Import/Export Pipeline:** Functionality to load an existing JSON file, edit it, and download/save the validated output. + +--- + +## 3. Pipeline Integration (Wiring it up) + +Ensure that the generated character configurations are actually used by the rest of the HomeAI ecosystem. + +### 3.1 OpenClaw Integration (P4 Link) +- Configure OpenClaw to load the active character from `~/.openclaw/characters/aria.json`. +- Modify OpenClaw's initialization to inject the `system_prompt` from the JSON into Ollama requests. +- Implement schema version checking in OpenClaw (fail gracefully if `schema_version` is unsupported). +- Ensure OpenClaw supports hot-reloading if the character JSON is updated. + +### 3.2 Voice Pipeline Integration (P3 Link) +- Update the TTS dispatch logic to read the `tts` configuration block from the character JSON. +- Dynamically route TTS requests based on the `engine` field (e.g., routing to Kokoro vs. Chatterbox). + +--- + +## 4. Custom Voice Cloning (Optional/Advanced) + +If moving beyond the default Kokoro voice, set up a custom voice clone. + +### 4.1 Audio Processing +- Record 30-60 seconds of clean reference audio for the character (`~/voices/aria-raw.wav`). +- Pre-process the audio using FFmpeg: `ffmpeg -i aria-raw.wav -ar 22050 -ac 1 aria.wav`. +- Move the processed file to the designated directory (`~/voices/aria.wav`). + +### 4.2 Configuration & Testing +- Update `aria.json` to use `"engine": "chatterbox"` and set `"voice_ref_path"` to the new audio file. +- Test the voice output. If the quality is insufficient, evaluate Qwen3-TTS as a fallback alternative. + +--- + +## Success Criteria Checklist + +- [ ] `character.schema.json` is fully defined and documented. +- [ ] `aria.json` is created and passes strict validation against the schema. +- [ ] Vite-based Character Manager UI runs locally without errors. +- [ ] Character Manager successfully imports, edits, validates, and exports character JSONs. +- [ ] OpenClaw successfully reads `aria.json` and applies the system prompt to LLM generation. +- [ ] TTS engine selection dynamically respects the configuration in the character JSON. +- [ ] (Optional) Custom voice reference audio is processed and tested. \ No newline at end of file
{hint}
{json}
Note: Kokoro previews are fetched from the local bridge. Others use browser TTS for speed testing.
No custom rules defined.