diff --git a/.gitignore b/.gitignore index 1419237..6e0c4ce 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,10 @@ release/ # Build *.tsbuildinfo build/ +# PyInstaller build artifacts (sidecar) — regenerated by `npm run sidecar:build` +apps/desktop/build/sidecar-build/ +apps/desktop/build/sidecar.spec + !apps/desktop/build/ !apps/desktop/build/entitlements.mac.plist sidecar-dist/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3454c8d..fdcef83 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -243,6 +243,11 @@ package-windows: $env:CSC_LINK = (Resolve-Path -LiteralPath $env:WIN_CSC_PFX_FILE).Path $env:CSC_KEY_PASSWORD = $env:WIN_CSC_KEY_PASSWORD - node scripts/ci/sync-version.mjs --check --tag "$CI_COMMIT_TAG" + # 로컬 STT는 faster-whisper 사이드카에 의존한다. 이 번들이 빠지면 설치본에서 + # 전사가 전혀 동작하지 않으므로, 패키징 전에 반드시 빌드하고 검증한다. + - npm run sidecar:setup --workspace=@d3ro/desktop + - npm run sidecar:build --workspace=@d3ro/desktop + - node scripts/ci/verify-sidecar-bundle.mjs - npm run build --workspace=@d3ro/desktop - cd apps/desktop - npx electron-builder --win --x64 --config electron-builder.yml @@ -270,6 +275,10 @@ package-macos: - npm ci script: - node scripts/ci/sync-version.mjs --check --tag "$CI_COMMIT_TAG" + # 로컬 STT(faster-whisper 사이드카) 번들 — 누락 시 설치본 전사 불가. + - npm run sidecar:setup --workspace=@d3ro/desktop + - npm run sidecar:build --workspace=@d3ro/desktop + - node scripts/ci/verify-sidecar-bundle.mjs - npm run build --workspace=@d3ro/desktop - cd apps/desktop - npx electron-builder --mac --arm64 --config electron-builder.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 842a6d8..afe66c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,55 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Cloud-optional backup (encrypted, opt-in) - Plugin system for custom pipelines +## [1.3.0] - 2026-09-18 + +> Published from an annotated tag through CI. Installer and update metadata are +> served by the canonical Forgejo feed; no binaries are committed to this repository. + +### Added +- **Live partial transcript while dictating**: while the hotkey is held, the app now + transcribes the recent window of audio every 1.5 seconds and shows it in the recording + tip, so the text can be seen forming before the key is released. Partials never reach + the clipboard or the result popup; only the final transcription is inserted. +- **Instant first dictation**: the local speech engine (sidecar process + Whisper model) + is warmed up in the background at app start, so the first press does not wait for the + model to load. +- **Packaged local speech engine**: desktop installers now ship the faster-whisper + sidecar (`sidecar.exe` plus runtime data, including the Silero VAD model) and ffmpeg, so + local transcription works on a fresh install without Python on the machine. +- Sidecar build tooling: `npm --prefix apps/desktop run sidecar:setup` and + `sidecar:build`, plus a packaging-time bundle verifier that fails the build when the + engine or its VAD data is missing. + +### Changed +- Local engine connections now target the IPv4 loopback (`127.0.0.1`) instead of + `localhost`. On machines where `localhost` resolves only to IPv6, every local request + (Ollama and the speech sidecar) was refused and local AI silently did nothing. +- Whisper decoding is tuned for dictation: previous-text conditioning is disabled so + repeated hallucinations cannot compound, silence is trimmed more aggressively, and + low-confidence fallbacks are bounded. Same transcript quality, roughly five times + faster on the same machine. +- The sidecar reuses an already-loaded model instead of reloading it, and reports the + load time it measured. +- Local engine logs stream to the app log as complete UTF-8 lines instead of mangled + fragments, so failures are diagnosable. +- Failed local-engine startup now fails immediately with an actionable message (missing + bundled engine, damaged virtualenv, or missing SoX) instead of waiting for a 30 second + health check and reporting a generic error. + +### Fixed +- **Local transcription never worked in packaged builds**: the sidecar was not part of + the packaged resources and no pipeline step built it, so the app always fell back to a + system Python that had no faster-whisper installed. +- **Local transcription and local LLM never worked in development**: the sidecar and SoX + paths were resolved against the Vite output directory (`out/main`) instead of the app + root, so recording failed with a SoX `ENOENT` and the sidecar fell back to a Python + without the runtime dependencies. +- **Silero VAD data was missing from the bundled engine**, which would have made + silence-trimmed transcription fail at runtime even with the engine bundled. +- Audio capture and the speech sidecar no longer flash a console window on Windows, and + SoX/spawn failures name the fix (`npm --prefix apps/desktop run setup:sox`). + ## [1.2.0] - 2026-09-16 > Published from an annotated tag through CI. Installer and update metadata are diff --git a/apps/admin-swagger/openapi.json b/apps/admin-swagger/openapi.json index 140aded..1032f14 100644 --- a/apps/admin-swagger/openapi.json +++ b/apps/admin-swagger/openapi.json @@ -3,7 +3,7 @@ "info": { "title": "D3RO-VOICE Admin API", "description": "Admin CRM Edge Functions for user management, subscription CRUD, payment history, and audit logs.", - "version": "1.2.0" + "version": "1.3.0" }, "servers": [ { diff --git a/apps/admin/package.json b/apps/admin/package.json index 74e1de4..17c7840 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -1,6 +1,6 @@ { "name": "@d3ro/admin", - "version": "1.2.0", + "version": "1.3.0", "private": true, "description": "D3RO Voice Admin CRM — SaaS 관리 도구", "scripts": { diff --git a/apps/api-server/D3ROVoice.Api.csproj b/apps/api-server/D3ROVoice.Api.csproj index c62425c..9b8fda2 100644 --- a/apps/api-server/D3ROVoice.Api.csproj +++ b/apps/api-server/D3ROVoice.Api.csproj @@ -2,7 +2,7 @@ net10.0 - 1.2.0 + 1.3.0 enable enable diff --git a/apps/desktop/electron-builder.yml b/apps/desktop/electron-builder.yml index 8742df8..290de60 100644 --- a/apps/desktop/electron-builder.yml +++ b/apps/desktop/electron-builder.yml @@ -36,6 +36,8 @@ asarUnpack: - "node_modules/better-sqlite3/**" - "node_modules/uiohook-napi/**" - "node_modules/@nut-tree-fork/**" + # ffmpeg 정적 바이너리는 실행 파일이므로 asar 내부에서 spawn할 수 없다 + - "node_modules/@ffmpeg-installer/**" # ──────────────────────────────────────────────────────────────────── # Windows @@ -108,16 +110,29 @@ extraResources: - "*.wav" # SoX 바이너리 + # 주의: filter에 "**/*"를 쓰면 하위 디렉토리가 통째로 누락된다(실측). + # 디렉토리를 그대로 복사할 때는 filter를 지정하지 않는다. - from: resources/sox/ to: sox/ + + # faster-whisper STT 사이드카 (PyInstaller onedir: sidecar.exe + _internal/). + # 반드시 존재해야 한다. 누락되면 로컬 전사가 전혀 동작하지 않는다. + # 빌드: npm --prefix apps/desktop run sidecar:build + # electron-builder는 이 트리를 재귀로 복사한다(_internal 포함). + # 서명 검증에 실패하면 복사가 중간에 끊겨 _internal이 빠지므로, + # 서명 없이 로컬 검증할 때는 -c.win.forceCodeSigning=false 를 사용한다. + - from: sidecar-dist/sidecar/ + to: sidecar/ filter: - "**/*" + # ffmpeg (파일 전사/미디어 변환용). CI가 resources/ffmpeg/에 배치한다. + - from: resources/ffmpeg/ + to: ffmpeg/ + # Ollama 바이너리 (포터블 zip을 사전 배치) - from: resources/ollama/ to: ollama/ - filter: - - "**/*" nsis: # 파일명 공백 금지 — mac.artifactName 주석과 동일한 이유 (latest.yml url 정합) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 376115a..b7f828c 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,6 +1,6 @@ { "name": "@d3ro/desktop", - "version": "1.2.0", + "version": "1.3.0", "productName": "d3ro-voice", "description": "로컬 AI 음성 어시스턴트 (Electron)", "main": "./out/main/index.js", @@ -20,7 +20,10 @@ "dist": "electron-vite build && electron-builder --config electron-builder.yml", "dist:win": "electron-vite build && electron-builder --win --config electron-builder.yml --publish never", "dist:mac": "electron-vite build && electron-builder --mac --config electron-builder.yml --publish never -c.mac.identity=- -c.mac.hardenedRuntime=false", - "setup:sox": "powershell -ExecutionPolicy Bypass -File scripts/download-sox.ps1" + "setup:sox": "powershell -ExecutionPolicy Bypass -File scripts/download-sox.ps1", + "sidecar:setup": "node scripts/setup-sidecar.mjs", + "sidecar:build": "node scripts/build-sidecar.mjs", + "dist:win:full": "npm run sidecar:build && electron-vite build && electron-builder --win --config electron-builder.yml --publish never" }, "author": "D3RO", "license": "MIT", @@ -45,6 +48,7 @@ "@electron-toolkit/utils": "^4.0.0", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", + "@ffmpeg-installer/ffmpeg": "^1.1.0", "@mui/material": "^7.0.0", "@nut-tree-fork/nut-js": "^4.2.6", "@supabase/supabase-js": "^2.45.0", diff --git a/apps/desktop/resources/ffmpeg/.gitkeep b/apps/desktop/resources/ffmpeg/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/apps/desktop/scripts/build-sidecar.mjs b/apps/desktop/scripts/build-sidecar.mjs new file mode 100644 index 0000000..bec2783 --- /dev/null +++ b/apps/desktop/scripts/build-sidecar.mjs @@ -0,0 +1,127 @@ +// scripts/build-sidecar.mjs +// faster-whisper 사이드카를 PyInstaller(onedir)로 묶어 sidecar-dist/sidecar/ 에 만든다. +// +// 사용: npm --prefix apps/desktop run sidecar:build +// 출력: apps/desktop/sidecar-dist/sidecar/sidecar(.exe) ← electron-builder extraResources 대상 +// +// 주의: +// - `--collect-all faster_whisper` 가 필수다. faster-whisper는 VAD용 +// `assets/silero_vad_v6.onnx` 데이터 파일을 패키지 안에 두는데, 이걸 누락하면 +// 번들된 앱에서 VAD 사용 시 런타임에 실패한다. +// - 콘솔 모드를 유지한다(--noconsole 금지). 메인 프로세스가 stdout/stderr를 로그로 +// 수집하는데, windowed 모드에서는 스트림이 사라져 진단이 불가능해진다. +// 콘솔 창 깜빡임은 메인 프로세스 spawn의 windowsHide로 막는다. + +import { spawnSync } from 'node:child_process' +import { existsSync, rmSync, statSync } from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const scriptDir = path.dirname(fileURLToPath(import.meta.url)) +const desktopDir = path.resolve(scriptDir, '..') +const sidecarDir = path.join(desktopDir, 'sidecar') +const mainPy = path.join(sidecarDir, 'main.py') +const outputDir = path.join(desktopDir, 'sidecar-dist') +const isWindows = process.platform === 'win32' +const exeSuffix = isWindows ? '.exe' : '' + +const venvPython = isWindows + ? path.join(sidecarDir, '.venv', 'Scripts', 'python.exe') + : path.join(sidecarDir, '.venv', 'bin', 'python3') + +const python = existsSync(venvPython) + ? venvPython + : isWindows + ? 'python' + : 'python3' + +if (!existsSync(mainPy)) { + console.error(`사이드카 소스를 찾을 수 없습니다: ${mainPy}`) + process.exit(1) +} + +const probe = spawnSync( + python, + ['-c', 'import PyInstaller, faster_whisper; print("ok")'], + { encoding: 'utf-8' }, +) +if (probe.status !== 0) { + console.error( + [ + `PyInstaller/faster-whisper를 사용할 수 없습니다 (python: ${python}).`, + '먼저 사이드카 환경을 준비하세요:', + ' npm --prefix apps/desktop run sidecar:setup', + probe.stderr?.trim() || '', + ].join('\n'), + ) + process.exit(1) +} + +if (existsSync(outputDir)) { + rmSync(outputDir, { recursive: true, force: true }) +} + +const args = [ + '-m', + 'PyInstaller', + '--name', + 'sidecar', + '--distpath', + outputDir, + '--workpath', + path.join(desktopDir, 'build', 'sidecar-build'), + '--specpath', + path.join(desktopDir, 'build'), + '--noconfirm', + '--clean', + // 패키지 데이터/바이너리 포함 (VAD onnx, ctranslate2 DLL 등) + '--collect-all', + 'faster_whisper', + '--collect-all', + 'ctranslate2', + '--collect-all', + 'tokenizers', + '--collect-all', + 'huggingface_hub', + // uvicorn은 동적 임포트를 사용하므로 명시 + '--hidden-import', + 'uvicorn.logging', + '--hidden-import', + 'uvicorn.protocols.http', + '--hidden-import', + 'uvicorn.protocols.http.auto', + '--hidden-import', + 'uvicorn.protocols.http.h11_impl', + '--hidden-import', + 'uvicorn.protocols.websockets', + '--hidden-import', + 'uvicorn.protocols.websockets.auto', + '--hidden-import', + 'uvicorn.lifespan', + '--hidden-import', + 'uvicorn.lifespan.on', + '--hidden-import', + 'uvicorn.lifespan.off', + mainPy, +] + +console.log('='.repeat(64)) +console.log(`D3RO-VOICE 사이드카 빌드 (${process.platform} ${process.arch})`) +console.log('='.repeat(64)) +console.log(`\n$ ${python} ${args.join(' ')}\n`) + +const result = spawnSync(python, args, { stdio: 'inherit', cwd: sidecarDir }) +if (result.status !== 0) { + console.error(`\nPyInstaller 빌드 실패 (exit ${result.status ?? 'null'})`) + process.exit(result.status ?? 1) +} + +const exePath = path.join(outputDir, 'sidecar', `sidecar${exeSuffix}`) +if (!existsSync(exePath)) { + console.error(`\n빌드 산출물을 찾을 수 없습니다: ${exePath}`) + process.exit(1) +} + +const exeMb = statSync(exePath).size / (1024 * 1024) +console.log(`\n빌드 성공: ${exePath} (${exeMb.toFixed(1)} MB)`) +console.log('electron-builder가 sidecar-dist/sidecar/ 를 resources/sidecar/ 로 복사한다.') diff --git a/apps/desktop/scripts/setup-sidecar.mjs b/apps/desktop/scripts/setup-sidecar.mjs new file mode 100644 index 0000000..96edc0a --- /dev/null +++ b/apps/desktop/scripts/setup-sidecar.mjs @@ -0,0 +1,103 @@ +// scripts/setup-sidecar.mjs +// STT 사이드카 개발/빌드 환경을 준비한다. +// +// - sidecar/.venv 가 없으면 생성한다. +// - requirements.txt + pyinstaller 를 설치한다. +// +// 사용: npm --prefix apps/desktop run sidecar:setup +// +// 주의: faster-whisper/ctranslate2 휠은 수백 MB이며 최초 실행 시 네트워크가 필요하다. + +import { spawnSync } from 'node:child_process' +import { existsSync, mkdirSync } from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const scriptDir = path.dirname(fileURLToPath(import.meta.url)) +const desktopDir = path.resolve(scriptDir, '..') +const sidecarDir = path.join(desktopDir, 'sidecar') +const isWindows = process.platform === 'win32' +const venvPython = isWindows + ? path.join(sidecarDir, '.venv', 'Scripts', 'python.exe') + : path.join(sidecarDir, '.venv', 'bin', 'python3') + +function run(command, args, label) { + console.log(`\n$ ${command} ${args.join(' ')}`) + const result = spawnSync(command, args, { stdio: 'inherit', cwd: sidecarDir }) + if (result.status !== 0) { + console.error(`\n${label} 단계가 실패했습니다 (exit ${result.status ?? 'null'})`) + process.exit(result.status ?? 1) + } +} + +function findSystemPython() { + const candidates = isWindows + ? [ + ['py', ['-3.11']], + ['py', ['-3']], + ['python', []], + ['python3', []], + ] + : [ + ['python3.11', []], + ['python3', []], + ['python', []], + ] + + for (const [command, prefixArgs] of candidates) { + const probe = spawnSync(command, [...prefixArgs, '--version'], { + encoding: 'utf-8', + shell: isWindows, + }) + if (probe.status === 0) { + console.log(`시스템 Python 발견: ${command} ${prefixArgs.join(' ')} → ${probe.stdout.trim()}`) + return { command, prefixArgs, shell: isWindows } + } + } + + console.error( + 'Python 3.11+ 를 찾을 수 없습니다. https://www.python.org/downloads/ 에서 설치하거나 PATH에 추가하세요.', + ) + process.exit(1) +} + +function hasModule(python, moduleName) { + return ( + spawnSync(python, ['-c', `import ${moduleName}`], { stdio: 'ignore' }).status === 0 + ) +} + +if (!existsSync(sidecarDir)) { + console.error(`사이드카 디렉토리를 찾을 수 없습니다: ${sidecarDir}`) + process.exit(1) +} + +if (existsSync(venvPython) && hasModule(venvPython, 'faster_whisper')) { + // CI/재실행 시 불필요한 재설치를 건너뛴다 (수백 MB 다운로드 방지). + console.log(`사이드카 환경이 이미 준비되어 있습니다: ${venvPython}`) + if (!hasModule(venvPython, 'PyInstaller')) { + run(venvPython, ['-m', 'pip', 'install', 'pyinstaller>=6.0'], 'PyInstaller 설치') + } +} else { + if (!existsSync(venvPython)) { + const systemPython = findSystemPython() + mkdirSync(path.dirname(venvPython), { recursive: true }) + run( + systemPython.command, + [...systemPython.prefixArgs, '-m', 'venv', path.join(sidecarDir, '.venv')], + '가상환경 생성', + ) + } else { + console.log(`기존 가상환경 사용: ${venvPython}`) + } + + run(venvPython, ['-m', 'pip', 'install', '--upgrade', 'pip'], 'pip 업그레이드') + run( + venvPython, + ['-m', 'pip', 'install', '-r', path.join(sidecarDir, 'requirements.txt')], + '사이드카 의존성 설치', + ) + run(venvPython, ['-m', 'pip', 'install', 'pyinstaller>=6.0'], 'PyInstaller 설치') +} + +console.log('\n사이드카 환경 준비 완료.') diff --git a/apps/desktop/sidecar/main.py b/apps/desktop/sidecar/main.py index 3c3a3ab..5504939 100644 --- a/apps/desktop/sidecar/main.py +++ b/apps/desktop/sidecar/main.py @@ -39,6 +39,14 @@ from fastapi.responses import JSONResponse # ── 로깅 설정 ────────────────────────────────────────────── +# Windows에서 파이프로 연결되면 Python이 로케일(cp949) 인코딩으로 출력해 +# 메인 프로세스의 UTF-8 로그가 깨진다. 명시적으로 UTF-8로 고정한다. +for _stream in (sys.stdout, sys.stderr): + try: + _stream.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr] + except (AttributeError, ValueError): + pass + logging.basicConfig( level=logging.INFO, format="[%(asctime)s] [%(levelname)s] [%(name)s] %(message)s", @@ -121,6 +129,11 @@ def _detect_gpu() -> None: logger.info("GPU 감지 실패, CPU 모드로 동작: %s", exc) +def _cpu_threads() -> int: + """CPU 추론에 사용할 스레드 수 (과도한 점유 방지 위해 8로 상한).""" + return max(1, min(8, os.cpu_count() or 4)) + + # ── 모델 다운로드 헬퍼 ───────────────────────────────────── @@ -250,6 +263,52 @@ def _cleanup_partial(model_id: str) -> None: pass +def _build_transcribe_kwargs( + language: str, + vad_filter: str, + initial_prompt: str, + is_partial: bool, +) -> dict: + """전사 옵션을 만든다. + + 받아쓰기 정합성을 위해 컨텍스트 누적(condition_on_previous_text)을 끈다. + Whisper가 앞 세그먼트 오류를 반복 증폭하는 현상(환각 루프)을 막는다. + 미리보기(partial)는 지연이 목표이므로 greedy + VAD 없음으로 디코딩한다. + """ + if is_partial: + kwargs: dict = { + "beam_size": 1, + "temperature": 0.0, + "vad_filter": False, + "condition_on_previous_text": False, + "word_timestamps": False, + } + else: + kwargs = { + "beam_size": 5, + # 0.0 단일 온도는 실패 시 재시도가 없어 환각이 남는다. + # 낮은 온도 폴백만 허용하되 컨텍스트를 끊어 반복을 차단한다. + "temperature": [0.0, 0.2, 0.4], + "condition_on_previous_text": False, + "no_speech_threshold": 0.6, + "compression_ratio_threshold": 2.4, + "log_prob_threshold": -1.0, + "vad_filter": vad_filter.lower() == "true", + "word_timestamps": False, + } + if kwargs["vad_filter"]: + # 무음 구간을 촘촘히 잘라 속도를 올린다. + kwargs["vad_parameters"] = {"min_silence_duration_ms": 300} + + if language != "auto": + kwargs["language"] = language + + if initial_prompt and not is_partial: + kwargs["initial_prompt"] = initial_prompt + + return kwargs + + # ── 엔드포인트 ───────────────────────────────────────────── @@ -260,7 +319,9 @@ async def health() -> JSONResponse: content={ "status": "ready" if _model is not None else "no_model", "model": _model_id, + "model_loaded": _model is not None, "gpu": _gpu_available, + "device": "cuda" if _gpu_available else "cpu", } ) @@ -282,6 +343,18 @@ async def load_model(body: dict) -> JSONResponse: # noqa: ANN001 start_time = time.monotonic() + # 같은 모델이 이미 로딩되어 있으면 재사용 (재로딩은 수초 지연을 만든다) + if _model is not None and _model_id == model_id: + logger.info("이미 로딩된 모델 재사용: %s", model_id) + return JSONResponse( + content={ + "status": "loaded", + "model_id": model_id, + "load_time_ms": 0, + "reused": True, + } + ) + try: from faster_whisper import WhisperModel @@ -295,10 +368,15 @@ async def load_model(body: dict) -> JSONResponse: # noqa: ANN001 if local_dir: logger.info("로컬 모델 디렉토리 사용: %s", local_dir) + # 모델 교체 시 이전 모델을 먼저 해제해 VRAM/RAM을 회수한다. + _model = None + _model = WhisperModel( model_source, device=device, compute_type=compute_type, + cpu_threads=_cpu_threads(), + num_workers=1, ) _model_id = model_id @@ -333,14 +411,16 @@ async def transcribe( language: str = Form("auto"), vad_filter: str = Form("true"), initial_prompt: str = Form(""), + partial: str = Form("false"), ) -> JSONResponse: """오디오 파일을 전사한다. Multipart form: - audio - PCM16 16kHz mono 바이너리 파일 - language - 언어 코드 ('auto', 'ko', 'en', ...) - vad_filter - VAD 필터 활성화 ('true' / 'false') + audio - PCM16 16kHz mono 바이너리 파일 + language - 언어 코드 ('auto', 'ko', 'en', ...) + vad_filter - VAD 필터 활성화 ('true' / 'false') initial_prompt - 초기 프롬프트 (컨텍스트 힌트) + partial - 녹음 중 미리보기 모드 ('true'면 greedy 디코딩 + 컨텍스트 미사용) """ if _model is None: return JSONResponse( @@ -348,6 +428,7 @@ async def transcribe( content={"status": "error", "message": "모델이 로딩되지 않았습니다"}, ) + is_partial = partial.lower() == "true" start_time = time.monotonic() try: @@ -367,22 +448,19 @@ async def transcribe( audio_duration = len(audio_array) / sample_rate logger.info( - "전사 시작: %.1f초 오디오, language=%s, vad=%s", + "전사 시작: %.1f초 오디오, language=%s, vad=%s, partial=%s", audio_duration, language, vad_filter, + is_partial, ) - transcribe_kwargs: dict = { - "vad_filter": vad_filter.lower() == "true", - "beam_size": 5, - } - - if language != "auto": - transcribe_kwargs["language"] = language - - if initial_prompt: - transcribe_kwargs["initial_prompt"] = initial_prompt + transcribe_kwargs = _build_transcribe_kwargs( + language=language, + vad_filter=vad_filter, + initial_prompt=initial_prompt, + is_partial=is_partial, + ) # VAD가 전체 오디오를 제거하면 max() 에러 발생 → VAD 없이 재시도 try: diff --git a/apps/desktop/src/main/bootstrap.ts b/apps/desktop/src/main/bootstrap.ts index 587eb38..58956d2 100644 --- a/apps/desktop/src/main/bootstrap.ts +++ b/apps/desktop/src/main/bootstrap.ts @@ -63,6 +63,7 @@ export async function bootstrap(): Promise { { name: 'popup-preload', critical: false, fn: initPopupWindows }, { name: 'hotkey', critical: false, fn: initHotkey }, { name: 'voice-mode', critical: false, fn: initVoiceMode }, + { name: 'stt-warmup', critical: false, fn: initSTTWarmup }, { name: 'llm-polling', critical: false, fn: initLLMPolling }, { name: 'meeting-summary-wiring', critical: false, fn: initMeetingSummaryWiring }, { name: 'meeting-mode', critical: false, fn: initMeetingMode }, @@ -270,6 +271,20 @@ async function initLLMPolling(): Promise { await startLocalLLMAvailability() } +/** + * 로컬 STT(sidecar + Whisper 모델)를 앱 시작 시 백그라운드로 미리 데운다. + * 첫 받아쓰기에서 모델 로딩(수초)을 기다리는 체감 지연을 없앤다. + * bootstrap을 막지 않도록 await하지 않는다 — 실패는 warmUpLocal이 흡수한다. + */ +async function initSTTWarmup(): Promise { + try { + const { getSTTManager } = await import('./services/stt/STTManager') + void getSTTManager().warmUpLocal() + } catch (err) { + logger.warn('STT warmup scheduling failed:', err) + } +} + async function initCloudSync(): Promise { const { getCloudSyncService } = await import('./services/CloudSyncService') const sync = getCloudSyncService() diff --git a/apps/desktop/src/main/ipc/llm-handlers.ts b/apps/desktop/src/main/ipc/llm-handlers.ts index 4e1bf03..1e29f78 100644 --- a/apps/desktop/src/main/ipc/llm-handlers.ts +++ b/apps/desktop/src/main/ipc/llm-handlers.ts @@ -7,6 +7,7 @@ import { getLocalLLMService } from '../services/LocalLLMService' import { getPremiumLLMService } from '../services/PremiumLLMService' import { getOnlineLLMService } from '../services/OnlineLLMService' import { configGet, configSet } from '../services/ConfigService' +import { normalizeLoopbackUrl } from '../utils/loopback' import { getMainWindow } from '../windows/WindowManager' import type { SetLLMModelParams, SetServerUrlParams, LLMProcessParams } from '@d3ro/core/types' @@ -129,7 +130,7 @@ export function registerLLMHandlers(): void { // ONLINE AUTH HANDLERS ipcMain.handle(IPC_CHANNELS.ONLINE_AUTH.REGISTER, async (_event, params: { email: string; password: string }) => { - const apiUrl = configGet('onlineApiUrl') ?? 'http://localhost:5000' + const apiUrl = normalizeLoopbackUrl(configGet('onlineApiUrl') ?? 'http://127.0.0.1:5000') try { const res = await fetch(`${apiUrl}/api/auth/register`, { method: 'POST', @@ -151,7 +152,7 @@ export function registerLLMHandlers(): void { }) ipcMain.handle(IPC_CHANNELS.ONLINE_AUTH.LOGIN, async (_event, params: { email: string; password: string }) => { - const apiUrl = configGet('onlineApiUrl') ?? 'http://localhost:5000' + const apiUrl = normalizeLoopbackUrl(configGet('onlineApiUrl') ?? 'http://127.0.0.1:5000') try { const res = await fetch(`${apiUrl}/api/auth/login`, { method: 'POST', diff --git a/apps/desktop/src/main/services/AudioCaptureService.ts b/apps/desktop/src/main/services/AudioCaptureService.ts index 5cb3005..5a10c7e 100644 --- a/apps/desktop/src/main/services/AudioCaptureService.ts +++ b/apps/desktop/src/main/services/AudioCaptureService.ts @@ -135,7 +135,7 @@ class AudioCaptureService extends EventEmitter { ] logger.info(`SoX args: ${soxArgs.join(' ')}`) - this._soxProcess = spawn(soxExe, soxArgs, { stdio: ['pipe', 'pipe', 'pipe'] }) + this._soxProcess = spawn(soxExe, soxArgs, { stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true }) this._stream = this._soxProcess.stdout this._residualBuffer = Buffer.alloc(0) this._levelAccumulator = [] @@ -168,8 +168,12 @@ class AudioCaptureService extends EventEmitter { this._soxProcess.on('error', (err: Error) => { logger.error(`SoX process spawn error: ${err.message}`) + const hint = + soxExe === 'sox' && (err as NodeJS.ErrnoException).code === 'ENOENT' + ? ' 번들된 SoX(resources/sox/sox.exe)도, 시스템 PATH의 sox도 없습니다. `npm --prefix apps/desktop run setup:sox`로 내려받으세요.' + : '' this._handleError( - new D3ROError(ErrorCode.AudioCaptureStartFailed, `SoX 프로세스 시작 실패: ${err.message}`), + new D3ROError(ErrorCode.AudioCaptureStartFailed, `SoX 프로세스 시작 실패: ${err.message}.${hint}`), 'error' ) }) @@ -392,7 +396,7 @@ class AudioCaptureService extends EventEmitter { '-b', '16', '-e', 'signed-integer', '-t', 'raw', '-'] return new Promise((resolve, reject) => { - const proc = spawn(soxExe, soxArgs, { stdio: ['ignore', 'pipe', 'pipe'] }) + const proc = spawn(soxExe, soxArgs, { stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true }) const buffers: Buffer[] = [] let peakRms = 0 diff --git a/apps/desktop/src/main/services/ConfigService.ts b/apps/desktop/src/main/services/ConfigService.ts index f6079b6..2f0b2e9 100644 --- a/apps/desktop/src/main/services/ConfigService.ts +++ b/apps/desktop/src/main/services/ConfigService.ts @@ -26,13 +26,13 @@ const CONFIG_DEFAULTS: AppConfig = { sttProvider: 'local' as const, sttProviderConfigs: { local: { modelId: 'large-v3-turbo' }, - 'd3ro-cloud': { modelId: 'default', apiKey: '', baseUrl: 'http://localhost:5000' }, + 'd3ro-cloud': { modelId: 'default', apiKey: '', baseUrl: 'http://127.0.0.1:5000' }, openai: { modelId: 'whisper-1', apiKey: '', baseUrl: 'https://api.openai.com/v1' }, groq: { modelId: 'whisper-large-v3-turbo', apiKey: '', baseUrl: 'https://api.groq.com/openai/v1' }, deepgram: { modelId: 'nova-3', apiKey: '', baseUrl: 'https://api.deepgram.com' }, assemblyai: { modelId: 'best', apiKey: '', baseUrl: 'https://api.assemblyai.com/v2' }, google: { modelId: 'gemini-2.0-flash', apiKey: '', baseUrl: 'https://generativelanguage.googleapis.com/v1beta' }, - custom: { modelId: 'whisper-1', apiKey: '', baseUrl: 'http://localhost:8000/v1' }, + custom: { modelId: 'whisper-1', apiKey: '', baseUrl: 'http://127.0.0.1:8000/v1' }, }, sttFallbackToLocal: true, // large-v3 대비 6배 빠르고 정확도 손실 1~2%, 다운로드 1.6GB (온보딩에서 사전 다운로드) @@ -40,10 +40,10 @@ const CONFIG_DEFAULTS: AppConfig = { sttLanguage: 'auto', ttsVoiceId: null, ttsSpeed: 1.0, - onlineApiUrl: 'http://localhost:5000', + onlineApiUrl: 'http://127.0.0.1:5000', localModelsDir: '', llmModelId: 'gemma-2-2b-it.Q4_K_M.gguf', - ollamaServerUrl: 'http://localhost:11434', + ollamaServerUrl: 'http://127.0.0.1:11434', appUsageMode: null, authToken: null, userEmail: null, diff --git a/apps/desktop/src/main/services/LocalLLMService.ts b/apps/desktop/src/main/services/LocalLLMService.ts index 94cdc4f..003f31e 100644 --- a/apps/desktop/src/main/services/LocalLLMService.ts +++ b/apps/desktop/src/main/services/LocalLLMService.ts @@ -12,9 +12,15 @@ import { D3ROError, ErrorCode } from '@d3ro/core/errors' import type { LLMStatus, LLMModel, LLMAction, LLMConnectionState } from '@d3ro/core/types' import { resolveSystemPrompt } from './llm-prompts' import { getBundledOllamaPath } from '../utils/paths' +import { normalizeLoopbackUrl } from '../utils/loopback' const logger = getLogger('LocalLLMService') +/** Ollama 서버 URL — localhost는 ::1로 해석되어 실패하므로 IPv4 루프백으로 정규화한다. */ +export function getOllamaServerUrl(): string { + return normalizeLoopbackUrl(configGet('ollamaServerUrl') || 'http://127.0.0.1:11434') +} + // ============================================================ // 내부 타입 // ============================================================ @@ -172,7 +178,7 @@ class LocalLLMService extends EventEmitter { * Ollama /api/version 또는 /api/tags 엔드포인트로 가용성 핑. 지정 타임아웃 내 응답이 오면 true. */ private async _ping(timeoutMs: number): Promise { - const serverUrl = configGet('ollamaServerUrl') || 'http://localhost:11434' + const serverUrl = getOllamaServerUrl() try { const response = await fetch(`${serverUrl}/api/version`, { signal: AbortSignal.timeout(timeoutMs) @@ -325,7 +331,7 @@ class LocalLLMService extends EventEmitter { throw new D3ROError(ErrorCode.LLMServerUnreachable, 'Ollama 서버에 연결할 수 없습니다') } - const serverUrl = configGet('ollamaServerUrl') + const serverUrl = getOllamaServerUrl() const model = options?.model ?? configGet('llmModelId') ?? 'gemma4:e4b' this._state = LLMState.Generating @@ -392,7 +398,7 @@ class LocalLLMService extends EventEmitter { throw new D3ROError(ErrorCode.LLMServerUnreachable, 'Ollama 서버에 연결할 수 없습니다') } - const serverUrl = configGet('ollamaServerUrl') + const serverUrl = getOllamaServerUrl() const model = options?.model ?? configGet('llmModelId') ?? 'gemma4:e4b' this._state = LLMState.Generating @@ -536,7 +542,7 @@ class LocalLLMService extends EventEmitter { * Ollama에 설치된 모델 목록을 조회한다. */ async getModels(): Promise { - const serverUrl = configGet('ollamaServerUrl') + const serverUrl = getOllamaServerUrl() try { const response = await fetch(`${serverUrl}/api/tags`, { @@ -582,7 +588,7 @@ class LocalLLMService extends EventEmitter { } private async _doPullModel(modelId: string): Promise { - const serverUrl = configGet('ollamaServerUrl') + const serverUrl = getOllamaServerUrl() logger.info(`Pull 시작: ${modelId}`) const response = await fetch(`${serverUrl}/api/pull`, { @@ -656,7 +662,7 @@ class LocalLLMService extends EventEmitter { return { connectionState, - serverUrl: configGet('ollamaServerUrl') || 'http://localhost:11434', + serverUrl: getOllamaServerUrl(), activeModel: configGet('llmModelId') || 'gemma2:2b', serverVersion: this._serverVersion } @@ -679,7 +685,7 @@ class LocalLLMService extends EventEmitter { throw new D3ROError(ErrorCode.LLMServerUnreachable, 'Ollama server not available') } - const serverUrl = configGet('ollamaServerUrl') || 'http://localhost:11434' + const serverUrl = getOllamaServerUrl() const model = options?.model ?? configGet('llmModelId') ?? 'gemma2:2b' this._abortController = new AbortController() @@ -754,7 +760,7 @@ class LocalLLMService extends EventEmitter { private async _checkAvailability(): Promise { if (this._disposed) return - const serverUrl = configGet('ollamaServerUrl') || 'http://localhost:11434' + const serverUrl = getOllamaServerUrl() let isOk = false let detectedVersion: string | null = null diff --git a/apps/desktop/src/main/services/LocalSTTService.ts b/apps/desktop/src/main/services/LocalSTTService.ts index 189ec3f..a52227f 100644 --- a/apps/desktop/src/main/services/LocalSTTService.ts +++ b/apps/desktop/src/main/services/LocalSTTService.ts @@ -10,7 +10,7 @@ import { existsSync } from 'fs' import { join } from 'path' import { getLogger } from './LoggerService' import { configGet } from './ConfigService' -import { getSidecarCommand, getWhisperModelsDir } from '../utils/paths' +import { getSidecarCommand, getSidecarBaseUrl, getWhisperModelsDir } from '../utils/paths' import { D3ROError, ErrorCode } from '@d3ro/core/errors' import type { STTModel, @@ -52,6 +52,8 @@ export interface TranscribeOptions { language?: string initialPrompt?: string vadFilter?: boolean + /** 음 중 실시간 미리보기 요청 — 상태/이벤트를 건드리지 않고 greedy 디코딩을 사용한다. */ + partial?: boolean } /** sidecar /health 응답 */ @@ -109,6 +111,8 @@ const HEALTH_CHECK_INTERVAL_MS = 1000 const HEALTH_CHECK_TIMEOUT_MS = 30000 const MAX_RESTART_COUNT = 3 const SIDECAR_REQUEST_TIMEOUT_MS = 120000 +/** 부분 전사(미리보기) 타임아웃 — 실패해도 무시되므로 짧게 잡는다 */ +const SIDECAR_PARTIAL_TIMEOUT_MS = 15000 /** 알려진 Whisper 모델 카탈로그 */ const MODEL_CATALOG: STTModel[] = [ @@ -207,6 +211,11 @@ class LocalSTTService extends EventEmitter { return this._currentModelId } + /** sidecar HTTP 기본 URL — IPv4 루프백 고정 (localhost는 ::1로 해석되어 실패) */ + private get _baseUrl(): string { + return getSidecarBaseUrl(this._port) + } + // ── 공개 메서드 ── /** @@ -300,6 +309,65 @@ class LocalSTTService extends EventEmitter { return this._sendToSidecar(audioBuffer, options) } + /** + * 앱 시작 시 sidecar와 모델을 미리 데운다. + * 첫 받아쓰기에서 모델 로딩(수초)을 기다리지 않게 하는 것이 목적이므로 + * 실패는 조용히 경고로만 남기고 예외를 던지지 않는다. + */ + async warmUp(): Promise { + if (this._disposed) return false + if (this._state === STTState.Ready && this._currentModelId) return true + + const modelId = configGet('sttModelId') + if (!modelId) { + logger.info('STT 워밍업 생략: 모델이 선택되지 않았습니다') + return false + } + + if (!existsSync(join(getWhisperModelsDir(), modelId, 'model.bin'))) { + logger.info(`STT 워밍업 생략: 모델 미설치 (${modelId})`) + return false + } + + try { + await this.initialize(modelId) + return true + } catch (err) { + logger.warn( + `STT 워밍업 실패: ${err instanceof Error ? err.message : String(err)}`, + ) + return false + } + } + + /** + * 녹음 중 실시간 미리보기 전사. + * 최종 결과와 분리되어 삽입되지 않으며, 실패해도 빈 문자열을 반환한다. + * 지연 최소화를 위해 상태/이벤트를 건드리지 않는다. + */ + async transcribePartial( + audioBuffer: Buffer, + options?: TranscribeOptions, + ): Promise { + if (this._disposed) return '' + if (!this._modelReady) return '' + if (audioBuffer.length === 0) return '' + if (!this._sidecarProcess || this._sidecarProcess.exitCode !== null) return '' + + try { + const result = await this._sendToSidecar(audioBuffer, { + ...options, + partial: true, + }) + return result.text + } catch (err) { + logger.debug( + `부분 전사 실패(무시): ${err instanceof Error ? err.message : String(err)}`, + ) + return '' + } + } + /** * 다운로드된 모델 목록 조회. * models-dir 사전 다운로드 여부 + 현재 로딩 여부로 downloaded를 판정한다. @@ -328,7 +396,7 @@ class LocalSTTService extends EventEmitter { await this._ensureSidecarRunning() - const startRes = await fetch(`http://localhost:${this._port}/download`, { + const startRes = await fetch(`${this._baseUrl}/download`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model_id: modelId }), @@ -361,7 +429,7 @@ class LocalSTTService extends EventEmitter { let status: DownloadStatusResponse try { - const res = await fetch(`http://localhost:${this._port}/download/status`, { + const res = await fetch(`${this._baseUrl}/download/status`, { signal: AbortSignal.timeout(3000), }) if (!res.ok) throw new Error(`HTTP ${res.status}`) @@ -408,7 +476,7 @@ class LocalSTTService extends EventEmitter { /** 진행 중인 모델 다운로드 취소 요청 */ async cancelDownload(): Promise { try { - await fetch(`http://localhost:${this._port}/download/cancel`, { + await fetch(`${this._baseUrl}/download/cancel`, { method: 'POST', signal: AbortSignal.timeout(3000), }) @@ -570,77 +638,120 @@ class LocalSTTService extends EventEmitter { // dev mode HMR/재시작으로 이전 sidecar가 orphan으로 남아있을 수 있음. this._port = await this._findFreePort(SIDECAR_PORT, 20) - const { command, args } = getSidecarCommand() + // 번들/venv 경로가 깨졌으면 여기서 즉시 실패한다 (조용한 PATH 폴백 금지). + const launch = getSidecarCommand() const fullArgs = [ - ...args, + ...launch.args, '--port', String(this._port), '--models-dir', getWhisperModelsDir(), ] - logger.info(`Sidecar 시작: ${command} ${fullArgs.join(' ')}`) + logger.info( + `Sidecar 시작(${launch.source}): ${launch.command} ${fullArgs.join(' ')}`, + ) return new Promise((resolve, reject) => { - try { - this._sidecarProcess = spawn( - command, - fullArgs, - { - stdio: ['pipe', 'pipe', 'pipe'], - env: { ...process.env }, - }, - ) - } catch (err) { - const d3roErr = new D3ROError( - ErrorCode.STTSidecarSpawnFailed, - `Sidecar 프로세스 생성 실패: ${err instanceof Error ? err.message : String(err)}`, - ) - reject(d3roErr) - return - } + let settled = false + const child = spawn(launch.command, fullArgs, { + stdio: ['pipe', 'pipe', 'pipe'], + env: { + ...process.env, + // 사이드카 로그와 파일 경로가 UTF-8로 오가도록 고정 (Windows cp949 깨짐 방지) + PYTHONIOENCODING: 'utf-8', + PYTHONUTF8: '1', + }, + // Windows에서 콘솔 창이 깜빡이지 않게 한다. + windowsHide: true, + }) + this._sidecarProcess = child - const sidecarLogger = getLogger('sidecar') + this._pipeSidecarLogs(child, getLogger('sidecar')) - this._sidecarProcess.stdout?.on('data', (data: Buffer) => { - const text = data.toString().trim() - if (text) { - sidecarLogger.info(text) - } + // spawn 성공 = 프로세스가 실제로 시작됨. 즉시 resolve해 healthcheck로 넘어간다. + child.once('spawn', () => { + if (settled) return + settled = true + resolve() }) - this._sidecarProcess.stderr?.on('data', (data: Buffer) => { - const text = data.toString().trim() - if (text) { - sidecarLogger.warn(text) - } - }) - - this._sidecarProcess.on('error', (err: Error) => { + // spawn 실패(ENOENT 등)는 즉시 실패시킨다. 예전엔 즉시 resolve 후 + // healthcheck 30초를 헛되게 태우고 원인을 숨겼다. + child.once('error', (err: Error) => { logger.error(`Sidecar 프로세스 에러: ${err.message}`) - reject( - new D3ROError( - ErrorCode.STTSidecarSpawnFailed, - `Sidecar 프로세스 에러: ${err.message}`, - ), - ) + this._sidecarProcess = null + if (settled) return + settled = true + reject(this._spawnFailureError(err, launch)) }) - this._sidecarProcess.on('exit', (code: number | null, signal: string | null) => { + child.on('exit', (code: number | null, signal: string | null) => { logger.warn(`Sidecar 프로세스 종료: code=${code}, signal=${signal}`) - this._sidecarProcess = null + if (this._sidecarProcess === child) { + this._sidecarProcess = null + } this._modelReady = false if (!this._disposed) { this._handleSidecarCrash() } }) - - // spawn 자체는 비동기적이므로 즉시 resolve - // 실제 준비는 _waitForHealth에서 확인 - resolve() }) } + /** sidecar stdout/stderr를 줄 단위로 로그에 흘려보낸다. */ + private _pipeSidecarLogs( + child: ChildProcess, + sidecarLogger: ReturnType, + ): void { + const consume = ( + stream: NodeJS.ReadableStream | null | undefined, + write: (message: string) => void, + ): void => { + if (!stream) return + let pending = '' + stream.on('data', (chunk: Buffer) => { + pending += chunk.toString('utf8') + const lines = pending.split(/\r?\n/) + // 마지막 조각은 줄이 완성되지 않았을 수 있으니 다음 청크와 합친다. + pending = lines.pop() ?? '' + for (const line of lines) { + const trimmed = line.trim() + if (trimmed) write(trimmed) + } + }) + } + + consume(child.stdout, (message) => sidecarLogger.info(message)) + consume(child.stderr, (message) => sidecarLogger.warn(message)) + } + + /** sidecar 기동 실패 원인을 사용자가 조치할 수 있는 문구로 바꾼다. */ + private _spawnFailureError( + err: Error, + launch: { command: string; source: 'bundled' | 'venv' | 'python' }, + ): D3ROError { + const enoent = (err as NodeJS.ErrnoException).code === 'ENOENT' + if (!enoent) { + return new D3ROError( + ErrorCode.STTSidecarSpawnFailed, + `Sidecar 프로세스 에러: ${err.message}`, + ) + } + + const hint = + launch.source === 'bundled' + ? '번들된 사이드카 실행 파일이 손상되었거나 백신이 차단했습니다. 앱을 다시 설치하세요.' + : launch.source === 'venv' + ? '사이드카 가상환경이 손상되었습니다. `npm --prefix apps/desktop run sidecar:setup`을 실행하세요.' + : '시스템 Python을 찾을 수 없습니다. `npm --prefix apps/desktop run sidecar:setup`으로 가상환경을 만드세요.' + + return new D3ROError( + ErrorCode.STTSidecarSpawnFailed, + `Sidecar 실행 파일을 찾을 수 없습니다: ${launch.command} (${launch.source}). ${hint}`, + ) + } + private async _waitForHealth(): Promise { const startTime = Date.now() @@ -655,7 +766,7 @@ class LocalSTTService extends EventEmitter { } try { - const response = await fetch(`http://localhost:${this._port}/health`, { + const response = await fetch(`${this._baseUrl}/health`, { signal: AbortSignal.timeout(2000), }) @@ -685,7 +796,7 @@ class LocalSTTService extends EventEmitter { logger.info(`모델 로딩 시작: ${modelId}`) const startTime = Date.now() - const response = await fetch(`http://localhost:${this._port}/load`, { + const response = await fetch(`${this._baseUrl}/load`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model_id: modelId }), @@ -729,12 +840,16 @@ class LocalSTTService extends EventEmitter { ) } - this._setState(STTState.Transcribing) + const isPartial = options?.partial === true + + if (!isPartial) { + this._setState(STTState.Transcribing) + } const startTime = Date.now() try { const language = options?.language ?? configGet('sttLanguage') - const vadFilter = options?.vadFilter ?? true + const vadFilter = options?.vadFilter ?? !isPartial const initialPrompt = options?.initialPrompt ?? '' // Node 18+ 내장 fetch + FormData + Blob으로 multipart 전송 @@ -751,18 +866,19 @@ class LocalSTTService extends EventEmitter { ) formData.append('language', language) formData.append('vad_filter', String(vadFilter)) + // 부분 전사는 greedy 디코딩 + 컨텍스트 미사용으로 지연을 최소화한다. + formData.append('partial', String(isPartial)) if (initialPrompt) { formData.append('initial_prompt', initialPrompt) } - const response = await fetch( - `http://localhost:${this._port}/transcribe`, - { - method: 'POST', - body: formData, - signal: AbortSignal.timeout(SIDECAR_REQUEST_TIMEOUT_MS), - }, - ) + const response = await fetch(`${this._baseUrl}/transcribe`, { + method: 'POST', + body: formData, + signal: AbortSignal.timeout( + isPartial ? SIDECAR_PARTIAL_TIMEOUT_MS : SIDECAR_REQUEST_TIMEOUT_MS, + ), + }) if (!response.ok) { const errorText = await response.text() @@ -788,6 +904,12 @@ class LocalSTTService extends EventEmitter { processingTime, } + if (isPartial) { + // 미리보기 — 상태/이벤트를 건드리지 않는다 (최종 삽입과 무관). + logger.debug(`부분 전사: "${result.text.substring(0, 40)}" (${processingTime}ms)`) + return result + } + // 중간 결과 이벤트 (isFinal=true) this.emit('transcription-delta', { text: result.text, isFinal: true }) this.emit('transcription-complete', { result }) @@ -800,7 +922,9 @@ class LocalSTTService extends EventEmitter { return result } catch (err) { - this._setState(STTState.Ready) // 에러 후에도 Ready 복귀 (sidecar가 살아있으면) + if (!isPartial) { + this._setState(STTState.Ready) // 에러 후에도 Ready 복귀 (sidecar가 살아있으면) + } if (err instanceof D3ROError) { throw err @@ -874,7 +998,7 @@ class LocalSTTService extends EventEmitter { try { // POST /shutdown 요청 - await fetch(`http://localhost:${this._port}/shutdown`, { + await fetch(`${this._baseUrl}/shutdown`, { method: 'POST', signal: AbortSignal.timeout(3000), }) diff --git a/apps/desktop/src/main/services/OnlineLLMService.ts b/apps/desktop/src/main/services/OnlineLLMService.ts index 556784b..b2d68f8 100644 --- a/apps/desktop/src/main/services/OnlineLLMService.ts +++ b/apps/desktop/src/main/services/OnlineLLMService.ts @@ -4,6 +4,7 @@ import { EventEmitter } from 'events' import { configGet, configSet } from './ConfigService' +import { normalizeLoopbackUrl } from '../utils/loopback' import { D3ROError, ErrorCode } from '@d3ro/core/errors' import type { LLMAction } from '@d3ro/core/types' import { resolveSystemPrompt } from './llm-prompts' @@ -47,7 +48,7 @@ class OnlineLLMService extends EventEmitter { customPrompt?: string ): Promise { const token = this._ensureAuth() - const apiUrl = configGet('onlineApiUrl') ?? 'http://localhost:5000' + const apiUrl = normalizeLoopbackUrl(configGet('onlineApiUrl') ?? 'http://127.0.0.1:5000') const systemPrompt = resolveSystemPrompt(action, targetLanguage, customPrompt) try { @@ -100,7 +101,7 @@ class OnlineLLMService extends EventEmitter { options?: { model?: string; temperature?: number } ): AsyncGenerator { const token = this._ensureAuth() - const apiUrl = configGet('onlineApiUrl') ?? 'http://localhost:5000' + const apiUrl = normalizeLoopbackUrl(configGet('onlineApiUrl') ?? 'http://127.0.0.1:5000') const response = await fetch(`${apiUrl}/api/llm/chat`, { method: 'POST', diff --git a/apps/desktop/src/main/services/RAGService.ts b/apps/desktop/src/main/services/RAGService.ts index 319f91a..41fa603 100644 --- a/apps/desktop/src/main/services/RAGService.ts +++ b/apps/desktop/src/main/services/RAGService.ts @@ -7,7 +7,7 @@ import path from 'path' import { eq } from 'drizzle-orm' import { getLogger } from './LoggerService' import { getPremiumLLMService } from './PremiumLLMService' -import { configGet } from './ConfigService' +import { getOllamaServerUrl } from './LocalLLMService' import { getDatabase } from '../db' import { ragDocuments, ragChunks } from '../db/schema' import { getMainWindow } from '../windows/WindowManager' @@ -307,7 +307,7 @@ ${context}` * Ollama /api/embed 엔드포인트로 텍스트 임베딩 */ private async _embed(text: string): Promise { - const serverUrl = configGet('ollamaServerUrl') + const serverUrl = getOllamaServerUrl() try { const response = await fetch(`${serverUrl}/api/embed`, { diff --git a/apps/desktop/src/main/services/VoiceModeService.ts b/apps/desktop/src/main/services/VoiceModeService.ts index 212710b..c8e8b72 100644 --- a/apps/desktop/src/main/services/VoiceModeService.ts +++ b/apps/desktop/src/main/services/VoiceModeService.ts @@ -27,6 +27,7 @@ import { hideRecordingTip, updateRecordingTipState, sendAudioLevelToTip, + sendPartialTranscriptToTip, showResultPopup, } from '../windows/WindowManager' import type { ScreenContext } from '@d3ro/core/types' @@ -98,6 +99,18 @@ const TERMINAL_STATES = new Set([ RecognitionState.DESTROYED ]) +// ── 실시간 부분 전사(미리보기) ── +// 16kHz 16bit mono = 32 bytes/ms +const BYTES_PER_MS = 32 +/** 부분 전사 주기 */ +const PARTIAL_INTERVAL_MS = 1500 +/** 부분 전사를 시작할 최소 녹음 길이 */ +const PARTIAL_MIN_AUDIO_MS = 1200 +/** 부분 전사에 보낼 최대 오디오 창(끝부분만) — 오래 말해도 지연이 늘지 않게 한다 */ +const PARTIAL_MAX_WINDOW_MS = 7500 +/** 녹음 종료 시 진행 중 부분 전사를 기다리는 최대 시간 */ +const PARTIAL_DRAIN_TIMEOUT_MS = 2500 + // ============================================================ // VoiceModeService // ============================================================ @@ -123,6 +136,10 @@ class VoiceModeService extends EventEmitter { /** 녹음 종료 후 STT 준비 대기 타이머 — 전사 시작 시 반드시 해제 */ private _sttWaitTimer: NodeJS.Timeout | null = null + // 실시간 부분 전사(미리보기) + private _partialTimer: NodeJS.Timeout | null = null + private _partialInFlight: Promise | null = null + // Action Queue (이벤트 직렬화) private _actionQueue: VoiceAction[] = [] private _isProcessingQueue = false @@ -462,6 +479,8 @@ class VoiceModeService extends EventEmitter { this._setAudioState(AudioState.STREAMING) logger.info('Audio capture started') + this._startPartialLoop() + this._tryFlushAll() } catch (error) { if (this._isInTerminalState()) return @@ -488,6 +507,7 @@ class VoiceModeService extends EventEmitter { this._audioLevelHandler = null } this._audioStarted = false + this._stopPartialLoop() try { await audio.stop() @@ -496,6 +516,73 @@ class VoiceModeService extends EventEmitter { } } + // ── 실시간 부분 전사(미리보기) ───────────────────────────── + + /** + * 녹음 중 주기적으로 지금까지의 오디오를 전사해 RecordingTip에 미리보기를 띄운다. + * 최종 삽입 텍스트와는 완전히 분리된 경로이며, 실패는 조용히 무시된다. + */ + private _startPartialLoop(): void { + this._stopPartialLoop() + if ((configGet('sttProvider') ?? 'local') !== 'local') return + + this._partialTimer = setInterval(() => { + void this._runPartial() + }, PARTIAL_INTERVAL_MS) + } + + private _stopPartialLoop(): void { + if (this._partialTimer) { + clearInterval(this._partialTimer) + this._partialTimer = null + } + } + + /** 진행 중인 부분 전사가 끝나기를 최대 PARTIAL_DRAIN_TIMEOUT_MS까지 기다린다. */ + private async _drainPartial(): Promise { + const inFlight = this._partialInFlight + if (!inFlight) return + await Promise.race([ + inFlight, + new Promise((resolve) => setTimeout(resolve, PARTIAL_DRAIN_TIMEOUT_MS)), + ]) + } + + private async _runPartial(): Promise { + if (!this._audioStarted || this._partialInFlight) return + if (this._isInTerminalState()) return + if (!this._sttReady) return + if (this._audioBufferBytes < PARTIAL_MIN_AUDIO_MS * BYTES_PER_MS) return + + const sessionId = this._session?.id + const merged = Buffer.concat(this._audioBuffer) + const maxBytes = PARTIAL_MAX_WINDOW_MS * BYTES_PER_MS + const window = merged.length > maxBytes ? merged.subarray(merged.length - maxBytes) : merged + const language = configGet('sttLanguage') + + const task = (async (): Promise => { + try { + const text = await getSTTManager().transcribePartial(window, { + language: language === 'auto' ? undefined : language, + vadFilter: false, + }) + // 녹음이 끝났거나 세션이 바뀌었으면 미리보기를 버린다. + if (!this._audioStarted || this._isInTerminalState()) return + if (this._session?.id !== sessionId) return + if (!text) return + sendPartialTranscriptToTip(text) + } catch (err) { + logger.debug( + `부분 전사 미리보기 무시: ${err instanceof Error ? err.message : String(err)}`, + ) + } finally { + this._partialInFlight = null + } + })() + + this._partialInFlight = task + } + // ── 이중 조건 플러시 ─────────────────────────────────── private _tryFlushAll(): void { @@ -553,6 +640,10 @@ class VoiceModeService extends EventEmitter { // DictionaryService 미초기화 시 무시 } + // 사이드카는 요청을 직렬 처리하므로, 진행 중인 미리보기 요청이 최종 전사를 + // 지연시키지 않도록 먼저 배수한다(최대 PARTIAL_DRAIN_TIMEOUT_MS). + await this._drainPartial() + const result: TranscriptionResult = await stt.transcribe(merged, { language: language === 'auto' ? undefined : language, initialPrompt, @@ -864,6 +955,8 @@ class VoiceModeService extends EventEmitter { private _resetToIdle(): void { this._clearSttWaitTimer() + this._stopPartialLoop() + this._partialInFlight = null this._session = null this._audioBuffer = [] this._audioBufferBytes = 0 diff --git a/apps/desktop/src/main/services/stt/STTManager.ts b/apps/desktop/src/main/services/stt/STTManager.ts index 40a60d4..9f3f6df 100644 --- a/apps/desktop/src/main/services/stt/STTManager.ts +++ b/apps/desktop/src/main/services/stt/STTManager.ts @@ -22,6 +22,7 @@ import { AssemblyAIDriver } from './drivers/AssemblyAIDriver' import { GoogleDriver } from './drivers/GoogleDriver' import { CustomDriver } from './drivers/CustomDriver' import { D3ROCloudDriver } from './drivers/D3ROCloudDriver' +import { normalizeLoopbackUrl } from '../../utils/loopback' const logger = getLogger('STTManager') @@ -43,7 +44,7 @@ export const STT_PROVIDERS_META: STTProviderInfo[] = [ badge: 'Cloud · Zero Config', requiresApiKey: false, defaultModel: 'default', - defaultBaseUrl: 'http://localhost:5000', + defaultBaseUrl: 'http://127.0.0.1:5000', models: ['default', 'whisper-large-v3-turbo', 'nova-3', 'gemini-2.0-flash'], isCloud: true, }, @@ -109,7 +110,7 @@ export const STT_PROVIDERS_META: STTProviderInfo[] = [ badge: 'Self-Hosted / Proxy', requiresApiKey: false, defaultModel: 'whisper-1', - defaultBaseUrl: 'http://localhost:8000/v1', + defaultBaseUrl: 'http://127.0.0.1:8000/v1', models: ['whisper-1', 'custom'], isCloud: true, }, @@ -155,7 +156,8 @@ export class STTManager extends EventEmitter { return { apiKey: specificConfig.apiKey ?? '', - baseUrl: specificConfig.baseUrl ?? meta?.defaultBaseUrl ?? '', + // 저장된 값이 localhost일 수 있다(IPv6 해석 실패) → IPv4 루프백으로 정규화. + baseUrl: normalizeLoopbackUrl(specificConfig.baseUrl ?? meta?.defaultBaseUrl ?? ''), modelId: specificConfig.modelId ?? meta?.defaultModel ?? '', temperature: specificConfig.temperature ?? 0, } @@ -245,6 +247,25 @@ export class STTManager extends EventEmitter { } } + /** + * 녹음 중 실시간 미리보기 전사(최종 삽입과 무관). + * 로컬 Whisper에서만 지원한다 — 클라우드 공급자는 요청 비용/지연이 커서 사용하지 않는다. + * 실패는 빈 문자열로 흡수된다. + */ + async transcribePartial(audioBuffer: Buffer, options?: TranscribeOptions): Promise { + if (this.getActiveProvider() !== 'local') return '' + return getLocalSTTService().transcribePartial(audioBuffer, options) + } + + /** + * 로컬 STT 엔진(sidecar + 모델)을 백그라운드로 미리 데운다. + * 첫 받아쓰기 지연을 없애는 것이 목적이며 실패해도 조용히 넘어간다. + */ + async warmUpLocal(): Promise { + if (this.getActiveProvider() !== 'local') return false + return getLocalSTTService().warmUp() + } + getStatus(): STTStatus { const provider = this.getActiveProvider() if (provider === 'local') { diff --git a/apps/desktop/src/main/utils/loopback.ts b/apps/desktop/src/main/utils/loopback.ts new file mode 100644 index 0000000..13e1709 --- /dev/null +++ b/apps/desktop/src/main/utils/loopback.ts @@ -0,0 +1,39 @@ +// src/main/utils/loopback.ts +// 로컬 엔진(Ollama, STT sidecar) URL 정규화. +// +// 배경: Windows 호스트 파일에 `::1 localhost`만 있고 `127.0.0.1 localhost`가 없으면 +// localhost가 IPv6(::1)로만 해석된다. Ollama/uvicorn은 IPv4(127.0.0.1)에만 바인딩하므로 +// `http://localhost:` 요청이 전부 ECONNREFUSED로 실패한다(실측). +// 로컬 엔진은 바인딩 주소가 IPv4 루프백으로 고정이므로 항상 127.0.0.1로 정규화한다. + +/** IPv4 루프백으로 정규화할 호스트 이름 */ +const LOOPBACK_HOSTNAMES = new Set(['localhost', 'localhost.']) + +/** 로컬 엔진 기본 호스트 */ +export const LOOPBACK_HOST = '127.0.0.1' + +/** + * URL의 호스트가 localhost 계열이면 127.0.0.1로 바꾼다. + * 그 외 호스트/잘못된 URL은 원본을 그대로 반환한다. + */ +export function normalizeLoopbackUrl(rawUrl: string): string { + if (!rawUrl) return rawUrl + + try { + const parsed = new URL(rawUrl) + if (!LOOPBACK_HOSTNAMES.has(parsed.hostname.toLowerCase())) { + return rawUrl + } + parsed.hostname = LOOPBACK_HOST + // URL 직렬화는 경로가 없을 때 '/'를 붙인다. 호출측이 `${base}/api/...`로 + // 이어 붙이므로 말미 슬래시는 제거해 중복 래시를 막는다. + return parsed.toString().replace(/\/$/, '') + } catch { + return rawUrl.replace(/^(https?:\/\/)localhost(?=[:/]|$)/i, `$1${LOOPBACK_HOST}`) + } +} + +/** 루프백 호스트로 접속 가능한 로컬 엔진 기본 URL을 만든다. */ +export function loopbackUrl(port: number, protocol = 'http'): string { + return `${protocol}://${LOOPBACK_HOST}:${port}` +} \ No newline at end of file diff --git a/apps/desktop/src/main/utils/paths.ts b/apps/desktop/src/main/utils/paths.ts index f13a403..958c782 100644 --- a/apps/desktop/src/main/utils/paths.ts +++ b/apps/desktop/src/main/utils/paths.ts @@ -4,6 +4,8 @@ import path from 'path' import { app } from 'electron' import { existsSync } from 'fs' +import { D3ROError, ErrorCode } from '@d3ro/core/errors' +import { loopbackUrl } from './loopback' /** Windows는 .exe 접미사, 그 외는 없음 */ const EXE_SUFFIX = process.platform === 'win32' ? '.exe' : '' @@ -17,33 +19,112 @@ function isPackaged(): boolean { } /** - * SoX 실행 파일 경로. 번들된 게 있으면 그것, 없으면 시스템 PATH의 sox. - * - Windows: sox.exe - * - macOS/Linux: sox (brew install sox / apt install sox 필요) + * dev 실행 시 리소스 루트(apps/desktop)를 찾는다. + * + * electron-vite는 electron을 `out/main/index.js`로 직접 띄우기 때문에 + * `app.getAppPath()`가 `apps/desktop/out/main`을 가리킨다. 그대로 쓰면 + * `out/main/sidecar/main.py`, `out/main/resources/sox` 같은 존재하지 않는 경로가 + * 만들어져 sidecar/SoX가 조용히 시스템 PATH 폴백으로 새고 로컬 전사가 실패한다(실측). + * 따라서 상위 디렉토리를 훑어 실제 앱 루트를 찾아 캐시한다. */ -export function getSoxPath(): string { - const soxBin = `sox${EXE_SUFFIX}` - const bundledSox = isPackaged() - ? path.join(process.resourcesPath, 'sox', soxBin) - : path.join(app.getAppPath(), 'resources', 'sox', soxBin) +const APP_ROOT_MARKERS = [ + path.join('sidecar', 'main.py'), + path.join('resources', 'sox'), + 'electron-builder.yml', +] - if (existsSync(bundledSox)) { - return bundledSox +const MAX_ROOT_WALK_UP = 4 + +let cachedAppRoot: string | null = null + +function looksLikeAppRoot(dir: string): boolean { + return APP_ROOT_MARKERS.some((marker) => existsSync(path.join(dir, marker))) +} + +/** 리소스 루트(apps/desktop)를 반환한다. dev에서 못 찾으면 app.getAppPath(). */ +export function getAppRoot(): string { + if (cachedAppRoot) return cachedAppRoot + + const bases: string[] = [app.getAppPath(), process.cwd()] + // electron-vite는 main 번들을 CJS로 내보내므로 __dirname 사용 가능. + if (typeof __dirname === 'string' && __dirname) { + bases.push(__dirname) } - // 번들 없으면 시스템 PATH에서 찾기 (dev 환경 폴백) - return 'sox' + for (const base of bases) { + let dir = base + for (let step = 0; step <= MAX_ROOT_WALK_UP; step++) { + if (looksLikeAppRoot(dir)) { + cachedAppRoot = dir + return dir + } + const parent = path.dirname(dir) + if (parent === dir) break + dir = parent + } + } + + cachedAppRoot = app.getAppPath() + return cachedAppRoot +} + +/** 테스트에서 경로 캐시를 초기화한다. */ +export function resetPathCache(): void { + cachedAppRoot = null + cachedSoxPath = undefined } /** - * rec 실행 파일 경로 (SoX의 녹음 명령). + * 번들 리소스의 dev 경로. + * packaged → process.resourcesPath/, dev → <앱 루트>/resources/ + */ +function devResourcePath(...segments: string[]): string { + return path.join(getAppRoot(), 'resources', ...segments) +} + +function packagedResourcePath(...segments: string[]): string { + return path.join(process.resourcesPath, ...segments) +} + +let cachedSoxPath: string | undefined + +/** + * SoX 실행 파일 경로. 번들된 실행 파일을 우선 사용한다. + * - packaged: resources/sox/sox(.exe) + * - dev: <앱 루트>/resources/sox/sox(.exe) + * 번들이 없으면 시스템 PATH의 `sox`로 폴백한다(설치 안내는 호출측에서 처리). + */ +export function getSoxPath(): string { + if (cachedSoxPath !== undefined) return cachedSoxPath + + const soxBin = `sox${EXE_SUFFIX}` + const candidates = [ + isPackaged() + ? packagedResourcePath('sox', soxBin) + : devResourcePath('sox', soxBin), + ] + + for (const candidate of candidates) { + if (existsSync(candidate)) { + cachedSoxPath = candidate + return candidate + } + } + + // 번들 없으면 시스템 PATH에서 찾기 (dev 환경 폴백) + cachedSoxPath = 'sox' + return cachedSoxPath +} + +/** + * rec 실행 파일 경로 (SoX의 음 명령). * node-record-lpcm16은 rec를 사용한다. */ export function getRecPath(): string { const recBin = `rec${EXE_SUFFIX}` const bundledRec = isPackaged() - ? path.join(process.resourcesPath, 'sox', recBin) - : path.join(app.getAppPath(), 'resources', 'sox', recBin) + ? packagedResourcePath('sox', recBin) + : devResourcePath('sox', recBin) if (existsSync(bundledRec)) { return bundledRec @@ -52,52 +133,75 @@ export function getRecPath(): string { return 'rec' } +/** sidecar 실행 방법 */ +export interface SidecarLaunch { + command: string + args: string[] + /** 어디에서 결정되었는지 (로그/진단용) */ + source: 'bundled' | 'venv' | 'python' +} + /** * STT sidecar 실행 경로. - * - dev: sidecar/.venv/bin/python (있으면) + sidecar/main.py, 없으면 시스템 python3 - * - production: sidecar/sidecar(.exe) (PyInstaller 빌드) + * - packaged: resources/sidecar/sidecar(.exe) — 없으면 명확한 에러 (조용한 폴백 금지) + * - dev: sidecar/.venv python + sidecar/main.py (없으면 시스템 python 폴백) */ -export function getSidecarCommand(): { command: string; args: string[] } { +export function getSidecarCommand(): SidecarLaunch { const sidecarBin = `sidecar${EXE_SUFFIX}` if (isPackaged()) { - const exePath = path.join(process.resourcesPath, 'sidecar', sidecarBin) + const exePath = packagedResourcePath('sidecar', sidecarBin) if (existsSync(exePath)) { - return { command: exePath, args: [] } + return { command: exePath, args: [], source: 'bundled' } } - // PyInstaller 번들 실패 대비 폴백 - const pyPath = path.join(process.resourcesPath, 'sidecar', 'main.py') - const pythonCmd = process.platform === 'win32' ? 'python' : 'python3' - return { command: pythonCmd, args: [pyPath] } + throw new D3ROError( + ErrorCode.STTSidecarSpawnFailed, + `번들된 STT 사이드카를 찾을 수 없습니다: ${exePath}. ` + + '설치 패키지에 sidecar 리소스가 누락되었습니다(로컬 전사 불가). ' + + '앱을 다시 설치하거나 개발 모드에서 `npm run sidecar:build`로 빌드하세요.', + ) } // dev: venv 우선 → 없으면 시스템 python - const sidecarDir = path.join(app.getAppPath(), 'sidecar') + const sidecarDir = path.join(getAppRoot(), 'sidecar') const sidecarPath = path.join(sidecarDir, 'main.py') + if (!existsSync(sidecarPath)) { + throw new D3ROError( + ErrorCode.STTSidecarSpawnFailed, + `STT 사이드카 소스를 찾을 수 없습니다: ${sidecarPath}`, + ) + } + const venvPython = process.platform === 'win32' ? path.join(sidecarDir, '.venv', 'Scripts', 'python.exe') : path.join(sidecarDir, '.venv', 'bin', 'python3') if (existsSync(venvPython)) { - return { command: venvPython, args: [sidecarPath] } + return { command: venvPython, args: [sidecarPath], source: 'venv' } } const pythonCmd = process.platform === 'win32' ? 'python' : 'python3' - return { command: pythonCmd, args: [sidecarPath] } + return { command: pythonCmd, args: [sidecarPath], source: 'python' } +} + +/** STT 사이드카 HTTP 기본 URL. sidecar는 IPv4 프백에만 바인딩한다. */ +export function getSidecarBaseUrl(port: number): string { + return loopbackUrl(port) } /** * 번들된 Ollama 실행 파일 경로. 존재하지 않으면 null을 반환해 시스템 설치본 탐색으로 폴백. * - Windows: ollama.exe * - macOS/Linux: ollama + * (Ollama 탐색은 LocalLLMService 참조) */ export function getBundledOllamaPath(): string | null { const ollamaBin = `ollama${EXE_SUFFIX}` const bundled = isPackaged() - ? path.join(process.resourcesPath, 'ollama', ollamaBin) - : path.join(app.getAppPath(), 'resources', 'ollama', ollamaBin) + ? packagedResourcePath('ollama', ollamaBin) + : devResourcePath('ollama', ollamaBin) return existsSync(bundled) ? bundled : null } @@ -106,35 +210,46 @@ export function getBundledOllamaPath(): string | null { * 효과음 파일 경로. */ export function getSoundPath(filename: string): string { - if (isPackaged()) { - return path.join(process.resourcesPath, 'sounds', filename) - } - return path.join(app.getAppPath(), 'resources', 'sounds', filename) + return isPackaged() + ? packagedResourcePath('sounds', filename) + : devResourcePath('sounds', filename) } /** * ffmpeg 실행 파일 경로. - * - dev: @ffmpeg-installer/ffmpeg의 node_modules 경로 (플랫폼별 자동) - * - production: extraResources로 번들된 경로 + * 1) extraResources로 번들된 resources/ffmpeg/ffmpeg(.exe) + * 2) @ffmpeg-installer/ffmpeg npm 패키지(플랫폼별 정적 바이너리) + * 3) 시스템 PATH의 ffmpeg */ export function getFfmpegPath(): string { const ffmpegBin = `ffmpeg${EXE_SUFFIX}` - if (isPackaged()) { - const bundled = path.join(process.resourcesPath, 'ffmpeg', ffmpegBin) - if (existsSync(bundled)) { - return bundled - } + const bundled = isPackaged() + ? packagedResourcePath('ffmpeg', ffmpegBin) + : devResourcePath('ffmpeg', ffmpegBin) + + if (existsSync(bundled)) { + return bundled } - // dev: @ffmpeg-installer/ffmpeg에서 제공하는 경로 (플랫폼별 자동 선택) try { // eslint-disable-next-line @typescript-eslint/no-require-imports - const installer = require('@ffmpeg-installer/ffmpeg') - return installer.path as string + const installer = require('@ffmpeg-installer/ffmpeg') as { path?: string } + const installerPath = installer.path + if (installerPath) { + // asar 내부 경로는 실행 파일로 쓸 수 없다 → unpacked 경로로 치환 + const unpacked = installerPath.replace( + `${path.sep}app.asar${path.sep}`, + `${path.sep}app.asar.unpacked${path.sep}`, + ) + if (existsSync(unpacked)) return unpacked + if (existsSync(installerPath)) return installerPath + } } catch { - return 'ffmpeg' + // 설치 패키지 없음 → 시스템 PATH 폴백 } + + return 'ffmpeg' } /** @@ -161,7 +276,7 @@ export function getWhisperModelsDir(): string { export function getAppIconPath(): string | null { const filename = process.platform === 'win32' ? 'icon.ico' : 'icon.png' const candidate = isPackaged() - ? path.join(process.resourcesPath, 'icons', filename) - : path.join(app.getAppPath(), 'build', filename) + ? packagedResourcePath('icons', filename) + : path.join(getAppRoot(), 'build', filename) return existsSync(candidate) ? candidate : null -} +} \ No newline at end of file diff --git a/apps/desktop/tests/main/services/STTManager.test.ts b/apps/desktop/tests/main/services/STTManager.test.ts index 6d21482..293af8b 100644 --- a/apps/desktop/tests/main/services/STTManager.test.ts +++ b/apps/desktop/tests/main/services/STTManager.test.ts @@ -383,4 +383,44 @@ describe('STTManager & Multi-provider Drivers', () => { expect(result.text).toBe('로컬 Whisper 폴백 성공') }) }) + + describe('STTManager Live Partial (미리보기)', () => { + it('routes partial transcription through the local engine', async () => { + const mgr = getSTTManager() + mgr.setProvider('local') + + const { getLocalSTTService } = await import('../../../src/main/services/LocalSTTService') + const partialSpy = vi + .spyOn(getLocalSTTService(), 'transcribePartial') + .mockResolvedValue('미리보기 텍스트') + + await expect(mgr.transcribePartial(Buffer.alloc(32000))).resolves.toBe('미리보기 텍스트') + expect(partialSpy).toHaveBeenCalledOnce() + }) + + it('does not call the local engine for cloud providers', async () => { + const mgr = getSTTManager() + mgr.setProvider('groq') + mgr.setProviderConfig('groq', { apiKey: 'gsk-test' }) + + const { getLocalSTTService } = await import('../../../src/main/services/LocalSTTService') + const partialSpy = vi.spyOn(getLocalSTTService(), 'transcribePartial') + + await expect(mgr.transcribePartial(Buffer.alloc(32000))).resolves.toBe('') + expect(partialSpy).not.toHaveBeenCalled() + }) + + it('warms up the local engine only for the local provider', async () => { + const mgr = getSTTManager() + const { getLocalSTTService } = await import('../../../src/main/services/LocalSTTService') + const warmSpy = vi.spyOn(getLocalSTTService(), 'warmUp').mockResolvedValue(true) + + mgr.setProvider('local') + await expect(mgr.warmUpLocal()).resolves.toBe(true) + + mgr.setProvider('deepgram') + await expect(mgr.warmUpLocal()).resolves.toBe(false) + expect(warmSpy).toHaveBeenCalledOnce() + }) + }) }) diff --git a/apps/desktop/tests/main/utils/loopback.test.ts b/apps/desktop/tests/main/utils/loopback.test.ts new file mode 100644 index 0000000..afda159 --- /dev/null +++ b/apps/desktop/tests/main/utils/loopback.test.ts @@ -0,0 +1,54 @@ +// tests/main/utils/loopback.test.ts +// 로컬 엔진 URL 정규화 테스트. +// Windows에서 localhost가 ::1로만 해석되어 Ollama/sidecar 연결이 실패했던 회귀를 고정한다. + +import { describe, it, expect } from 'vitest' +import { loopbackUrl, normalizeLoopbackUrl } from '../../../src/main/utils/loopback' + +describe('normalizeLoopbackUrl', () => { + it('rewrites a bare localhost host to the IPv4 loopback', () => { + expect(normalizeLoopbackUrl('http://localhost:11434')).toBe('http://127.0.0.1:11434') + }) + + it('keeps the port and path', () => { + expect(normalizeLoopbackUrl('http://localhost:8000/v1')).toBe('http://127.0.0.1:8000/v1') + }) + + it('handles https and trailing dot host forms', () => { + expect(normalizeLoopbackUrl('https://localhost:5000/health')).toBe( + 'https://127.0.0.1:5000/health', + ) + expect(normalizeLoopbackUrl('http://localhost.:1234')).toBe('http://127.0.0.1:1234') + }) + + it('leaves remote hosts untouched', () => { + expect(normalizeLoopbackUrl('https://api.openai.com/v1')).toBe('https://api.openai.com/v1') + expect(normalizeLoopbackUrl('http://192.168.0.10:11434')).toBe('http://192.168.0.10:11434') + expect(normalizeLoopbackUrl('http://127.0.0.1:11434')).toBe('http://127.0.0.1:11434') + }) + + it('does not treat lookalike hostnames as loopback', () => { + expect(normalizeLoopbackUrl('http://localhost.evil.com')).toBe('http://localhost.evil.com') + }) + + it('falls back to a textual rewrite when the URL is not parseable', () => { + // 잘못된 포트는 URL 파서가 던진다 → 정규식 폴백 경로를 탄다. + expect(normalizeLoopbackUrl('http://localhost:99999999')).toBe( + 'http://127.0.0.1:99999999', + ) + }) + + it('passes through inputs without a recognizable host', () => { + expect(normalizeLoopbackUrl('localhost:11434')).toBe('localhost:11434') + }) + + it('returns empty input unchanged', () => { + expect(normalizeLoopbackUrl('')).toBe('') + }) +}) + +describe('loopbackUrl', () => { + it('builds an IPv4 loopback URL for a port', () => { + expect(loopbackUrl(18765)).toBe('http://127.0.0.1:18765') + }) +}) diff --git a/apps/desktop/tests/main/utils/paths.test.ts b/apps/desktop/tests/main/utils/paths.test.ts new file mode 100644 index 0000000..54be575 --- /dev/null +++ b/apps/desktop/tests/main/utils/paths.test.ts @@ -0,0 +1,97 @@ +// tests/main/utils/paths.test.ts +// dev/packaged 경로 해석 테스트. +// +// 회귀 배경: electron-vite dev에서 app.getAppPath()가 `out/main`을 가리켜 +// sidecar/SoX가 존재하지 않는 경로로 잡혔고, 결과적으로 로컬 전사와 녹음이 +// 모두 실패했다(시스템 python/PATH 폴백). 여기서 그 해석을 고정한다. + +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' +import { existsSync } from 'node:fs' +import path from 'node:path' +import { app } from 'electron' +import { + getAppRoot, + getSidecarCommand, + getSidecarBaseUrl, + getSoxPath, + resetPathCache, +} from '../../../src/main/utils/paths' + +const desktopDir = path.resolve(__dirname, '..', '..', '..') + +describe('paths (dev)', () => { + beforeEach(() => { + resetPathCache() + }) + + afterEach(() => { + vi.restoreAllMocks() + resetPathCache() + }) + + it('resolves the app root a level above the vite out/main bundle', () => { + // dev에서 app.getAppPath() = /out/main 이다. + vi.mocked(app.getAppPath).mockReturnValue(path.join(desktopDir, 'out', 'main')) + resetPathCache() + + expect(getAppRoot()).toBe(desktopDir) + }) + + it('prefers the bundled SoX binary over the system PATH', () => { + expect(getSoxPath()).toContain(path.join('resources', 'sox', 'sox')) + expect(existsSync(getSoxPath())).toBe(true) + expect(getSoxPath()).not.toBe('sox') + }) + + it('uses the sidecar virtualenv python when it exists', () => { + const launch = getSidecarCommand() + + expect(launch.source).toBe('venv') + expect(launch.command).toContain('.venv') + expect(launch.args[0]).toBe(path.join(desktopDir, 'sidecar', 'main.py')) + expect(existsSync(launch.command)).toBe(true) + }) + + it('builds the sidecar base URL on the IPv4 loopback', () => { + expect(getSidecarBaseUrl(18765)).toBe('http://127.0.0.1:18765') + }) +}) + +describe('paths (packaged)', () => { + beforeEach(() => { + resetPathCache() + Object.defineProperty(app, 'isPackaged', { value: true, configurable: true }) + }) + + afterEach(() => { + Object.defineProperty(app, 'isPackaged', { value: false, configurable: true }) + vi.restoreAllMocks() + resetPathCache() + }) + + it('fails loudly when the packaged sidecar bundle is missing', () => { + Object.defineProperty(process, 'resourcesPath', { + value: path.join(desktopDir, 'definitely-not-bundled'), + configurable: true, + }) + + expect(() => getSidecarCommand()).toThrowError(/사이드카를 찾을 수 없습니다/) + }) + + it('uses the packaged sidecar executable when present', () => { + Object.defineProperty(process, 'resourcesPath', { + value: path.join(desktopDir, 'resources'), + configurable: true, + }) + + // resources/sox 는 커밋되어 있으므로 resources/sidecar/sidecar(.exe)도 + // 같은 방식으로 배치된다. 존재하지 않는 플랫폼이면 번들 실패로 처리된다. + const exeSuffix = process.platform === 'win32' ? '.exe' : '' + const bundled = path.join(desktopDir, 'resources', 'sidecar', `sidecar${exeSuffix}`) + if (existsSync(bundled)) { + expect(getSidecarCommand().source).toBe('bundled') + } else { + expect(() => getSidecarCommand()).toThrow() + } + }) +}) diff --git a/apps/mobile-rn/android/app/build.gradle b/apps/mobile-rn/android/app/build.gradle index 8f1c98a..c99b448 100644 --- a/apps/mobile-rn/android/app/build.gradle +++ b/apps/mobile-rn/android/app/build.gradle @@ -151,8 +151,8 @@ def versionSettingsValid = configuredVersionName != null && configuredVersionName ==~ strictSemver && configuredVersionCodeValue != null && configuredVersionCodeValue <= 2100000000L -def resolvedVersionName = versionSettingsValid ? configuredVersionName : "1.2.0" -def resolvedVersionCode = versionSettingsValid ? configuredVersionCodeValue.toInteger() : 1020001 +def resolvedVersionName = versionSettingsValid ? configuredVersionName : "1.3.0" +def resolvedVersionCode = versionSettingsValid ? configuredVersionCodeValue.toInteger() : 1030001 def requiredReleaseSettings = [ D3RO_RELEASE_STORE_FILE: releaseStoreFilePath, diff --git a/apps/mobile-rn/ios/D3ROVoice.xcodeproj/project.pbxproj b/apps/mobile-rn/ios/D3ROVoice.xcodeproj/project.pbxproj index 2eaba84..81d4618 100644 --- a/apps/mobile-rn/ios/D3ROVoice.xcodeproj/project.pbxproj +++ b/apps/mobile-rn/ios/D3ROVoice.xcodeproj/project.pbxproj @@ -257,7 +257,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 1020001; + CURRENT_PROJECT_VERSION = 1030001; ENABLE_BITCODE = NO; INFOPLIST_FILE = D3ROVoice/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.1; @@ -265,7 +265,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.0; + MARKETING_VERSION = 1.3.0; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -287,14 +287,14 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 1020001; + CURRENT_PROJECT_VERSION = 1030001; INFOPLIST_FILE = D3ROVoice/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.1; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.0; + MARKETING_VERSION = 1.3.0; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", diff --git a/apps/mobile-rn/metadata/android/en-US/changelogs/1030001.txt b/apps/mobile-rn/metadata/android/en-US/changelogs/1030001.txt new file mode 100644 index 0000000..6c7095a --- /dev/null +++ b/apps/mobile-rn/metadata/android/en-US/changelogs/1030001.txt @@ -0,0 +1 @@ +A stability and quality update. Recording and transcription failures now explain the cause and the fix, and app startup behaviour is more predictable. diff --git a/apps/mobile-rn/metadata/android/ko-KR/changelogs/1030001.txt b/apps/mobile-rn/metadata/android/ko-KR/changelogs/1030001.txt new file mode 100644 index 0000000..d071632 --- /dev/null +++ b/apps/mobile-rn/metadata/android/ko-KR/changelogs/1030001.txt @@ -0,0 +1 @@ +안정성과 품질 개선 업데이트입니다. 녹음과 전사가 실패할 때 원인과 해결 방법을 더 분명하게 안내하고, 앱 시작 동작을 정리했습니다. diff --git a/apps/mobile-rn/package-lock.json b/apps/mobile-rn/package-lock.json index 6d68878..dc24c46 100644 --- a/apps/mobile-rn/package-lock.json +++ b/apps/mobile-rn/package-lock.json @@ -1,12 +1,12 @@ { "name": "@d3ro/mobile-rn", - "version": "1.2.0", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@d3ro/mobile-rn", - "version": "1.2.0", + "version": "1.3.0", "dependencies": { "@d3ro/api-client": "file:../../packages/api-client", "@d3ro/core": "file:../../packages/core", @@ -62,7 +62,7 @@ }, "../..": { "name": "d3ro-voice-monorepo", - "version": "1.2.0", + "version": "1.3.0", "license": "MIT", "workspaces": [ "apps/desktop", @@ -81,7 +81,7 @@ }, "../../packages/api-client": { "name": "@d3ro/api-client", - "version": "1.2.0", + "version": "1.3.0", "license": "MIT", "dependencies": { "@d3ro/core": "*", @@ -98,7 +98,7 @@ }, "../../packages/core": { "name": "@d3ro/core", - "version": "1.2.0", + "version": "1.3.0", "license": "MIT", "dependencies": { "docx": "^9.6.1" @@ -109,7 +109,7 @@ }, "../../packages/i18n": { "name": "@d3ro/i18n", - "version": "1.2.0", + "version": "1.3.0", "license": "MIT", "devDependencies": { "@types/react": "^19.0.0" @@ -120,7 +120,7 @@ }, "../../packages/ui-native": { "name": "@d3ro/ui-native", - "version": "1.2.0", + "version": "1.3.0", "license": "MIT", "devDependencies": { "@types/react": "*" diff --git a/apps/mobile-rn/package.json b/apps/mobile-rn/package.json index 7ea08ee..4d33564 100644 --- a/apps/mobile-rn/package.json +++ b/apps/mobile-rn/package.json @@ -1,6 +1,6 @@ { "name": "@d3ro/mobile-rn", - "version": "1.2.0", + "version": "1.3.0", "private": true, "scripts": { "android": "react-native run-android", diff --git a/apps/web/package.json b/apps/web/package.json index c6cbf8b..2aef4ab 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@d3ro/web", - "version": "1.2.0", + "version": "1.3.0", "private": true, "description": "D3RO Voice 웹 앱 — Next.js 기반 SaaS 인터페이스", "scripts": { diff --git a/apps/web/src/components/layout/sidebar.tsx b/apps/web/src/components/layout/sidebar.tsx index bcae6ea..ef28ded 100644 --- a/apps/web/src/components/layout/sidebar.tsx +++ b/apps/web/src/components/layout/sidebar.tsx @@ -139,7 +139,7 @@ export function Sidebar(): React.ReactElement { - v1.2.0 + v1.3.0 diff --git a/docs/map/00-index.md b/docs/map/00-index.md index 09002bf..bda0005 100644 --- a/docs/map/00-index.md +++ b/docs/map/00-index.md @@ -2,7 +2,7 @@ > Status: ACTIVE > Last full audit: 2026-09-13 -> Scope: entire monorepo `D:/workspace/D3ROVoice` at product version `1.2.0` +> Scope: entire monorepo `D:/workspace/D3ROVoice` at product version `1.3.0` > Purpose: let any agent (or human) answer two questions in under a minute: > 1. **What infrastructure exists?** (build, CI, services, APIs, data, packages, deploy) > 2. **How far is each feature developed?** (per surface, with file anchors and status) diff --git a/docs/map/02-infrastructure.md b/docs/map/02-infrastructure.md index b17e236..11803d4 100644 --- a/docs/map/02-infrastructure.md +++ b/docs/map/02-infrastructure.md @@ -97,7 +97,7 @@ Per-app commands that matter: | App | Commands | |---|---| -| desktop | `npm run dev --workspace=@d3ro/desktop`, `build`, `typecheck`, `test` (vitest, 1266 tests), playwright e2e | +| desktop | `npm run dev --workspace=@d3ro/desktop`, `build`, `typecheck`, `test` (vitest), playwright e2e; local STT engine: `npm --prefix apps/desktop run sidecar:setup` then `sidecar:build` (PyInstaller → `sidecar-dist/sidecar`), full local Windows package: `npm --prefix apps/desktop run dist:win:full`; `setup:sox` re-downloads the bundled SoX | | mobile-rn | `npm run typecheck:mobile` / `lint:mobile` / `test:mobile` (root), or `npm --prefix apps/mobile-rn run lint/typecheck/test`; android gradle builds, Maestro E2E | | api-server | `dotnet build`, `dotnet test` (also `apps/api-server.Tests`) | | web | `next build`, playwright e2e in `apps/web/e2e` | @@ -136,7 +136,7 @@ See [`03-shared-packages.md`](./03-shared-packages.md). Summary: ### GitLab CI (`.gitlab-ci.yml`) -Stages `validate → test → build → e2e → package → publish → deploy`. Primary pipeline for desktop Windows/macOS releases (Forgejo Generic Registry is the canonical updater feed; GitLab project 1172 is a legacy mirror) and production mobile releases (`mobile-production-release`, manual/protected). Admin NAS deploy job is intentionally **disabled**. +Stages `validate → test → build → e2e → package → publish → deploy`. Primary pipeline for desktop Windows/macOS releases (Forgejo Generic Registry is the canonical updater feed; GitLab project 1172 is a legacy mirror) and production mobile releases (`mobile-production-release`, manual/protected). Admin NAS deploy job is intentionally **disabled**. `package-windows`/`package-macos` build the faster-whisper sidecar (`sidecar:setup` → `sidecar:build`) and run `scripts/ci/verify-sidecar-bundle.mjs` before electron-builder, so a release can never ship without the local STT engine. ### Forgejo Actions (`.forgejo/workflows/`) @@ -190,11 +190,11 @@ Full detail: [`09-supabase-backend.md`](./09-supabase-backend.md). | File | Purpose | |---|---| -| `release/product-version.json` | version `1.2.0`, `androidVersionCode`/`iosBuildNumber` `1020001`, releaseDate, desktop license keyId | +| `release/product-version.json` | version `1.3.0`, `androidVersionCode`/`iosBuildNumber` `1030001`, releaseDate, desktop license keyId | | `release/android-release-identity.json` | package `com.d3ro.voice`, Play app ID, app-signing SHA-256, upload cert SHA-256, evidence keyId, AdMob unit IDs | | `release/desktop-license-public.pem` | Ed25519 public key for desktop offline licenses | | `release/mobile-release-evidence-public.pem` | Ed25519 public key for mobile release evidence | -| `apps/desktop/electron-builder.yml` | appId `com.d3ro.voice`, NSIS x64 (forced code signing), macOS DMG/ZIP arm64, generic Forgejo publish feed, asarUnpack native modules, extraResources (icons, sounds, sox, ollama) | +| `apps/desktop/electron-builder.yml` | appId `com.d3ro.voice`, NSIS x64 (forced code signing), macOS DMG/ZIP arm64, generic Forgejo publish feed, asarUnpack native modules + `@ffmpeg-installer`, extraResources (icons, sounds, sox, **sidecar**, ffmpeg, ollama) | | `apps/desktop/src/main/update-feed.ts` | Auto-update feed SSOT (canonical Forgejo + legacy GitLab mirror, channels) | | `release/update-policy.json` | Update policy SSOT (channels, minimum supported version, forced update, delta/full, staged rollout, kill switch) | | `apps/desktop/src/main/update-policy.ts` | Policy parsing/decision logic | @@ -207,6 +207,8 @@ Version sync is enforced by `scripts/ci/sync-version.mjs` and `verify-release-me ## 10. Resources & tests - `resources/sox/` — bundled Windows SoX (`sox.exe` + DLLs) for audio capture. +- `resources/ffmpeg/` — optional bundled ffmpeg (CI or manual); `getFfmpegPath()` also resolves the `@ffmpeg-installer/ffmpeg` binary from `app.asar.unpacked`. +- `apps/desktop/sidecar-dist/` — PyInstaller sidecar bundle consumed by `extraResources` (gitignored; built by `npm --prefix apps/desktop run sidecar:build`). - `resources/icons/` — empty; electron-builder falls back to `build/icon.ico|png`. - Desktop tests: `apps/desktop/tests/` (vitest unit + playwright e2e), `apps/desktop/test-results/`. - Mobile tests: `apps/mobile-rn/__tests__/` (57 suites / 353 tests per mobile SSOT), `.maestro/` + `.maestro-output/` E2E evidence. Note: a full parallel Jest run can hit the 5s render timeout on slow machines; re-run the failing spec in isolation before treating it as a regression. diff --git a/docs/map/04-desktop-app.md b/docs/map/04-desktop-app.md index 8bf4167..dd6e5df 100644 --- a/docs/map/04-desktop-app.md +++ b/docs/map/04-desktop-app.md @@ -28,8 +28,8 @@ Singleton + `EventEmitter` pattern (`getXService()` accessors). | Service | Purpose | |---|---| | `VoiceModeService` | Orchestrator: 9-state `RecognitionState` + 4-state `AudioState`, dual-condition flush, action queue. Events: session-started/completed/cancelled, transcription-update, audio-level, recognition/audio-state-changed, premium-llm-fallback, error | -| `AudioCaptureService` | Mic PCM16 16kHz mono (SoX on Windows, node-record-lpcm16 elsewhere). Events: audio-data, audio-level, device-changed, started, stopped, error | -| `LocalSTTService` | faster-whisper Python sidecar manager (state machine, dual-flush, model download/cancel) | +| `AudioCaptureService` | Mic PCM16 16kHz mono (bundled SoX on Windows, node-record-lpcm16 elsewhere). Spawns hidden (`windowsHide`); a missing SoX fails with the exact fix command | +| `LocalSTTService` | faster-whisper Python sidecar manager (state machine, dual-flush, model download/cancel, background warm-up, live partial transcription). Connects over IPv4 loopback (`getSidecarBaseUrl`) and fails fast with an actionable message when the bundled engine or virtualenv is missing | | `HotkeyService` | uiohook-napi global hooking (dictation/hands-free/command/caption). Events: hotkey-pressed/released, double-press, error | | `TextInsertService` | Clipboard save→set→Ctrl+V→restore via nut-js | | `SoundEffectService` | Preloaded WAV feedback (start/stop/error/cancel/chime) | @@ -37,7 +37,7 @@ Singleton + `EventEmitter` pattern (`getXService()` accessors). ### STT engine layer (`services/stt/`) | File | Purpose | |---|---| -| `STTManager` | Dispatcher across local + 6 cloud providers, auto-fallback (events provider-changed, config-changed, fallback-to-local) | +| `STTManager` | Dispatcher across local + 6 cloud providers, auto-fallback (events provider-changed, config-changed, fallback-to-local). `transcribePartial`/`warmUpLocal` route to the local engine only | | `types.ts` | `ISTTDriver` contract | | `audio-utils.ts` | `pcmToWav`, `createProbeWav` | | `drivers/OpenAI|Groq|Deepgram|AssemblyAI|Google|Custom|D3ROCloud` | Provider drivers; `D3ROCloudDriver` uses Supabase access token | @@ -184,6 +184,8 @@ DB schema (`src/main/db/schema.ts`, drizzle SQLite): `history`, `dictionary`, `s - Core dictation/LLM/history pipeline: **implemented + tested** (~590 desktop tests; vitest + playwright). - Cross-platform packaging: Windows NSIS (signed, `forceCodeSigning`), macOS DMG/ZIP arm64 (ad-hoc signing); auto-update via canonical Forgejo feed with update policy (`release/update-policy.json`). - Local-first AI (SoX + faster-whisper sidecar + bundled Ollama) and cloud paths both present. +- **Local STT is packaged** (`1.3.0`): `electron-builder.yml` `extraResources` copies `sidecar-dist/sidecar` → `resources/sidecar` and `resources/ffmpeg` → `resources/ffmpeg`; `scripts/ci/verify-sidecar-bundle.mjs` gates packaging. Build locally with `npm --prefix apps/desktop run sidecar:setup && npm --prefix apps/desktop run sidecar:build`. The sidecar stays in console mode so `stdout`/`stderr` reach the app log (UTF-8, line-buffered); a packaged sidecar **must** exist or startup fails loudly instead of silently falling back to a system Python. +- All local engine URLs (`LocalSTTService`, `LocalLLMService`, `RAGService`, `OnlineLLMService`, `STTManager`) pass through `src/main/utils/loopback.ts`, which rewrites `localhost` to `127.0.0.1`, because some Windows hosts resolve `localhost` to IPv6 only and local engines bind IPv4. - Meeting intelligence, RAG, voice conversation (local + Realtime), captions, file transcription: implemented. - **Ad mediation**: `DirectHouseSponsorAdapter` performs real configurable REST bids; the other 9 adapters remain fail-closed stubs pending official SDKs (see `11-gap-backlog.md` GAP-ADS-01/02). - Tier resolution now routes through `@d3ro/core/entitlement` (`resolveEntitlement`, `normalizeEntitlementTier`); `useLicenseState.isPro` includes `pro_plus`. @@ -206,3 +208,5 @@ DB schema (`src/main/db/schema.ts`, drizzle SQLite): `history`, `dictionary`, `s | Renderer shell / routes | `src/renderer/components/AppLayout.tsx` | | Update feed SSOT | `src/main/update-feed.ts` | | Update policy SSOT | `release/update-policy.json` + `src/main/update-policy.ts` | +| Path/loopback resolution | `src/main/utils/paths.ts`, `src/main/utils/loopback.ts` | +| Sidecar source / packaging | `sidecar/main.py`, `scripts/setup-sidecar.mjs`, `scripts/build-sidecar.mjs`, `scripts/ci/verify-sidecar-bundle.mjs` | diff --git a/docs/map/10-feature-catalog.md b/docs/map/10-feature-catalog.md index 02de900..b2c35af 100644 --- a/docs/map/10-feature-catalog.md +++ b/docs/map/10-feature-catalog.md @@ -16,11 +16,11 @@ Status quick-reference: `[x]` done+verified · `[~]` partial/unverified · `[ ]` |---|---|---|---|---|---|---| | CAP-01 | Push-to-talk dictation (hold/release) | [x] | [-] | [x] | [-] | Desktop `VoiceModeService`; mobile RecordScreen via app CTA/notification action (no global hotkey) | | CAP-02 | Hands-free toggle dictation | [x] | [-] | [x] | [-] | Desktop double-press; mobile toggle | -| CAP-03 | Live partial transcript while recording | [x] | [ ] | [ ] | [-] | Desktop `voice:partialTranscript` + recording-tip | +| CAP-03 | Live partial transcript while recording | [x] | [ ] | [ ] | [-] | Desktop `voice:partialTranscript` + recording-tip; producer added in `1.3.0` (`VoiceModeService._runPartial` → `LocalSTTService.transcribePartial`, 1.5 s cadence / 7.5 s window, never inserted). The row was `[x]` before any producer existed. | | CAP-04 | Recording waveform + level meter | [x] | [x] | [x] | [-] | Desktop 9-bar cos distribution; mobile audio level | | CAP-05 | Device/mic selection | [x] | [ ] | [~] | [-] | Desktop config; mobile uses system default | | CAP-06 | System/loopback audio capture | [x] | [-] | [ ] | [-] | Desktop only (caption source); mobile policy-limited | -| CAP-07 | Local Whisper STT | [x] | [-] | [x] | [-] | Desktop sidecar; mobile on-device Whisper (supported devices) | +| CAP-07 | Local Whisper STT | [x] | [-] | [x] | [-] | Desktop ships the faster-whisper sidecar (`resources/sidecar`, built by `sidecar:build`, verified by `scripts/ci/verify-sidecar-bundle.mjs`), warms it up at app start, and connects over IPv4 loopback; mobile on-device Whisper (supported devices) | | CAP-08 | Cloud STT (multi-provider) | [x] | [x] | [x] | [x] | Desktop 6 providers + D3RO Cloud; web/mobile via `stt-proxy`; .NET internal gateway | | CAP-09 | STT auto-fallback + fail-closed | [x] | [x] | [x] | [x] | `STTManager`; SSOT R-021/R-022 GREEN | | CAP-10 | STT model download/management UI | [x] | [-] | [~] | [-] | Desktop model manager + onboarding; mobile bundled model | @@ -191,7 +191,8 @@ Status quick-reference: `[x]` done+verified · `[~]` partial/unverified · `[ ]` | INFRA-11 | Docker + NAS deploy | [x] | `docker-compose.nas.yml`, `scripts/deploy-nas.*` | | INFRA-12 | Cloudflare edge + tunnel | [x] | `server/cloudflare-worker`, Cloudflare Tunnel `kd-nas` | | INFRA-13 | Site deploy (Cloudflare Pages + GitHub Pages) | [x] | `.forgejo/workflows/deploy-site.yml`, `.github/workflows/deploy-site.yml` | -| INFRA-15 | Update & release system | [x] | Canonical Forgejo feed + channels/policy (`release/update-policy.json`, `src/main/update-policy.ts`), canonical publisher `scripts/ci/publish-forgejo-release.mjs`, legacy GitLab mirror; `npm run release:metadata:test`. v1.1.0 was published to Forgejo on 2026-09-15; product version moved to `1.2.0` as a forward-fix with CI-only publication, a same-version re-release guard, and download centers that link the feed instead of repository paths. | +| INFRA-15 | Update & release system | [x] | Canonical Forgejo feed + channels/policy (`release/update-policy.json`, `src/main/update-policy.ts`), canonical publisher `scripts/ci/publish-forgejo-release.mjs`, legacy GitLab mirror; `npm run release:metadata:test`. v1.1.0 was published to Forgejo on 2026-09-15; product version moved to `1.2.0` as a forward-fix with CI-only publication, a same-version re-release guard, and download centers that link the feed instead of repository paths. `1.3.0` (2026-09-18) carries the local-STT fixes; Windows publication still needs the CI signing secrets (`11` GAP-REL-02). | +| INFRA-16 | Desktop STT engine packaging | [x] | `apps/desktop/scripts/setup-sidecar.mjs` + `build-sidecar.mjs`, `electron-builder.yml` `extraResources` (`sidecar-dist/sidecar` → `resources/sidecar`, `resources/ffmpeg`), and `scripts/ci/verify-sidecar-bundle.mjs` run in `package-windows`/`package-macos` before electron-builder. Verified on the real bundle: `sidecar.exe` + `_internal` including `faster_whisper/assets/silero_vad_v6.onnx`, plus a packaged-engine transcription round-trip on GPU. | --- diff --git a/docs/map/11-gap-backlog.md b/docs/map/11-gap-backlog.md index 69a4c41..161eb6e 100644 --- a/docs/map/11-gap-backlog.md +++ b/docs/map/11-gap-backlog.md @@ -22,7 +22,7 @@ Legend: `[ ]` open · `[~]` in progress · `[!]` blocked externally · `[x]` res |---|---|---|---|---| | GAP-QA-01 | Quality | Extreme Red Team: headful end-to-end bug hunting across real desktop Electron, Web Next.js, and CI pipelines. | `red_team_log.md`, `tests/e2e/red_team_cycle*.spec.ts`, `apps/web/e2e/red_team_cycle4_web.spec.ts` | `[x]` 2026-09-15: 18 scenarios executed, 14 defects caught and 100% resolved (infinite chunking loop DEF-008, IPC signature mismatch DEF-004, markdown editor typing rollback DEF-006, Web RSC Link serialization DEF-012, secret scanner lookahead DEF-013, etc.). All 18 scenarios GREEN with zero regressions. | | GAP-REL-01 | Release | Official release publication to Forgejo and active public download center deployment. | `scripts/ci/publish-forgejo-release.mjs`, `apps/web/src/app/download/page.tsx`, `site/src/sections/Download.tsx`, `apps/web/e2e/red_team_cycle4_web.spec.ts` | `[~]` 2026-09-15: v1.1.0 release assets (`D3RO-Voice-Setup-1.1.0-x64.exe`, `.blockmap`, `latest.yml`, `update-policy.json`) published to canonical Forgejo registry and release hub. 2026-09-16: the published 1.1.0 installer carries no Authenticode signature, so it does not satisfy the release policy; product version moved to `1.2.0` and publication must come from CI with the signing gate GREEN. Download centers in `apps/web` (`/download`) and `site` (`#download`) link the canonical Forgejo feed. | -| GAP-REL-02 | Release | Windows stable publication needs an external public-trust Authenticode PFX, its password, the exact signer subject, and a Forgejo token, none of which live in the repository. | `.forgejo/workflows/release.yml`, `.gitlab-ci.yml`, `scripts/ci/verify-windows-release-artifact.ps1` | `[!]` 2026-09-16: every publisher fails closed without `WIN_CSC_*` and `FORGEJO_TOKEN`; provide them as protected CI secrets, then re-run the `1.2.0` tag pipeline. | +| GAP-REL-02 | Release | Windows stable publication needs an external public-trust Authenticode PFX, its password, the exact signer subject, and a Forgejo token, none of which live in the repository. | `.forgejo/workflows/release.yml`, `.gitlab-ci.yml`, `scripts/ci/verify-windows-release-artifact.ps1` | `[!]` 2026-09-16: every publisher fails closed without `WIN_CSC_*` and `FORGEJO_TOKEN`; provide them as protected CI secrets, then re-run the tag pipeline. Still open as of `1.3.0` (2026-09-18): the `v1.3.0` tag must be built by CI with the signing gate GREEN. Local packaging cannot produce a signed installer (`forceCodeSigning: true`). | | GAP-ADS-01 | Ads | 9 of 10 desktop ad adapters still extend `UnavailableAdAdapter` (`provider_not_integrated`). | `apps/desktop/src/main/services/ads/*` | `[~]` 2026-09-13: `DirectHouseSponsorAdapter` is now a real configurable REST adapter (bid/impression/click/reward via `endpointUrl`; fail-closed when unconfigured; 22 unit tests GREEN). Remaining 9 need official SDKs/authenticated endpoints. | | GAP-ADS-02 | Ads | Desktop mediation reward accounting is not wired to license quota (`claimReward` still returns no tokens). | `AdMediationEngine.ts`, `AppLayout.tsx` | Wire verified `reportRewardCompletion` to `LicenseService` quota after the direct sponsor endpoint exists. | | GAP-ID-01 | Identity | Supabase, .NET JWT/SQLite, and the desktop offline license each had their own tier/role shape. | `@d3ro/core/entitlement`, `LicenseService`, `entitlement-context` | `[~]` 2026-09-13: canonical `EntitlementSnapshot` + `resolveEntitlement` added with tests; desktop tier normalization + `isPro` fixed. Full adoption tracked as GAP-ID-02. | @@ -42,6 +42,12 @@ Legend: `[ ]` open · `[~]` in progress · `[!]` blocked externally · `[x]` res | GAP-PUSH-02 | Push | Nothing triggered `send-push?mode=drain`; enqueued notifications never left the outbox. | `server/cloudflare-worker/src/push-drain.ts`, `wrangler.toml` | `[x]` 2026-09-13: Cloudflare Cron Trigger (`* * * * *`) drains the outbox; tests in CI. Requires `SUPABASE_URL` + `SUPABASE_SERVICE_ROLE_KEY` secret on the worker. | | GAP-PUSH-03 | Push | Mobile/web clients register only `fcm`; no service worker subscription or APNs device token. | `apps/mobile-rn/src/features/notifications/*`, `apps/web` | Add web service worker + `pushManager.subscribe` (store JSON subscription) and iOS APNs token registration. | | GAP-PUSH-04 | Push | Android still depends on FCM (`google-services.json`). | `apps/mobile-rn/android`, `send-push` | Decide: minimal Firebase project, or UnifiedPush/ntfy. See `docs/deployment/push-transport-without-firebase.md`. | +| GAP-STT-01 | Local STT | Packaged desktop builds shipped **no** faster-whisper sidecar: `electron-builder.yml` had no `extraResources` entry for it and no pipeline job built it. Local transcription was impossible on any installed build; the app fell back to a system Python without the runtime. | `apps/desktop/electron-builder.yml`, `.gitlab-ci.yml`, `apps/desktop/scripts/build-sidecar.mjs` | `[x]` 2026-09-18: sidecar bundled via `extraResources` (`sidecar-dist/sidecar` → `resources/sidecar`), `sidecar:setup`/`sidecar:build` scripts, and `scripts/ci/verify-sidecar-bundle.mjs` fails packaging when the engine or its Silero VAD data is missing. Verified on the real 242 MB bundle. | +| GAP-STT-02 | Local STT | Dev resolved the sidecar/SoX paths against the Vite output dir (`out/main`), so the sidecar ran a Python without faster-whisper and SoX recording died with `spawn sox ENOENT`. | `apps/desktop/src/main/utils/paths.ts`, `apps/desktop/tests/main/utils/paths.test.ts` | `[x]` 2026-09-18: app root is discovered by walking up for app markers; packaged mode fails loudly instead of silently falling back. Unit tests pin both branches. | +| GAP-STT-03 | Local engines | On hosts where `localhost` resolves only to IPv6, every local engine call (STT sidecar and Ollama) was refused. Audio capture and local LLM appeared dead. | `apps/desktop/src/main/utils/loopback.ts`, `LocalSTTService`, `LocalLLMService`, `RAGService`, `OnlineLLMService`, `STTManager` | `[x]` 2026-09-18: loopback normalization to `127.0.0.1` for all local engine URLs; defaults updated; 9 unit tests. Verified against the live sidecar and Ollama on a host with an IPv6-only `localhost`. | +| GAP-STT-04 | Local STT | Live partial transcript (`CAP-03`, `voice:partialTranscript`) was marked done but had **no producer**: the channel, popup UI, and preload existed, nothing ever emitted. | `apps/desktop/src/main/services/VoiceModeService.ts`, `LocalSTTService.transcribePartial`, `STTManager.transcribePartial` | `[x]` 2026-09-18: 1.5 s cadence over a 7.5 s trailing window, greedy decode, drained before the final transcription; never inserted. | +| GAP-STT-05 | Local STT | The bundled sidecar lacked faster-whisper's Silero VAD data, so `vad_filter=true` transcription would have failed at runtime even with the engine bundled. | `apps/desktop/scripts/build-sidecar.mjs`, `scripts/ci/verify-sidecar-bundle.mjs` | `[x]` 2026-09-18: `--collect-all faster_whisper` plus a packaging-time presence check for `assets/silero_vad_v6.onnx`. | +| GAP-STT-06 | Local STT | Decode settings were untuned: previous-text conditioning let repeated hallucinations compound, and no VAD parameters meant slow, uneven segments. | `apps/desktop/sidecar/main.py` | `[x]` 2026-09-18: `condition_on_previous_text=false`, bounded low-temperature fallback, `no_speech`/`compression_ratio`/`log_prob` thresholds, 300 ms silence trimming. Same transcript, ~5x faster on the reference machine (7.7 s audio: 1609 ms → 303 ms). | --- @@ -97,7 +103,9 @@ These are the mobile SSOT rows still `[ ]` / `[~]`. Do not duplicate the full te ## 5. Quick "is X done?" lookup -- **Desktop local dictation / LLM / history / meetings / RAG / conversation:** yes, tested. Ads: one real adapter, rest stubs. +- **Desktop local dictation / LLM / history / meetings / RAG / conversation:** local + dictation works in dev **and** in packaged builds as of `1.3.0` (engine bundled, paths + fixed, IPv4 loopback). Ads: one real adapter, rest stubs. - **Web console:** yes, feature-complete for server-shared data; knowledge upload/search and team feed implemented. - **Mobile:** code complete for most flows and tested locally; blocked mainly by external store/console gates, plus a11y and some E2E depth. - **Backend:** fail-closed AI proxies, RLS, billing, ads SSV implemented; push transports (FCM + webpush + APNs) and cron drain implemented. diff --git a/memory/project_status.md b/memory/project_status.md index 8e09510..e634eb0 100644 --- a/memory/project_status.md +++ b/memory/project_status.md @@ -1,5 +1,52 @@ # D3RO-VOICE 프로젝트 현황 +## v1.3.0 — 로컬 전사가 실제로 동작하게 (2026-09-18) 🎙️ + +설치본에서 로컬 전사가 **한 번도 성공한 적 없던** 원인을 끝까지 추적해 수정. 엔진 자체는 +정상이었고, 배선(경로/루프백/패키징)이 전부 어긋나 있었다. + +**원인 (실측 근거)** +1. **패키지에 사이드카가 없었다**: `electron-builder.yml` extraResources에 sidecar 항목이 + 없었고 CIS도 PyInstaller 빌드를 하지 않았다. 설치본 `resources/`에는 icons/sounds/sox만 + 존재 → 항상 faster-whisper 없는 시스템 Python으로 폴백 → 즉사. +2. **dev 경로 해석 실패**: electron-vite는 `out/main/index.js`를 직접 띄워 `app.getAppPath()`가 + `apps/desktop/out/main`이 된다. 그래서 `out/main/sidecar/main.py`(없음) + 시스템 Python 3.14, + SoX도 `spawn sox ENOENT`. 실제 venv(`apps/desktop/sidecar/.venv`, py3.11 + faster-whisper 1.2.1)는 + 한 번도 사용되지 않았다. +3. **localhost = ::1 only**: 이 호스트는 `localhost`가 IPv6로만 해석된다. 사이드카(uvicorn)와 + Ollama는 127.0.0.1에만 바인딩 → `http://localhost:...` 요청 전부 ECONNREFUSED. STT와 로컬 LLM이 + 동시에 죽어 있었다. +4. **VAD 데이터 누락**: faster-whisper의 `assets/silero_vad_v6.onnx`를 PyInstaller가 수집하지 + 않아, 사이드카를 번들했더라도 VAD 사용 시 런타임 실패했을 것. +5. **CAP-03(실시간 부분 전사)은 프로듀서가 없었다**: 채널/팝업/preload는 있는데 emit하는 코드가 0. + +**수정** +- `paths.ts`: 앱 루트를 마커(`sidecar/main.py`, `resources/sox`)로 탐색. 패키지 모드에서 사이드카가 + 없으면 조용히 폴백하지 않고 즉시 실패(명확한 조치 안내). +- `loopback.ts`: `localhost` → `127.0.0.1` 정규화 (LocalSTT/LocalLLM/RAG/OnlineLLM/STTManager, + ConfigService 기본값). 단위 테스트 9개. +- 패키징: extraResources에 `sidecar-dist/sidecar → resources/sidecar`, `resources/ffmpeg → ffmpeg`, + ffmpeg는 `@ffmpeg-installer` 의존성 + asarUnpack. `setup-sidecar.mjs`/`build-sidecar.mjs` + + `scripts/ci/verify-sidecar-bundle.mjs`(VAD onnx 존재 검증) → CI `package-windows/macos`에 추가. + 실측 번들: sidecar.exe 9.4MB + `_internal` 242MB, GPU 전사 왕복 성공. +- 사이드카: 모델 재사용, `--collect-all faster_whisper`/`ctranslate2`, console 모드 유지(로그 수집) + + spawn `windowsHide`, UTF-8 강제(PYTHONIOENCODING/`reconfigure`), 줄 단위 로그 파이프. +- 성능/정합성: `condition_on_previous_text=False`, 온도 폴백 제한([0.0,0.2,0.4]), + no_speech/compression/log_prob 임계, VAD `min_silence_duration_ms=300`, `cpu_threads` 8 상한. + **7.7초 오디오: 1609ms → 303ms (약 5배)**, 전사문 동일. +- 체감: 앱 시작 시 `warmUpLocal()`로 sidecar+모델 예열(bootstrap `stt-warmup`), 녹음 중 1.5초마다 + 최근 7.5초 창 부분 전사 → RecordingTip 미리보기(삽입되지 않음, 최종 전사 전에 drain). + +**검증**: typecheck 0, 변경 파일 lint 0, 신규 테스트 18개 GREEN(loopback 9 / paths 6 / STTManager 3 — +기존 361건 실패는 better-sqlite3가 Electron ABI(130)로 빌드되어 Node 23(131)에서 로드 실패하는 +**선행 환경 문제**로 본 변경과 무관). 실제 사이드카 통합 테스트(실제 PCM 7.7초)로 최종 전사/부분 +전사 확인. electron-builder `--dir`로 패키지 산출물에 sidecar/_internal/VAD/ffmpeg/sox 존재 확인. + +**미해결(외부)**: Windows 공개 서명(WIN_CSC_*)이 없어 로컬에서 서명된 설치본을 만들 수 없다 → +`v1.3.0` 태그는 CI가 서명 게이트와 함께 빌드해야 한다(`11` GAP-REL-02). + +--- + ## D3RO NAS Docker 클라우드 서비스 배포 체계 구축 (2026-08-19 /goal) 🚀 D3RO Voice의 클라우드 API 백엔드, AI 프록시, 관리자 백오피스(BackOffice)를 Synology, QNAP, Linux 등 NAS 환경에 Docker 컨테이너로 손쉽게 배포하고 운영할 수 있는 완전 자동화 인프라를 구축 완료. diff --git a/package-lock.json b/package-lock.json index 832c0b8..3e672a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "d3ro-voice-monorepo", - "version": "1.2.0", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "d3ro-voice-monorepo", - "version": "1.2.0", + "version": "1.3.0", "license": "MIT", "workspaces": [ "apps/desktop", @@ -25,7 +25,7 @@ }, "apps/admin": { "name": "@d3ro/admin", - "version": "1.2.0", + "version": "1.3.0", "dependencies": { "@d3ro/api-client": "*", "@d3ro/core": "*", @@ -109,7 +109,7 @@ }, "apps/desktop": { "name": "@d3ro/desktop", - "version": "1.2.0", + "version": "1.3.0", "license": "MIT", "dependencies": { "@d3ro/core": "*", @@ -118,6 +118,7 @@ "@electron-toolkit/utils": "^4.0.0", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", + "@ffmpeg-installer/ffmpeg": "^1.1.0", "@mui/material": "^7.0.0", "@nut-tree-fork/nut-js": "^4.2.6", "@supabase/supabase-js": "^2.45.0", @@ -154,62 +155,9 @@ "@rollup/rollup-win32-x64-msvc": "^4.60.1" } }, - "apps/mobile-rn": { - "name": "@d3ro/mobile-rn", - "version": "1.2.0", - "extraneous": true, - "dependencies": { - "@d3ro/api-client": "file:../../packages/api-client", - "@d3ro/core": "file:../../packages/core", - "@d3ro/i18n": "file:../../packages/i18n", - "@d3ro/ui-native": "file:../../packages/ui-native", - "@noble/hashes": "^1.8.0", - "@react-native-async-storage/async-storage": "^2.1.0", - "@react-native-documents/picker": "12.0.2", - "@react-navigation/bottom-tabs": "^7.3.0", - "@react-navigation/native": "^7.1.0", - "@react-navigation/native-stack": "^7.3.0", - "@supabase/supabase-js": "^2.45.0", - "react": "19.2.3", - "react-native": "^0.85.0", - "react-native-file-access": "^4.0.3", - "react-native-gesture-handler": "^2.24.0", - "react-native-google-mobile-ads": "16.3.4", - "react-native-iap": "^16.3.1", - "react-native-inappbrowser-reborn": "^3.7.0", - "react-native-keychain": "^9.2.0", - "react-native-nitro-modules": "^0.36.5", - "react-native-nitro-sound": "^0.2.19", - "react-native-safe-area-context": "^5.5.2", - "react-native-screens": "^4.11.0", - "react-native-url-polyfill": "^2.0.0", - "whisper.rn": "0.7.2" - }, - "devDependencies": { - "@babel/core": "^7.25.2", - "@babel/preset-env": "^7.25.3", - "@babel/runtime": "^7.25.0", - "@react-native-community/cli": "20.1.0", - "@react-native-community/cli-platform-android": "20.1.0", - "@react-native-community/cli-platform-ios": "20.1.0", - "@react-native/babel-preset": "0.85.0", - "@react-native/jest-preset": "0.85.0", - "@react-native/metro-config": "0.85.0", - "@react-native/typescript-config": "0.85.0", - "@types/react": "^19.2.0", - "@types/react-test-renderer": "^19.1.0", - "jest": "^29.6.3", - "prettier": "2.8.8", - "react-test-renderer": "19.2.3", - "typescript": "^5.8.3" - }, - "engines": { - "node": "^22.21.0 || ^24.3.0 || >=25.0.0" - } - }, "apps/web": { "name": "@d3ro/web", - "version": "1.2.0", + "version": "1.3.0", "dependencies": { "@d3ro/api-client": "*", "@d3ro/core": "*", @@ -344,20 +292,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", - "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-compilation-targets": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", @@ -383,88 +317,6 @@ "semver": "bin/semver.js" } }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", - "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "@babel/helper-member-expression-to-functions": "^7.29.7", - "@babel/helper-optimise-call-expression": "^7.29.7", - "@babel/helper-replace-supers": "^7.29.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", - "@babel/traverse": "^7.29.7", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.29.7.tgz", - "integrity": "sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "regexpu-core": "^6.3.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", - "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "debug": "^4.4.3", - "lodash.debounce": "^4.0.8", - "resolve": "^1.22.11" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, "node_modules/@babel/helper-globals": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", @@ -474,21 +326,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", - "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-module-imports": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", @@ -519,20 +356,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", - "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-plugin-utils": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", @@ -543,59 +366,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.29.7.tgz", - "integrity": "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "@babel/helper-wrap-function": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", - "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.29.7", - "@babel/helper-optimise-call-expression": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", - "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", @@ -623,22 +393,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.29.7.tgz", - "integrity": "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/template": "^7.29.7", - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helpers": { "version": "7.29.2", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", @@ -667,133 +421,6 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-proposal-export-default-from": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.29.7.tgz", - "integrity": "sha512-p+G5BNXDcy3bOXplhY4HybQ1GxH3i2Tppmdm/3epyRu2VgJJZuUlZ61MqRTg582Q7ZLBdP7fePYvsumSEkMxcQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-default-from": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.29.7.tgz", - "integrity": "sha512-foag0BB37ROhdeIX9O8G0jX7hw0UekJc04cHMrYLOnrErsnBKqJGHJ8eDRpoCFZBvEPPygmmtw4qyU97qa4oOw==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-flow": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.29.7.tgz", - "integrity": "sha512-ajMX6QPcyomotqwpzhkYGxcK2i/us0rs1Qo9QvUpa+Fca0FTmqrzKrctoIYLMxcOhGZldGT/BAVkRGTWBiR8gQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", - "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", - "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-arrow-functions": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz", @@ -810,318 +437,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.7.tgz", - "integrity": "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-remap-async-to-generator": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.29.7.tgz", - "integrity": "sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-remap-async-to-generator": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.29.7.tgz", - "integrity": "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.29.7.tgz", - "integrity": "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.29.7.tgz", - "integrity": "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-globals": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-replace-supers": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.29.7.tgz", - "integrity": "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.29.7.tgz", - "integrity": "sha512-wRHeUjUjCZnMHmiO5bRgjFLcoEh7JyTdByOW11ahhwNa4V0bmeGEaIvt51yq0zQp2yWIpqfxXXPyUP6GFJZHOQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/plugin-syntax-flow": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.29.7.tgz", - "integrity": "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", - "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.7.tgz", - "integrity": "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.29.7.tgz", - "integrity": "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.29.7.tgz", - "integrity": "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.29.7.tgz", - "integrity": "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.29.7.tgz", - "integrity": "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.29.7.tgz", - "integrity": "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "@babel/helper-create-class-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.29.7.tgz", - "integrity": "sha512-+1wdDMGNb4UPeY3Q4L5yLiYe6TXPXubs4NjrgRFw13hPRLJfEMw2Q5OXkee6/IfdqePIeW4Jjwe3aBh7SdKz4Q==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.29.7.tgz", - "integrity": "sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/plugin-syntax-jsx": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.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", @@ -1154,110 +469,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.8.tgz", - "integrity": "sha512-0UpIXPtdDtMXfnV2OJAVMLpj3H/92vmkA6lpSRakmycJvj3VUy6Xs1dM8tXRugupykr5WB+LpiVl0J8LMVg2mg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.7.tgz", - "integrity": "sha512-xmAscdE/AsqRW7vutbPNoUmu/nF5SrLKPs7aoJgEjo35lLKA/Bc0i2rMv/hr1+Y0o1bQCiVtith3u2vdgRL39Q==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "babel-plugin-polyfill-corejs2": "^0.4.14", - "babel-plugin-polyfill-corejs3": "^0.13.0", - "babel-plugin-polyfill-regenerator": "^0.6.5", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime/node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", - "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5", - "core-js-compat": "^3.43.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", - "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "@babel/helper-create-class-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", - "@babel/plugin-syntax-typescript": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.29.7.tgz", - "integrity": "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/runtime": { "version": "7.29.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", @@ -2488,25 +1699,132 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "license": "BSD-3-Clause", + "node_modules/@ffmpeg-installer/darwin-arm64": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/darwin-arm64/-/darwin-arm64-4.1.5.tgz", + "integrity": "sha512-hYqTiP63mXz7wSQfuqfFwfLOfwwFChUedeCVKkBtl/cliaTM7/ePI9bVzfZ2c+dWu3TqCwLDRWNSJ5pqZl8otA==", + "cpu": [ + "arm64" + ], + "hasInstallScript": true, + "license": "https://git.ffmpeg.org/gitweb/ffmpeg.git/blob_plain/HEAD:/LICENSE.md", "optional": true, - "peer": true + "os": [ + "darwin" + ] }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "license": "BSD-3-Clause", + "node_modules/@ffmpeg-installer/darwin-x64": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/darwin-x64/-/darwin-x64-4.1.0.tgz", + "integrity": "sha512-Z4EyG3cIFjdhlY8wI9aLUXuH8nVt7E9SlMVZtWvSPnm2sm37/yC2CwjUzyCQbJbySnef1tQwGG2Sx+uWhd9IAw==", + "cpu": [ + "x64" + ], + "hasInstallScript": true, + "license": "LGPL-2.1", "optional": true, - "peer": true, - "dependencies": { - "@hapi/hoek": "^9.0.0" + "os": [ + "darwin" + ] + }, + "node_modules/@ffmpeg-installer/ffmpeg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/ffmpeg/-/ffmpeg-1.1.0.tgz", + "integrity": "sha512-Uq4rmwkdGxIa9A6Bd/VqqYbT7zqh1GrT5/rFwCwKM70b42W5gIjWeVETq6SdcL0zXqDtY081Ws/iJWhr1+xvQg==", + "license": "LGPL-2.1", + "optionalDependencies": { + "@ffmpeg-installer/darwin-arm64": "4.1.5", + "@ffmpeg-installer/darwin-x64": "4.1.0", + "@ffmpeg-installer/linux-arm": "4.1.3", + "@ffmpeg-installer/linux-arm64": "4.1.4", + "@ffmpeg-installer/linux-ia32": "4.1.0", + "@ffmpeg-installer/linux-x64": "4.1.0", + "@ffmpeg-installer/win32-ia32": "4.1.0", + "@ffmpeg-installer/win32-x64": "4.1.0" } }, + "node_modules/@ffmpeg-installer/linux-arm": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-arm/-/linux-arm-4.1.3.tgz", + "integrity": "sha512-NDf5V6l8AfzZ8WzUGZ5mV8O/xMzRag2ETR6+TlGIsMHp81agx51cqpPItXPib/nAZYmo55Bl2L6/WOMI3A5YRg==", + "cpu": [ + "arm" + ], + "hasInstallScript": true, + "license": "GPLv3", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ffmpeg-installer/linux-arm64": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-arm64/-/linux-arm64-4.1.4.tgz", + "integrity": "sha512-dljEqAOD0oIM6O6DxBW9US/FkvqvQwgJ2lGHOwHDDwu/pX8+V0YsDL1xqHbj1DMX/+nP9rxw7G7gcUvGspSoKg==", + "cpu": [ + "arm64" + ], + "hasInstallScript": true, + "license": "GPLv3", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ffmpeg-installer/linux-ia32": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-ia32/-/linux-ia32-4.1.0.tgz", + "integrity": "sha512-0LWyFQnPf+Ij9GQGD034hS6A90URNu9HCtQ5cTqo5MxOEc7Rd8gLXrJvn++UmxhU0J5RyRE9KRYstdCVUjkNOQ==", + "cpu": [ + "ia32" + ], + "hasInstallScript": true, + "license": "GPLv3", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ffmpeg-installer/linux-x64": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-x64/-/linux-x64-4.1.0.tgz", + "integrity": "sha512-Y5BWhGLU/WpQjOArNIgXD3z5mxxdV8c41C+U15nsE5yF8tVcdCGet5zPs5Zy3Ta6bU7haGpIzryutqCGQA/W8A==", + "cpu": [ + "x64" + ], + "hasInstallScript": true, + "license": "GPLv3", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ffmpeg-installer/win32-ia32": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/win32-ia32/-/win32-ia32-4.1.0.tgz", + "integrity": "sha512-FV2D7RlaZv/lrtdhaQ4oETwoFUsUjlUiasiZLDxhEUPdNDWcH1OU9K1xTvqz+OXLdsmYelUDuBS/zkMOTtlUAw==", + "cpu": [ + "ia32" + ], + "license": "GPLv3", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@ffmpeg-installer/win32-x64": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/win32-x64/-/win32-x64-4.1.0.tgz", + "integrity": "sha512-Drt5u2vzDnIONf4ZEkKtFlbvwj6rI3kxw1Ck9fpudmtgaZIHD4ucsWB2lCZBXRxJgXR+2IMSti+4rtM4C4rXgg==", + "cpu": [ + "x64" + ], + "license": "GPLv3", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -4481,826 +3799,6 @@ "url": "https://opencollective.com/popperjs" } }, - "node_modules/@react-native-community/cli": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-20.1.0.tgz", - "integrity": "sha512-441WsVtRe4nGJ9OzA+QMU1+22lA6Q2hRWqqIMKD0wjEMLqcSfOZyu2UL9a/yRpL/dRpyUsU4n7AxqKfTKO/Csg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-clean": "20.1.0", - "@react-native-community/cli-config": "20.1.0", - "@react-native-community/cli-doctor": "20.1.0", - "@react-native-community/cli-server-api": "20.1.0", - "@react-native-community/cli-tools": "20.1.0", - "@react-native-community/cli-types": "20.1.0", - "commander": "^9.4.1", - "deepmerge": "^4.3.0", - "execa": "^5.0.0", - "find-up": "^5.0.0", - "fs-extra": "^8.1.0", - "graceful-fs": "^4.1.3", - "picocolors": "^1.1.1", - "prompts": "^2.4.2", - "semver": "^7.5.2" - }, - "bin": { - "rnc-cli": "build/bin.js" - }, - "engines": { - "node": ">=20.19.4" - } - }, - "node_modules/@react-native-community/cli-config-android": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config-android/-/cli-config-android-20.1.0.tgz", - "integrity": "sha512-3A01ZDyFeCALzzPcwP/fleHoP3sGNq1UX7FzxkTrOFX8RRL9ntXNXQd27E56VU4BBxGAjAJT4Utw8pcOjJceIA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-tools": "20.1.0", - "fast-glob": "^3.3.2", - "fast-xml-parser": "^4.4.1", - "picocolors": "^1.1.1" - } - }, - "node_modules/@react-native-community/cli-config-android/node_modules/@react-native-community/cli-tools": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-20.1.0.tgz", - "integrity": "sha512-/YmzHGOkY6Bgrv4OaA1L8rFqsBlQd1EB2/ipAoKPiieV0EcB5PUamUSuNeFU3sBZZTYQCUENwX4wgOHgFUlDnQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@vscode/sudo-prompt": "^9.0.0", - "appdirsjs": "^1.2.4", - "execa": "^5.0.0", - "find-up": "^5.0.0", - "launch-editor": "^2.9.1", - "mime": "^2.4.1", - "ora": "^5.4.1", - "picocolors": "^1.1.1", - "prompts": "^2.4.2", - "semver": "^7.5.2" - } - }, - "node_modules/@react-native-community/cli-config-android/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-config-android/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-config-android/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-config-android/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-config-apple": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config-apple/-/cli-config-apple-20.1.0.tgz", - "integrity": "sha512-n6JVs8Q3yxRbtZQOy05ofeb1kGtspGN3SgwPmuaqvURF9fsuS7c4/9up2Kp9C+1D2J1remPJXiZLNGOcJvfpOA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-tools": "20.1.0", - "execa": "^5.0.0", - "fast-glob": "^3.3.2", - "picocolors": "^1.1.1" - } - }, - "node_modules/@react-native-community/cli-config-apple/node_modules/@react-native-community/cli-tools": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-20.1.0.tgz", - "integrity": "sha512-/YmzHGOkY6Bgrv4OaA1L8rFqsBlQd1EB2/ipAoKPiieV0EcB5PUamUSuNeFU3sBZZTYQCUENwX4wgOHgFUlDnQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@vscode/sudo-prompt": "^9.0.0", - "appdirsjs": "^1.2.4", - "execa": "^5.0.0", - "find-up": "^5.0.0", - "launch-editor": "^2.9.1", - "mime": "^2.4.1", - "ora": "^5.4.1", - "picocolors": "^1.1.1", - "prompts": "^2.4.2", - "semver": "^7.5.2" - } - }, - "node_modules/@react-native-community/cli-config-apple/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-config-apple/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-config-apple/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-config-apple/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-platform-android": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-20.1.0.tgz", - "integrity": "sha512-TeHPDThOwDppQRpndm9kCdRCBI8AMy3HSIQ+iy7VYQXL5BtZ5LfmGdusoj7nVN/ZGn0Lc6Gwts5qowyupXdeKg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-config-android": "20.1.0", - "@react-native-community/cli-tools": "20.1.0", - "execa": "^5.0.0", - "logkitty": "^0.7.1", - "picocolors": "^1.1.1" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/@react-native-community/cli-tools": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-20.1.0.tgz", - "integrity": "sha512-/YmzHGOkY6Bgrv4OaA1L8rFqsBlQd1EB2/ipAoKPiieV0EcB5PUamUSuNeFU3sBZZTYQCUENwX4wgOHgFUlDnQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@vscode/sudo-prompt": "^9.0.0", - "appdirsjs": "^1.2.4", - "execa": "^5.0.0", - "find-up": "^5.0.0", - "launch-editor": "^2.9.1", - "mime": "^2.4.1", - "ora": "^5.4.1", - "picocolors": "^1.1.1", - "prompts": "^2.4.2", - "semver": "^7.5.2" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-platform-ios": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-20.1.0.tgz", - "integrity": "sha512-XN7Da9z4WsJxtqVtEzY8q2bv22OsvzaFP5zy5+phMWNoJlU4lf7IvBSxqGYMpQ9XhYP7arDw5vmW4W34s06rnA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-platform-apple": "20.1.0" - } - }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/@react-native-community/cli-platform-apple": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-apple/-/cli-platform-apple-20.1.0.tgz", - "integrity": "sha512-0ih1hrYezSM2cuOlVnwBEFtMwtd8YgpTLmZauDJCv50rIumtkI1cQoOgLoS4tbPCj9U/Vn2a9BFH0DLFOOIacg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-config-apple": "20.1.0", - "@react-native-community/cli-tools": "20.1.0", - "execa": "^5.0.0", - "fast-xml-parser": "^4.4.1", - "picocolors": "^1.1.1" - } - }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/@react-native-community/cli-tools": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-20.1.0.tgz", - "integrity": "sha512-/YmzHGOkY6Bgrv4OaA1L8rFqsBlQd1EB2/ipAoKPiieV0EcB5PUamUSuNeFU3sBZZTYQCUENwX4wgOHgFUlDnQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@vscode/sudo-prompt": "^9.0.0", - "appdirsjs": "^1.2.4", - "execa": "^5.0.0", - "find-up": "^5.0.0", - "launch-editor": "^2.9.1", - "mime": "^2.4.1", - "ora": "^5.4.1", - "picocolors": "^1.1.1", - "prompts": "^2.4.2", - "semver": "^7.5.2" - } - }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/@react-native-community/cli-clean": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-20.1.0.tgz", - "integrity": "sha512-77L4DifWfxAT8ByHnkypge7GBMYpbJAjBGV+toowt5FQSGaTBDcBHCX+FFqFRukD5fH6i8sZ41Gtw+nbfCTTIA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-tools": "20.1.0", - "execa": "^5.0.0", - "fast-glob": "^3.3.2", - "picocolors": "^1.1.1" - } - }, - "node_modules/@react-native-community/cli/node_modules/@react-native-community/cli-config": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-20.1.0.tgz", - "integrity": "sha512-1x9rhLLR/dKKb92Lb5O0l0EmUG08FHf+ZVyVEf9M+tX+p5QIm52MRiy43R0UAZ2jJnFApxRk+N3sxoYK4Dtnag==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-tools": "20.1.0", - "cosmiconfig": "^9.0.0", - "deepmerge": "^4.3.0", - "fast-glob": "^3.3.2", - "joi": "^17.2.1", - "picocolors": "^1.1.1" - } - }, - "node_modules/@react-native-community/cli/node_modules/@react-native-community/cli-doctor": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-20.1.0.tgz", - "integrity": "sha512-QfJF1GVjA4PBrIT3SJ0vFFIu0km1vwOmLDlOYVqfojajZJ+Dnvl0f94GN1il/jT7fITAxom///XH3/URvi7YTQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-config": "20.1.0", - "@react-native-community/cli-platform-android": "20.1.0", - "@react-native-community/cli-platform-apple": "20.1.0", - "@react-native-community/cli-platform-ios": "20.1.0", - "@react-native-community/cli-tools": "20.1.0", - "command-exists": "^1.2.8", - "deepmerge": "^4.3.0", - "envinfo": "^7.13.0", - "execa": "^5.0.0", - "node-stream-zip": "^1.9.1", - "ora": "^5.4.1", - "picocolors": "^1.1.1", - "semver": "^7.5.2", - "wcwidth": "^1.0.1", - "yaml": "^2.2.1" - } - }, - "node_modules/@react-native-community/cli/node_modules/@react-native-community/cli-platform-apple": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-apple/-/cli-platform-apple-20.1.0.tgz", - "integrity": "sha512-0ih1hrYezSM2cuOlVnwBEFtMwtd8YgpTLmZauDJCv50rIumtkI1cQoOgLoS4tbPCj9U/Vn2a9BFH0DLFOOIacg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-config-apple": "20.1.0", - "@react-native-community/cli-tools": "20.1.0", - "execa": "^5.0.0", - "fast-xml-parser": "^4.4.1", - "picocolors": "^1.1.1" - } - }, - "node_modules/@react-native-community/cli/node_modules/@react-native-community/cli-server-api": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-20.1.0.tgz", - "integrity": "sha512-Tb415Oh8syXNT2zOzLzFkBXznzGaqKCiaichxKzGCDKg6JGHp3jSuCmcTcaPeYC7oc32n/S3Psw7798r4Q/7lA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-tools": "20.1.0", - "body-parser": "^1.20.3", - "compression": "^1.7.1", - "connect": "^3.6.5", - "errorhandler": "^1.5.1", - "nocache": "^3.0.1", - "open": "^6.2.0", - "pretty-format": "^29.7.0", - "serve-static": "^1.13.1", - "ws": "^6.2.3" - } - }, - "node_modules/@react-native-community/cli/node_modules/@react-native-community/cli-tools": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-20.1.0.tgz", - "integrity": "sha512-/YmzHGOkY6Bgrv4OaA1L8rFqsBlQd1EB2/ipAoKPiieV0EcB5PUamUSuNeFU3sBZZTYQCUENwX4wgOHgFUlDnQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@vscode/sudo-prompt": "^9.0.0", - "appdirsjs": "^1.2.4", - "execa": "^5.0.0", - "find-up": "^5.0.0", - "launch-editor": "^2.9.1", - "mime": "^2.4.1", - "ora": "^5.4.1", - "picocolors": "^1.1.1", - "prompts": "^2.4.2", - "semver": "^7.5.2" - } - }, - "node_modules/@react-native-community/cli/node_modules/@react-native-community/cli-types": { - "version": "20.1.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-20.1.0.tgz", - "integrity": "sha512-D0kDspcwgbVXyNjwicT7Bb1JgXjijTw1JJd+qxyF/a9+sHv7TU4IchV+gN38QegeXqVyM4Ym7YZIvXMFBmyJqA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "joi": "^17.2.1" - } - }, - "node_modules/@react-native-community/cli/node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": "^12.20.0 || >=14" - } - }, - "node_modules/@react-native-community/cli/node_modules/cosmiconfig": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", - "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@react-native-community/cli/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@react-native-community/cli/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/ws": { - "version": "6.2.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.6.tgz", - "integrity": "sha512-XTrf1gv7kXoVf1hbC3PAyAiPgR8Wz1blcrYIjEsUmr08BLksT41R8KbjmS9408C2ERx7v1JDLD/BkpLEttjfKA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "async-limiter": "~1.0.0" - } - }, - "node_modules/@react-native-community/cli/node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/@react-native/babel-plugin-codegen": { - "version": "0.85.0", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.85.0.tgz", - "integrity": "sha512-+b+kilQXUMEREXyFZxmSrnvUZ6rIW2ATu6pkMZLsituRUx85fg22A8Z+hKHXj7q9Hyny1W0+506gHoxYr6zevw==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/traverse": "^7.29.0", - "@react-native/codegen": "0.85.0" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/babel-plugin-codegen/node_modules/@react-native/codegen": { - "version": "0.85.0", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.85.0.tgz", - "integrity": "sha512-5CHJkC9UpBxQokGju7gD6W615RO1zR17INuB1PB4kcXNy3rre7tyy6ufct+sllDD6ildRC9A//cyh6TI03+jxA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/parser": "^7.29.0", - "hermes-parser": "0.33.3", - "invariant": "^2.2.4", - "nullthrows": "^1.1.1", - "tinyglobby": "^0.2.15", - "yargs": "^17.6.2" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/@react-native/babel-plugin-codegen/node_modules/hermes-estree": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.33.3.tgz", - "integrity": "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==", - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/@react-native/babel-plugin-codegen/node_modules/hermes-parser": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.33.3.tgz", - "integrity": "sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "hermes-estree": "0.33.3" - } - }, - "node_modules/@react-native/babel-preset": { - "version": "0.85.0", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.85.0.tgz", - "integrity": "sha512-xU3q7VeSiBGrT5n0cdtzWf/3NtQlw1C3rJXwOftbFPdbb8nvCpefOdSAKL2UKNHSvA3rcCkNqMeZEtjXRKjcCA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/plugin-proposal-export-default-from": "^7.24.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-default-from": "^7.24.7", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-transform-async-generator-functions": "^7.25.4", - "@babel/plugin-transform-async-to-generator": "^7.24.7", - "@babel/plugin-transform-block-scoping": "^7.25.0", - "@babel/plugin-transform-class-properties": "^7.25.4", - "@babel/plugin-transform-classes": "^7.25.4", - "@babel/plugin-transform-destructuring": "^7.24.8", - "@babel/plugin-transform-flow-strip-types": "^7.25.2", - "@babel/plugin-transform-for-of": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.8", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", - "@babel/plugin-transform-optional-catch-binding": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.8", - "@babel/plugin-transform-private-methods": "^7.24.7", - "@babel/plugin-transform-private-property-in-object": "^7.24.7", - "@babel/plugin-transform-react-display-name": "^7.24.7", - "@babel/plugin-transform-react-jsx": "^7.25.2", - "@babel/plugin-transform-react-jsx-self": "^7.24.7", - "@babel/plugin-transform-react-jsx-source": "^7.24.7", - "@babel/plugin-transform-regenerator": "^7.24.7", - "@babel/plugin-transform-runtime": "^7.24.7", - "@babel/plugin-transform-typescript": "^7.25.2", - "@babel/plugin-transform-unicode-regex": "^7.24.7", - "@react-native/babel-plugin-codegen": "0.85.0", - "babel-plugin-syntax-hermes-parser": "0.33.3", - "babel-plugin-transform-flow-enums": "^0.0.2", - "react-refresh": "^0.14.0" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/@react-native/babel-preset/node_modules/react-refresh": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", - "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/@react-native/debugger-shell": { "version": "0.85.3", "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.85.3.tgz", @@ -5316,523 +3814,6 @@ "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } }, - "node_modules/@react-native/metro-config": { - "version": "0.85.0", - "resolved": "https://registry.npmjs.org/@react-native/metro-config/-/metro-config-0.85.0.tgz", - "integrity": "sha512-3L245HZo2F377BndO6WJD9qRZEnBOIG+uKcXvJirna8SzGxD7wvUjDJHHWvwZo4TXSP6aG/FOa/FAHhCYxjiBg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@react-native/js-polyfills": "0.85.0", - "@react-native/metro-babel-transformer": "0.85.0", - "metro-config": "^0.84.0", - "metro-runtime": "^0.84.0" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/metro-config/node_modules/@react-native/js-polyfills": { - "version": "0.85.0", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.85.0.tgz", - "integrity": "sha512-h2nfIqNEA72Ebdcq5scJg1kyZ01B9xI+NJ2AA8ZpGN8SbxOBNAiZtWEqxzAUe6v5Iu7LE3+1WFBWcMQGtT4zLQ==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/metro-config/node_modules/@react-native/metro-babel-transformer": { - "version": "0.85.0", - "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.85.0.tgz", - "integrity": "sha512-Fao5MQFz2hr4UY09EbXxt8UAJJnQ64QvxNIgoTek24ObTDQ6SqY6MoOLaeV9HGic5sBAHX4lVV/WazhmYkQ1jQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/core": "^7.25.2", - "@react-native/babel-preset": "0.85.0", - "hermes-parser": "0.33.3", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/@react-native/metro-config/node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@react-native/metro-config/node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/@react-native/metro-config/node_modules/hermes-estree": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.33.3.tgz", - "integrity": "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==", - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/@react-native/metro-config/node_modules/hermes-parser": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.33.3.tgz", - "integrity": "sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "hermes-estree": "0.33.3" - } - }, - "node_modules/@react-native/metro-config/node_modules/metro": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.84.5.tgz", - "integrity": "sha512-r1liLkyFZMVSEMNjU1CJU5pRzs3NdkxHqXS60O25c0rCIqAR+cGk7rPydw/g0WAIKVXojIBIF45yYBPagJGcgw==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/core": "^7.25.2", - "@babel/generator": "^7.29.1", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "accepts": "^2.0.0", - "ci-info": "^2.0.0", - "connect": "^3.6.5", - "debug": "^4.4.0", - "error-stack-parser": "^2.0.6", - "flow-enums-runtime": "^0.0.6", - "graceful-fs": "^4.2.4", - "hermes-parser": "0.35.0", - "invariant": "^2.2.4", - "jest-worker": "^29.7.0", - "jsc-safe-url": "^0.2.2", - "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.84.5", - "metro-cache": "0.84.5", - "metro-cache-key": "0.84.5", - "metro-config": "0.84.5", - "metro-core": "0.84.5", - "metro-file-map": "0.84.5", - "metro-resolver": "0.84.5", - "metro-runtime": "0.84.5", - "metro-source-map": "0.84.5", - "metro-symbolicate": "0.84.5", - "metro-transform-plugins": "0.84.5", - "metro-transform-worker": "0.84.5", - "mime-types": "^3.0.1", - "nullthrows": "^1.1.1", - "serialize-error": "^2.1.0", - "source-map": "^0.5.6", - "throat": "^5.0.0", - "ws": "^7.5.10", - "yargs": "^17.6.2" - }, - "bin": { - "metro": "src/cli.js" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/metro-config/node_modules/metro-babel-transformer": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.84.5.tgz", - "integrity": "sha512-2WbHILKMiJUzfdjmGOQOqU1bWi9//gqiclc/tkk/AIsrrVw3efhZ1uhkOwMTxUEPOzqoo091H0olLmVZH5FHGQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/core": "^7.25.2", - "flow-enums-runtime": "^0.0.6", - "hermes-parser": "0.35.0", - "metro-cache-key": "0.84.5", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/metro-config/node_modules/metro-babel-transformer/node_modules/hermes-estree": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", - "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/@react-native/metro-config/node_modules/metro-babel-transformer/node_modules/hermes-parser": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", - "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "hermes-estree": "0.35.0" - } - }, - "node_modules/@react-native/metro-config/node_modules/metro-cache": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.84.5.tgz", - "integrity": "sha512-WHS0n2OxQqtwEjSeQFPePNrMvEFhmQcUQM9cRJMHByWoi/GMWFBEWOf7hVkAM/0KRutAXNbDlSu/cZB6CyxgQQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "exponential-backoff": "^3.1.1", - "flow-enums-runtime": "^0.0.6", - "https-proxy-agent": "^7.0.5", - "metro-core": "0.84.5" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/metro-config/node_modules/metro-cache-key": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.84.5.tgz", - "integrity": "sha512-3dPB2TnvGjjf0/9O7AXVQURKXuQNauTZE7WpTGTlR017Gh/B5y0m/2wcqxfveUguHSpu89KhVxCAlr2k/H7uhQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "flow-enums-runtime": "^0.0.6" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/metro-config/node_modules/metro-config": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.84.5.tgz", - "integrity": "sha512-zie+uN6oohscowi2S7ByU+wUw6CrT4ZxW9uAbONOObSxx86RGmnIAmjXHLkfmcdYoY7jzOPEbqcI6oeVmqyBQA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "connect": "^3.6.5", - "flow-enums-runtime": "^0.0.6", - "jest-validate": "^29.7.0", - "metro": "0.84.5", - "metro-cache": "0.84.5", - "metro-core": "0.84.5", - "metro-runtime": "0.84.5", - "yaml": "^2.6.1" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/metro-config/node_modules/metro-core": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.84.5.tgz", - "integrity": "sha512-xwm605hCi5Y6eJTTb8ZWo6pkUcoBEIyiQOfkZh5GwtDwUrP9SNhTQZhzJHrBCwwxlf3Ptl/pxWJgQ1rsNYMnrA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "flow-enums-runtime": "^0.0.6", - "lodash.throttle": "^4.1.1", - "metro-resolver": "0.84.5" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/metro-config/node_modules/metro-file-map": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.84.5.tgz", - "integrity": "sha512-mlm/JL8toSbSc2akpKIGmzvrVRSCgZ5vkbycI34oMLoOnLGuLyC8WTyVJ6P0hZG/usDaGwZSl/s9BCRriqjGJA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "debug": "^4.4.0", - "fb-watchman": "^2.0.0", - "flow-enums-runtime": "^0.0.6", - "graceful-fs": "^4.2.4", - "invariant": "^2.2.4", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "nullthrows": "^1.1.1", - "walker": "^1.0.7" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/metro-config/node_modules/metro-minify-terser": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.84.5.tgz", - "integrity": "sha512-BJoFwCEDsYnagPqarayInv2+diCDNDdLlaof/p6s9w4gh+gc9HXYM+pDvsKGKKUumpZswNF3Z/ftTMqKl/5IBg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "flow-enums-runtime": "^0.0.6", - "terser": "^5.15.0" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/metro-config/node_modules/metro-resolver": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.84.5.tgz", - "integrity": "sha512-VSSnepg1k6LyCwtb6eirWdAWlpKwBG8Rdtsr1mU38rMelFyWgh3/QuMSiZIZAIjwg/fsa8GhW5/FO54CAUPCEA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "flow-enums-runtime": "^0.0.6" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/metro-config/node_modules/metro-runtime": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.84.5.tgz", - "integrity": "sha512-U1m2+d1Pr+JO2/iVXBB2OfXXityz7tqwIorxfrT15IEgaHvpJBq/OHiqnOWPKJbUl3JcxjcdviZZOKk85oK4Qg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/runtime": "^7.25.0", - "flow-enums-runtime": "^0.0.6" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/metro-config/node_modules/metro-source-map": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.84.5.tgz", - "integrity": "sha512-2BtV5L9uPc49F13Gn5wiP6bX/EncqzqTIk2VL/0F/96Vo0YEOjluT/qktQjFODfqGFsucwnh5mPEAl/2jVEfeg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "flow-enums-runtime": "^0.0.6", - "invariant": "^2.2.4", - "metro-symbolicate": "0.84.5", - "nullthrows": "^1.1.1", - "ob1": "0.84.5", - "source-map": "^0.5.6", - "vlq": "^1.0.0" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/metro-config/node_modules/metro-symbolicate": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.84.5.tgz", - "integrity": "sha512-rQ40zYDAkaWBN9yvjUuAD0ZpzBMZSoKyGYXnb5JrfbKjun7fTvfoLHL3KXFYenBTYZkQtlp4cKSCv/1utxFyOw==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "flow-enums-runtime": "^0.0.6", - "invariant": "^2.2.4", - "metro-source-map": "0.84.5", - "nullthrows": "^1.1.1", - "source-map": "^0.5.6", - "vlq": "^1.0.0" - }, - "bin": { - "metro-symbolicate": "src/index.js" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/metro-config/node_modules/metro-transform-plugins": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.84.5.tgz", - "integrity": "sha512-+InaSVGaOyt0DyRo4Y/zIdPI6CZwnbNho5LAL23tgmuGwv7fyfkF7kKfPjZcfxXBcoYdTLLFnCfCH/dHSiCqNg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/generator": "^7.29.1", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "flow-enums-runtime": "^0.0.6", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/metro-config/node_modules/metro-transform-worker": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.84.5.tgz", - "integrity": "sha512-ui1Z8x4s5RL36gMmKLaMMO7O9NNDHNdthEZSCDQHAau3JcAsTaFOK6I+2q4I/kW5u8hSEjJk9L45TXSVJw6g1A==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/core": "^7.25.2", - "@babel/generator": "^7.29.1", - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "flow-enums-runtime": "^0.0.6", - "metro": "0.84.5", - "metro-babel-transformer": "0.84.5", - "metro-cache": "0.84.5", - "metro-cache-key": "0.84.5", - "metro-minify-terser": "0.84.5", - "metro-source-map": "0.84.5", - "metro-transform-plugins": "0.84.5", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/metro-config/node_modules/metro/node_modules/hermes-estree": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", - "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/@react-native/metro-config/node_modules/metro/node_modules/hermes-parser": { - "version": "0.35.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", - "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "hermes-estree": "0.35.0" - } - }, - "node_modules/@react-native/metro-config/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@react-native/metro-config/node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@react-native/metro-config/node_modules/ob1": { - "version": "0.84.5", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.84.5.tgz", - "integrity": "sha512-aH9RkoZc7w/90HBamFxTw8ZLFr05wXS+iOnvmrgo53Ep8Pyrm5FieQSaPIVROkfFVQISeD/zo92fes26TOwe+A==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "flow-enums-runtime": "^0.0.6" - }, - "engines": { - "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" - } - }, - "node_modules/@react-native/metro-config/node_modules/serialize-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", - "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@react-native/metro-config/node_modules/ws": { - "version": "7.5.13", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", - "integrity": "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/@react-native/metro-config/node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, "node_modules/@react-native/virtualized-lists": { "version": "0.85.3", "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.85.3.tgz", @@ -6249,33 +4230,6 @@ "win32" ] }, - "node_modules/@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "license": "BSD-3-Clause", - "optional": true, - "peer": true, - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", - "license": "BSD-3-Clause", - "optional": true, - "peer": true - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "license": "BSD-3-Clause", - "optional": true, - "peer": true - }, "node_modules/@sinclair/typebox": { "version": "0.27.10", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", @@ -7374,14 +5328,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@vscode/sudo-prompt": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/@vscode/sudo-prompt/-/sudo-prompt-9.3.2.tgz", - "integrity": "sha512-gcXoCN00METUNFeQOFJ+C9xUI0DKB+0EGMVg7wbVYRHBw2Eq3fKisDZOkRdOz3kqXRKOENMfShPOmypw1/8nOw==", - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/@xmldom/xmldom": { "version": "0.8.12", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.12.tgz", @@ -7421,32 +5367,6 @@ "node": ">=6.5" } }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/accepts/node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -7551,115 +5471,6 @@ "license": "MIT", "peer": true }, - "node_modules/ansi-fragments": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", - "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "colorette": "^1.0.7", - "slice-ansi": "^2.0.0", - "strip-ansi": "^5.0.0" - } - }, - "node_modules/ansi-fragments/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-fragments/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ansi-fragments/node_modules/astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/ansi-fragments/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/ansi-fragments/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/ansi-fragments/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/ansi-fragments/node_modules/slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-fragments/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -7876,24 +5687,6 @@ "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/appdirsjs": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.8.tgz", - "integrity": "sha512-8zl1xlxeS4a0/36CT6LOaVioPOL8TeLT1b9OHk0j9xSbzmPBuM7lUgWMSTh6SbuF8fbwjcP1rr30OCLpd1fl+A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/codingjerk" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/codingjerk" - } - ], - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/arch": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", @@ -7969,14 +5762,6 @@ "node": ">=0.12.0" } }, - "node_modules/async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -8019,201 +5804,6 @@ "npm": ">=6" } }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.17", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", - "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-define-polyfill-provider": "^0.6.8", - "semver": "^6.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", - "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.8" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-react-compiler": { - "version": "0.0.0-experimental-592953e-20240517", - "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-0.0.0-experimental-592953e-20240517.tgz", - "integrity": "sha512-OjG1SVaeQZaJrqkMFJatg8W/MTow8Ak5rx2SI0ETQBO1XvOk/XZGMbltNCPdFJLKghBYoBjC+Y3Ap/Xr7B01mA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/generator": "7.2.0", - "@babel/types": "^7.19.0", - "chalk": "4", - "invariant": "^2.2.4", - "pretty-format": "^24", - "zod": "^3.22.4", - "zod-validation-error": "^2.1.0" - } - }, - "node_modules/babel-plugin-react-compiler/node_modules/@babel/generator": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.0.tgz", - "integrity": "sha512-BA75MVfRlFQG2EZgFYIwyT1r6xSkwfP2bdkY/kLZusEYWiJs4xCowab/alaEaT0wSvmVuXGqiefeBlP+7V1yKg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/types": "^7.2.0", - "jsesc": "^2.5.1", - "lodash": "^4.17.10", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - } - }, - "node_modules/babel-plugin-react-compiler/node_modules/@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/babel-plugin-react-compiler/node_modules/@types/istanbul-reports": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz", - "integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*", - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/babel-plugin-react-compiler/node_modules/@types/yargs": { - "version": "13.0.12", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.12.tgz", - "integrity": "sha512-qCxJE1qgz2y0hA4pIxjBR+PelCH0U5CK1XJXFwCNqfmliatKp47UCXXE9Dyk1OXBDLvsCF57TqQEJaeLfDYEOQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/babel-plugin-react-compiler/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/babel-plugin-react-compiler/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/babel-plugin-react-compiler/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/babel-plugin-react-compiler/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/babel-plugin-react-compiler/node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "license": "MIT", - "optional": true, - "peer": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/babel-plugin-react-compiler/node_modules/pretty-format": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", - "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@jest/types": "^24.9.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/babel-plugin-react-compiler/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/babel-plugin-syntax-hermes-parser": { "version": "0.33.3", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.33.3.tgz", @@ -8241,17 +5831,6 @@ "hermes-estree": "0.33.3" } }, - "node_modules/babel-plugin-transform-flow-enums": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz", - "integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@babel/plugin-syntax-flow": "^7.12.1" - } - }, "node_modules/bail": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", @@ -8358,112 +5937,6 @@ "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==", "license": "MIT" }, - "node_modules/body-parser": { - "version": "1.20.6", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", - "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "bytes": "~3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "~1.2.0", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "on-finished": "~2.4.1", - "qs": "~6.15.1", - "raw-body": "~2.5.3", - "type-is": "~1.6.18", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/body-parser/node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/boolean": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", @@ -8666,17 +6139,6 @@ "node": ">= 10.0.0" } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -8829,24 +6291,6 @@ "node": ">= 0.4" } }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -8856,17 +6300,6 @@ "node": ">=6" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/caniuse-lite": { "version": "1.0.30001809", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", @@ -9195,14 +6628,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, - "node_modules/colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -9226,14 +6651,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/command-exists": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", - "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/commander": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", @@ -9254,92 +6671,6 @@ "node": ">=0.10.0" } }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "bytes": "3.1.2", - "compressible": "~2.0.18", - "debug": "2.6.9", - "negotiator": "~0.6.4", - "on-headers": "~1.1.0", - "safe-buffer": "5.2.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/compression/node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -9443,17 +6774,6 @@ "license": "MIT", "peer": true }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", @@ -9473,24 +6793,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/core-js-compat": { - "version": "3.50.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.50.0.tgz", - "integrity": "sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "browserslist": "^4.28.7" - }, - "engines": { - "node": ">=6.4.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -10108,17 +7410,6 @@ } } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/decimal.js-light": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", @@ -10191,17 +7482,6 @@ "dev": true, "license": "MIT" }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -11135,20 +8415,6 @@ "node": ">=6" } }, - "node_modules/envinfo": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", - "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", - "license": "MIT", - "optional": true, - "peer": true, - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/err-code": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", @@ -11175,25 +8441,6 @@ "stackframe": "^1.3.4" } }, - "node_modules/errorhandler": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.2.tgz", - "integrity": "sha512-kNAL7hESndBCrWwS72QyV3IVOTrVmj9D062FV5BQswNL5zEdeRmz/WJFyh6Aj/plvvSOrzddkxW57HgkZcR9Fw==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "accepts": "~1.3.8", - "escape-html": "~1.0.3" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -11775,38 +9022,6 @@ "node": ">=6.0.0" } }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "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", @@ -11837,26 +9052,6 @@ ], "license": "BSD-3-Clause" }, - "node_modules/fast-xml-parser": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.7.tgz", - "integrity": "sha512-a6Qh1RMCNbSrU1+sAyAAZH3rTe+OaWJbNZIq0S+ifZciUUOQtlVxBJwoTUE2bYhysmG/RYyI5WJFIKdBahJdrQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "strnum": "^1.0.5" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, "node_modules/fastdom": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/fastdom/-/fastdom-1.0.12.tgz", @@ -12736,17 +9931,6 @@ "node": ">= 14" } }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "engines": { - "node": ">=10.17.0" - } - }, "node_modules/iceberg-js": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", @@ -13352,21 +10536,6 @@ "jiti": "lib/jiti-cli.mjs" } }, - "node_modules/joi": { - "version": "17.13.6", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.6.tgz", - "integrity": "sha512-ImNZaq/LSysofih+xIGYfR0WUXMA9GLUNB//YTCSrZptoRmVgaNAdJyi6K1kXi9pkLEoSkoI8I4UwtNiu/D7nw==", - "license": "BSD-3-Clause", - "optional": true, - "peer": true, - "dependencies": { - "@hapi/hoek": "^9.3.0", - "@hapi/topo": "^5.1.0", - "@sideway/address": "^4.1.5", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } - }, "node_modules/jpeg-js": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", @@ -13521,29 +10690,6 @@ "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/launch-editor": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.14.1.tgz", - "integrity": "sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "picocolors": "^1.1.1", - "shell-quote": "^1.8.4" - } - }, "node_modules/layout-base": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", @@ -13679,14 +10825,6 @@ "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", "license": "MIT" }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/lodash.escaperegexp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", @@ -13731,158 +10869,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/logkitty": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/logkitty/-/logkitty-0.7.1.tgz", - "integrity": "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ansi-fragments": "^0.2.1", - "dayjs": "^1.8.15", - "yargs": "^15.1.0" - }, - "bin": { - "logkitty": "bin/logkitty.js" - } - }, - "node_modules/logkitty/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/logkitty/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/logkitty/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "license": "ISC", - "optional": true, - "peer": true - }, - "node_modules/logkitty/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -14316,17 +11302,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/memoize-one": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", @@ -14341,17 +11316,6 @@ "license": "MIT", "peer": true }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 8" - } - }, "node_modules/mermaid": { "version": "11.17.0", "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.17.0.tgz", @@ -15381,17 +12345,6 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "license": "MIT" }, - "node_modules/nocache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", - "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/node-abi": { "version": "4.28.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.28.0.tgz", @@ -15526,21 +12479,6 @@ "node": ">=18" } }, - "node_modules/node-stream-zip": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.16.0.tgz", - "integrity": "sha512-ObaRrRoR8T68wF6suxHd7R4XQNamij6ZQHrwG7Dx1D2zeHcDNLsIOBcWrIwtDm7AsCXBguaPHgXhcjxDa2szrg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.12.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/antelle" - } - }, "node_modules/nopt": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", @@ -15606,20 +12544,6 @@ "node": ">=0.10.0" } }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -15649,17 +12573,6 @@ "node": ">= 0.8" } }, - "node_modules/on-headers": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", - "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -15685,31 +12598,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/open": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", - "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "is-wsl": "^1.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/open/node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -15831,17 +12719,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -16190,7 +13067,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } @@ -16469,21 +13345,6 @@ "node": ">=10" } }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -16543,24 +13404,6 @@ "node": ">=6" } }, - "node_modules/qs": { - "version": "6.15.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", - "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", - "license": "BSD-3-Clause", - "optional": true, - "peer": true, - "dependencies": { - "es-define-property": "^1.0.1", - "side-channel": "^1.1.1" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -16604,70 +13447,6 @@ "node": ">= 0.6" } }, - "node_modules/raw-body": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/raw-body/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -17672,75 +14451,12 @@ "redux": "^5.0.0" } }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", - "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", "license": "MIT" }, - "node_modules/regexpu-core": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", - "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.2", - "regjsgen": "^0.8.0", - "regjsparser": "^0.13.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.2.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/regjsparser": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.2.tgz", - "integrity": "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==", - "license": "BSD-2-Clause", - "optional": true, - "peer": true, - "dependencies": { - "jsesc": "~3.1.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, "node_modules/remark-gfm": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", @@ -17825,14 +14541,6 @@ "node": ">=0.10.0" } }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "license": "ISC", - "optional": true, - "peer": true - }, "node_modules/resedit": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz", @@ -18277,14 +14985,6 @@ "node": ">= 0.8" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "license": "ISC", - "optional": true, - "peer": true - }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -18395,86 +15095,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/side-channel": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", - "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4", - "side-channel-list": "^1.0.1", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -18546,14 +15166,6 @@ "node": ">=10" } }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/slice-ansi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", @@ -18836,17 +15448,6 @@ "node": ">=0.10.0" } }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -18860,20 +15461,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strnum": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", - "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/strtok3": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", @@ -19370,17 +15957,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/trough": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", @@ -19467,21 +16043,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -19533,54 +16094,6 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", - "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", - "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", - "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -19807,17 +16320,6 @@ "uuid": "dist-node/bin/uuid" } }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/verror": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", @@ -20111,14 +16613,6 @@ "node": ">= 8" } }, - "node_modules/which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "license": "ISC", - "optional": true, - "peer": true - }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -20360,31 +16854,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "optional": true, - "peer": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-validation-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-2.1.0.tgz", - "integrity": "sha512-VJh93e2wb4c3tWtGgTa0OF/dTt/zoPCPzXq4V11ZjxmEAFaPi/Zss1xIZdEB5RD8GD00U0/iVXgqkF77RV7pdQ==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "zod": "^3.18.0" - } - }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", @@ -20397,7 +16866,7 @@ }, "packages/api-client": { "name": "@d3ro/api-client", - "version": "1.2.0", + "version": "1.3.0", "license": "MIT", "dependencies": { "@d3ro/core": "*", @@ -20414,7 +16883,7 @@ }, "packages/core": { "name": "@d3ro/core", - "version": "1.2.0", + "version": "1.3.0", "license": "MIT", "dependencies": { "docx": "^9.6.1" @@ -20425,7 +16894,7 @@ }, "packages/i18n": { "name": "@d3ro/i18n", - "version": "1.2.0", + "version": "1.3.0", "license": "MIT", "devDependencies": { "@types/react": "^19.0.0" @@ -20436,7 +16905,7 @@ }, "packages/ui": { "name": "@d3ro/ui", - "version": "1.2.0", + "version": "1.3.0", "license": "MIT", "dependencies": { "@d3ro/core": "*" @@ -20456,7 +16925,7 @@ }, "packages/ui-native": { "name": "@d3ro/ui-native", - "version": "1.2.0", + "version": "1.3.0", "license": "MIT", "devDependencies": { "@types/react": "*" diff --git a/package.json b/package.json index f6f935c..2d16304 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "d3ro-voice-monorepo", - "version": "1.2.0", + "version": "1.3.0", "private": true, "description": "D3RO Voice — 멀티플랫폼 AI 음성 어시스턴트 (Monorepo)", "author": "D3RO", diff --git a/packages/api-client/package.json b/packages/api-client/package.json index fb092e8..e56a903 100644 --- a/packages/api-client/package.json +++ b/packages/api-client/package.json @@ -1,6 +1,6 @@ { "name": "@d3ro/api-client", - "version": "1.2.0", + "version": "1.3.0", "private": true, "description": "D3RO Voice API 클라이언트 — Supabase 래퍼 (web/desktop/mobile 공유)", "license": "MIT", diff --git a/packages/core/package.json b/packages/core/package.json index 79f6241..f337d21 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@d3ro/core", - "version": "1.2.0", + "version": "1.3.0", "private": true, "description": "D3RO Voice 공유 비즈니스 로직 — 타입, 에러, IPC 채널, 상수, 유틸", "license": "MIT", diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 11e3c2d..d496837 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@d3ro/i18n", - "version": "1.2.0", + "version": "1.3.0", "private": true, "description": "D3RO Voice 공유 i18n — 12개 locale, 타입 안전 키, Context, 포맷 유틸", "license": "MIT", diff --git a/packages/ui-native/package.json b/packages/ui-native/package.json index c89ef8f..746c3ce 100644 --- a/packages/ui-native/package.json +++ b/packages/ui-native/package.json @@ -1,6 +1,6 @@ { "name": "@d3ro/ui-native", - "version": "1.2.0", + "version": "1.3.0", "private": true, "description": "D3RO Voice React Native DS — MetalCard/PhosphorText/Led/WaveBars etc.", "license": "MIT", diff --git a/packages/ui/package.json b/packages/ui/package.json index 97ca0c6..ce578c6 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@d3ro/ui", - "version": "1.2.0", + "version": "1.3.0", "private": true, "description": "D3RO Voice 공유 UI — 디자인 시스템 컴포넌트 + 테마 토큰 + CSS 변수 맵", "license": "MIT", diff --git a/release/product-version.json b/release/product-version.json index 0cbc573..ec8a3a1 100644 --- a/release/product-version.json +++ b/release/product-version.json @@ -1,8 +1,8 @@ { "schemaVersion": 1, - "version": "1.2.0", - "androidVersionCode": 1020001, - "iosBuildNumber": 1020001, - "releaseDate": "2026-09-16", + "version": "1.3.0", + "androidVersionCode": 1030001, + "iosBuildNumber": 1030001, + "releaseDate": "2026-09-18", "desktopLicensePublicKeyId": "5c52b765135ee2531c681b53cd4dc0e96fff8737f496c0b04ba8334fc81a887f" } diff --git a/scripts/ci/verify-sidecar-bundle.mjs b/scripts/ci/verify-sidecar-bundle.mjs new file mode 100644 index 0000000..b7f0865 --- /dev/null +++ b/scripts/ci/verify-sidecar-bundle.mjs @@ -0,0 +1,57 @@ +// scans/ci/verify-sidecar-bundle.mjs +// 패키징 전에 STT 사이드카 번들이 실제로 존재하고 필수 데이터가 들어있는지 검증한다. +// +// 왜 필요한가: electron-builder의 extraResources는 소스 디렉토리가 없으면 조용히 +// 건너뛴다. 그 결과 로컬 전사가 전혀 동작하지 않는 설치 파일이 배포된 이력이 있다. +// 패키징 직전에 하드 실패시켜 같은 회귀를 막는다. +// +// 사용: node scripts/ci/verify-sidecar-bundle.mjs + +import { existsSync, statSync } from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const scriptDir = path.dirname(fileURLToPath(import.meta.url)) +const repoRoot = path.resolve(scriptDir, '..', '..') +const desktopDir = path.join(repoRoot, 'apps', 'desktop') +const bundleDir = path.join(desktopDir, 'sidecar-dist', 'sidecar') +const exeSuffix = process.platform === 'win32' ? '.exe' : '' +const exePath = path.join(bundleDir, `sidecar${exeSuffix}`) + +/** 패키지에 반드시 포함돼야 하는 런타임 데이터 (PyInstaller 6은 _internal/ 하위) */ +const REQUIRED_RELATIVE = [ + // faster-whisper VAD onnx — 누락하면 vad_filter=true 전사가 런타임에 실패한다. + ['faster_whisper', 'assets', 'silero_vad_v6.onnx'], +] + +const problems = [] + +if (!existsSync(exePath)) { + problems.push( + `사이드카 실행 파일이 없습니다: ${exePath}\n` + + ' 빌드: npm --prefix apps/desktop run sidecar:setup && npm --prefix apps/desktop run sidecar:build', + ) +} + +if (existsSync(bundleDir)) { + for (const parts of REQUIRED_RELATIVE) { + const candidates = [ + path.join(bundleDir, '_internal', ...parts), + path.join(bundleDir, ...parts), + ] + if (!candidates.some((candidate) => existsSync(candidate))) { + problems.push( + `사이드카 번들에 필수 데이터가 없습니다: ${candidates[0]} (또는 ${candidates[1]})`, + ) + } + } +} + +if (problems.length > 0) { + console.error('STT 사이드카 번들 검증 실패:') + for (const problem of problems) console.error(`- ${problem}`) + process.exit(1) +} + +const sizeMb = statSync(exePath).size / (1024 * 1024) +console.log(`STT 사이드카 번들 검증 통과: ${exePath} (${sizeMb.toFixed(1)} MB)`) diff --git a/site/package-lock.json b/site/package-lock.json index 4c5df39..57d4b69 100644 --- a/site/package-lock.json +++ b/site/package-lock.json @@ -1,12 +1,12 @@ { "name": "d3ro-voice-site", - "version": "1.2.0", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "d3ro-voice-site", - "version": "1.2.0", + "version": "1.3.0", "dependencies": { "react": "^19.0.0", "react-dom": "^19.0.0" diff --git a/site/package.json b/site/package.json index e1247c3..1c5a97d 100644 --- a/site/package.json +++ b/site/package.json @@ -1,7 +1,7 @@ { "name": "d3ro-voice-site", "private": true, - "version": "1.2.0", + "version": "1.3.0", "type": "module", "scripts": { "dev": "vite --port 5199 --host",