From 2268501b830c283f3ef6c2d97c9825ef42c93991 Mon Sep 17 00:00:00 2001
From: Young Han <110819238+seyeong-han@users.noreply.github.com>
Date: Tue, 25 Aug 2026 12:24:03 -0700
Subject: [PATCH 1/5] Add Muse Glimmer local voice agent for macOS
---
muse_glimmer/macos/.gitignore | 54 +
muse_glimmer/macos/.node-version | 1 +
muse_glimmer/macos/.python-version | 1 +
muse_glimmer/macos/CONTRIBUTING.md | 16 +
muse_glimmer/macos/LICENSE | 30 +
.../macos/LICENSES/LIVEKIT-MODEL-LICENSE.txt | 113 +
muse_glimmer/macos/LICENSES/OFL-1.1.txt | 80 +
muse_glimmer/macos/Makefile | 49 +
muse_glimmer/macos/PROVENANCE.md | 41 +
muse_glimmer/macos/README.md | 113 +
muse_glimmer/macos/SECURITY.md | 23 +
muse_glimmer/macos/THIRD_PARTY_NOTICES.md | 31 +
.../macos/apps/muse-glimmer-server/README.md | 9 +
.../macos/apps/muse-glimmer-server/launch.py | 64 +
muse_glimmer/macos/apps/token-service/LICENSE | 30 +
.../macos/apps/token-service/pyproject.toml | 38 +
.../muse_glimmer_token_service/__init__.py | 5 +
.../muse_glimmer_token_service/__main__.py | 22 +
.../src/muse_glimmer_token_service/app.py | 110 +
.../src/muse_glimmer_token_service/config.py | 56 +
.../src/muse_glimmer_token_service/tokens.py | 55 +
.../apps/token-service/tests/test_app.py | 186 +
.../apps/token-service/tests/test_cli.py | 28 +
.../apps/token-service/tests/test_config.py | 101 +
muse_glimmer/macos/apps/web/.gitignore | 4 +
muse_glimmer/macos/apps/web/README.md | 41 +
muse_glimmer/macos/apps/web/eslint.config.js | 43 +
muse_glimmer/macos/apps/web/index.html | 17 +
muse_glimmer/macos/apps/web/package-lock.json | 4563 +++++++++++++++++
muse_glimmer/macos/apps/web/package.json | 46 +
muse_glimmer/macos/apps/web/server.mjs | 154 +
muse_glimmer/macos/apps/web/server.test.mjs | 62 +
muse_glimmer/macos/apps/web/src/App.test.tsx | 91 +
muse_glimmer/macos/apps/web/src/App.tsx | 155 +
muse_glimmer/macos/apps/web/src/app.css | 836 +++
.../apps/web/src/avatar/MuseAvatar.test.tsx | 34 +
.../macos/apps/web/src/avatar/MuseAvatar.tsx | 128 +
.../src/components/CompactTranscript.test.tsx | 51 +
.../web/src/components/CompactTranscript.tsx | 45 +
.../apps/web/src/components/RuntimeBadge.tsx | 12 +
.../src/components/SessionControls.test.tsx | 76 +
.../web/src/components/SessionControls.tsx | 94 +
.../apps/web/src/components/VoiceSession.tsx | 84 +
muse_glimmer/macos/apps/web/src/config.ts | 3 +
.../apps/web/src/hooks/useNamedAgentState.ts | 24 +
.../web/src/hooks/usePrefersReducedMotion.ts | 18 +
.../web/src/hooks/useTranscriptionSegments.ts | 59 +
.../web/src/lib/agentPresentation.test.ts | 47 +
.../apps/web/src/lib/agentPresentation.ts | 80 +
.../apps/web/src/lib/tokenClient.test.ts | 97 +
.../macos/apps/web/src/lib/tokenClient.ts | 95 +
.../macos/apps/web/src/lib/transcript.test.ts | 79 +
.../macos/apps/web/src/lib/transcript.ts | 47 +
muse_glimmer/macos/apps/web/src/main.tsx | 20 +
muse_glimmer/macos/apps/web/src/test/setup.ts | 19 +
muse_glimmer/macos/apps/web/src/vite-env.d.ts | 1 +
muse_glimmer/macos/apps/web/tsconfig.app.json | 21 +
muse_glimmer/macos/apps/web/tsconfig.json | 7 +
.../macos/apps/web/tsconfig.node.json | 14 +
muse_glimmer/macos/apps/web/vite.config.ts | 20 +
muse_glimmer/macos/apps/worker/LICENSE | 30 +
muse_glimmer/macos/apps/worker/PROVENANCE.md | 38 +
muse_glimmer/macos/apps/worker/README.md | 19 +
muse_glimmer/macos/apps/worker/pyproject.toml | 66 +
.../src/muse_glimmer_worker/__init__.py | 5 +
.../src/muse_glimmer_worker/__main__.py | 4 +
.../worker/src/muse_glimmer_worker/agent.py | 142 +
.../worker/src/muse_glimmer_worker/cli.py | 762 +++
.../worker/src/muse_glimmer_worker/config.py | 222 +
.../src/muse_glimmer_worker/lifecycle.py | 67 +
.../macos/apps/worker/tests/conftest.py | 10 +
.../macos/apps/worker/tests/test_agent.py | 105 +
.../macos/apps/worker/tests/test_cli.py | 43 +
.../macos/apps/worker/tests/test_config.py | 92 +
.../macos/apps/worker/tests/test_lifecycle.py | 71 +
muse_glimmer/macos/artifacts/README.md | 12 +
.../macos/artifacts/macos-arm64.lock.json | 146 +
.../macos/artifacts/manifest.schema.json | 34 +
.../dependencies/compatibility.lock.json | 39 +
.../config/dependencies/toolchain.lock.json | 15 +
.../macos/config/livekit/macos-arm64.yaml | 22 +
muse_glimmer/macos/docs/architecture.md | 33 +
muse_glimmer/macos/docs/artifacts.md | 19 +
muse_glimmer/macos/docs/development.md | 21 +
muse_glimmer/macos/docs/observability.md | 19 +
muse_glimmer/macos/docs/security-model.md | 41 +
muse_glimmer/macos/docs/upstream-pins.md | 32 +
.../macos/native/macos-arm64/README.md | 9 +
.../macos/native/macos-arm64/targets.json | 12 +
.../livekit-plugins-executorch/LICENSE | 30 +
.../livekit-plugins-executorch/PROVENANCE.md | 43 +
.../livekit-plugins-executorch/README.md | 19 +
.../livekit/plugins/executorch/__init__.py | 18 +
.../plugins/executorch/_helper_process.py | 265 +
.../livekit/plugins/executorch/log.py | 3 +
.../livekit/plugins/executorch/py.typed | 0
.../livekit/plugins/executorch/stt.py | 241 +
.../plugins/executorch/supertonic_tts.py | 487 ++
.../livekit/plugins/executorch/version.py | 1 +
.../livekit-plugins-executorch/pyproject.toml | 62 +
.../tests/fake_helper.py | 207 +
.../tests/fake_supertonic_runner.py | 124 +
.../tests/test_helper_process.py | 125 +
.../tests/test_stt.py | 144 +
.../tests/test_supertonic_tts.py | 282 +
muse_glimmer/macos/pyproject.toml | 41 +
muse_glimmer/macos/scripts/__init__.py | 1 +
muse_glimmer/macos/scripts/bootstrap.py | 146 +
muse_glimmer/macos/scripts/dev_stack.py | 583 +++
muse_glimmer/macos/scripts/llm_readiness.py | 81 +
.../macos/scripts/prepare_artifacts.py | 100 +
muse_glimmer/macos/scripts/privacy_audit.py | 134 +
.../macos/scripts/publication_check.py | 156 +
muse_glimmer/macos/scripts/repository.py | 273 +
.../macos/scripts/validate_manifests.py | 190 +
muse_glimmer/macos/tests/conftest.py | 8 +
muse_glimmer/macos/tests/test_bootstrap.py | 17 +
muse_glimmer/macos/tests/test_dev_stack.py | 327 ++
muse_glimmer/macos/tests/test_e2e_stack.py | 41 +
muse_glimmer/macos/tests/test_makefile.py | 18 +
.../macos/tests/test_privacy_audit.py | 32 +
.../macos/tests/test_publication_check.py | 91 +
muse_glimmer/macos/tests/test_repository.py | 116 +
.../macos/tests/test_validate_manifests.py | 124 +
muse_glimmer/macos/uv.lock | 1594 ++++++
125 files changed, 16697 insertions(+)
create mode 100644 muse_glimmer/macos/.gitignore
create mode 100644 muse_glimmer/macos/.node-version
create mode 100644 muse_glimmer/macos/.python-version
create mode 100644 muse_glimmer/macos/CONTRIBUTING.md
create mode 100644 muse_glimmer/macos/LICENSE
create mode 100644 muse_glimmer/macos/LICENSES/LIVEKIT-MODEL-LICENSE.txt
create mode 100644 muse_glimmer/macos/LICENSES/OFL-1.1.txt
create mode 100644 muse_glimmer/macos/Makefile
create mode 100644 muse_glimmer/macos/PROVENANCE.md
create mode 100644 muse_glimmer/macos/README.md
create mode 100644 muse_glimmer/macos/SECURITY.md
create mode 100644 muse_glimmer/macos/THIRD_PARTY_NOTICES.md
create mode 100644 muse_glimmer/macos/apps/muse-glimmer-server/README.md
create mode 100644 muse_glimmer/macos/apps/muse-glimmer-server/launch.py
create mode 100644 muse_glimmer/macos/apps/token-service/LICENSE
create mode 100644 muse_glimmer/macos/apps/token-service/pyproject.toml
create mode 100644 muse_glimmer/macos/apps/token-service/src/muse_glimmer_token_service/__init__.py
create mode 100644 muse_glimmer/macos/apps/token-service/src/muse_glimmer_token_service/__main__.py
create mode 100644 muse_glimmer/macos/apps/token-service/src/muse_glimmer_token_service/app.py
create mode 100644 muse_glimmer/macos/apps/token-service/src/muse_glimmer_token_service/config.py
create mode 100644 muse_glimmer/macos/apps/token-service/src/muse_glimmer_token_service/tokens.py
create mode 100644 muse_glimmer/macos/apps/token-service/tests/test_app.py
create mode 100644 muse_glimmer/macos/apps/token-service/tests/test_cli.py
create mode 100644 muse_glimmer/macos/apps/token-service/tests/test_config.py
create mode 100644 muse_glimmer/macos/apps/web/.gitignore
create mode 100644 muse_glimmer/macos/apps/web/README.md
create mode 100644 muse_glimmer/macos/apps/web/eslint.config.js
create mode 100644 muse_glimmer/macos/apps/web/index.html
create mode 100644 muse_glimmer/macos/apps/web/package-lock.json
create mode 100644 muse_glimmer/macos/apps/web/package.json
create mode 100644 muse_glimmer/macos/apps/web/server.mjs
create mode 100644 muse_glimmer/macos/apps/web/server.test.mjs
create mode 100644 muse_glimmer/macos/apps/web/src/App.test.tsx
create mode 100644 muse_glimmer/macos/apps/web/src/App.tsx
create mode 100644 muse_glimmer/macos/apps/web/src/app.css
create mode 100644 muse_glimmer/macos/apps/web/src/avatar/MuseAvatar.test.tsx
create mode 100644 muse_glimmer/macos/apps/web/src/avatar/MuseAvatar.tsx
create mode 100644 muse_glimmer/macos/apps/web/src/components/CompactTranscript.test.tsx
create mode 100644 muse_glimmer/macos/apps/web/src/components/CompactTranscript.tsx
create mode 100644 muse_glimmer/macos/apps/web/src/components/RuntimeBadge.tsx
create mode 100644 muse_glimmer/macos/apps/web/src/components/SessionControls.test.tsx
create mode 100644 muse_glimmer/macos/apps/web/src/components/SessionControls.tsx
create mode 100644 muse_glimmer/macos/apps/web/src/components/VoiceSession.tsx
create mode 100644 muse_glimmer/macos/apps/web/src/config.ts
create mode 100644 muse_glimmer/macos/apps/web/src/hooks/useNamedAgentState.ts
create mode 100644 muse_glimmer/macos/apps/web/src/hooks/usePrefersReducedMotion.ts
create mode 100644 muse_glimmer/macos/apps/web/src/hooks/useTranscriptionSegments.ts
create mode 100644 muse_glimmer/macos/apps/web/src/lib/agentPresentation.test.ts
create mode 100644 muse_glimmer/macos/apps/web/src/lib/agentPresentation.ts
create mode 100644 muse_glimmer/macos/apps/web/src/lib/tokenClient.test.ts
create mode 100644 muse_glimmer/macos/apps/web/src/lib/tokenClient.ts
create mode 100644 muse_glimmer/macos/apps/web/src/lib/transcript.test.ts
create mode 100644 muse_glimmer/macos/apps/web/src/lib/transcript.ts
create mode 100644 muse_glimmer/macos/apps/web/src/main.tsx
create mode 100644 muse_glimmer/macos/apps/web/src/test/setup.ts
create mode 100644 muse_glimmer/macos/apps/web/src/vite-env.d.ts
create mode 100644 muse_glimmer/macos/apps/web/tsconfig.app.json
create mode 100644 muse_glimmer/macos/apps/web/tsconfig.json
create mode 100644 muse_glimmer/macos/apps/web/tsconfig.node.json
create mode 100644 muse_glimmer/macos/apps/web/vite.config.ts
create mode 100644 muse_glimmer/macos/apps/worker/LICENSE
create mode 100644 muse_glimmer/macos/apps/worker/PROVENANCE.md
create mode 100644 muse_glimmer/macos/apps/worker/README.md
create mode 100644 muse_glimmer/macos/apps/worker/pyproject.toml
create mode 100644 muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/__init__.py
create mode 100644 muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/__main__.py
create mode 100644 muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/agent.py
create mode 100644 muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/cli.py
create mode 100644 muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/config.py
create mode 100644 muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/lifecycle.py
create mode 100644 muse_glimmer/macos/apps/worker/tests/conftest.py
create mode 100644 muse_glimmer/macos/apps/worker/tests/test_agent.py
create mode 100644 muse_glimmer/macos/apps/worker/tests/test_cli.py
create mode 100644 muse_glimmer/macos/apps/worker/tests/test_config.py
create mode 100644 muse_glimmer/macos/apps/worker/tests/test_lifecycle.py
create mode 100644 muse_glimmer/macos/artifacts/README.md
create mode 100644 muse_glimmer/macos/artifacts/macos-arm64.lock.json
create mode 100644 muse_glimmer/macos/artifacts/manifest.schema.json
create mode 100644 muse_glimmer/macos/config/dependencies/compatibility.lock.json
create mode 100644 muse_glimmer/macos/config/dependencies/toolchain.lock.json
create mode 100644 muse_glimmer/macos/config/livekit/macos-arm64.yaml
create mode 100644 muse_glimmer/macos/docs/architecture.md
create mode 100644 muse_glimmer/macos/docs/artifacts.md
create mode 100644 muse_glimmer/macos/docs/development.md
create mode 100644 muse_glimmer/macos/docs/observability.md
create mode 100644 muse_glimmer/macos/docs/security-model.md
create mode 100644 muse_glimmer/macos/docs/upstream-pins.md
create mode 100644 muse_glimmer/macos/native/macos-arm64/README.md
create mode 100644 muse_glimmer/macos/native/macos-arm64/targets.json
create mode 100644 muse_glimmer/macos/packages/livekit-plugins-executorch/LICENSE
create mode 100644 muse_glimmer/macos/packages/livekit-plugins-executorch/PROVENANCE.md
create mode 100644 muse_glimmer/macos/packages/livekit-plugins-executorch/README.md
create mode 100644 muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/__init__.py
create mode 100644 muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/_helper_process.py
create mode 100644 muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/log.py
create mode 100644 muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/py.typed
create mode 100644 muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/stt.py
create mode 100644 muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/supertonic_tts.py
create mode 100644 muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/version.py
create mode 100644 muse_glimmer/macos/packages/livekit-plugins-executorch/pyproject.toml
create mode 100644 muse_glimmer/macos/packages/livekit-plugins-executorch/tests/fake_helper.py
create mode 100755 muse_glimmer/macos/packages/livekit-plugins-executorch/tests/fake_supertonic_runner.py
create mode 100644 muse_glimmer/macos/packages/livekit-plugins-executorch/tests/test_helper_process.py
create mode 100644 muse_glimmer/macos/packages/livekit-plugins-executorch/tests/test_stt.py
create mode 100644 muse_glimmer/macos/packages/livekit-plugins-executorch/tests/test_supertonic_tts.py
create mode 100644 muse_glimmer/macos/pyproject.toml
create mode 100644 muse_glimmer/macos/scripts/__init__.py
create mode 100644 muse_glimmer/macos/scripts/bootstrap.py
create mode 100644 muse_glimmer/macos/scripts/dev_stack.py
create mode 100644 muse_glimmer/macos/scripts/llm_readiness.py
create mode 100644 muse_glimmer/macos/scripts/prepare_artifacts.py
create mode 100644 muse_glimmer/macos/scripts/privacy_audit.py
create mode 100644 muse_glimmer/macos/scripts/publication_check.py
create mode 100644 muse_glimmer/macos/scripts/repository.py
create mode 100644 muse_glimmer/macos/scripts/validate_manifests.py
create mode 100644 muse_glimmer/macos/tests/conftest.py
create mode 100644 muse_glimmer/macos/tests/test_bootstrap.py
create mode 100644 muse_glimmer/macos/tests/test_dev_stack.py
create mode 100644 muse_glimmer/macos/tests/test_e2e_stack.py
create mode 100644 muse_glimmer/macos/tests/test_makefile.py
create mode 100644 muse_glimmer/macos/tests/test_privacy_audit.py
create mode 100644 muse_glimmer/macos/tests/test_publication_check.py
create mode 100644 muse_glimmer/macos/tests/test_repository.py
create mode 100644 muse_glimmer/macos/tests/test_validate_manifests.py
create mode 100644 muse_glimmer/macos/uv.lock
diff --git a/muse_glimmer/macos/.gitignore b/muse_glimmer/macos/.gitignore
new file mode 100644
index 0000000000..0939211d29
--- /dev/null
+++ b/muse_glimmer/macos/.gitignore
@@ -0,0 +1,54 @@
+# Local configuration and credentials
+.env
+.env.*
+!.env.example
+*.key
+*.keys
+*.pem
+*.token
+
+# Managed local state
+.local/
+
+# Python
+.venv/
+__pycache__/
+*.py[cod]
+*.egg-info/
+.pytest_cache/
+.mypy_cache/
+.ruff_cache/
+.coverage
+htmlcov/
+dist/
+build/
+
+# Node
+node_modules/
+*.tsbuildinfo
+coverage/
+
+# Native/model artifacts
+*.pte
+*.pt
+*.ptd
+*.onnx
+*.safetensors
+*.gguf
+*.bin
+*.dylib
+*.so
+*.a
+*.o
+*.wav
+*.pcm
+*.metallib
+
+# Runtime data
+*.log
+*.pid
+*.sock
+recordings/
+reports/
+museglimmer-reports/
+.DS_Store
diff --git a/muse_glimmer/macos/.node-version b/muse_glimmer/macos/.node-version
new file mode 100644
index 0000000000..1d9b7831ba
--- /dev/null
+++ b/muse_glimmer/macos/.node-version
@@ -0,0 +1 @@
+22.12.0
diff --git a/muse_glimmer/macos/.python-version b/muse_glimmer/macos/.python-version
new file mode 100644
index 0000000000..24ee5b1be9
--- /dev/null
+++ b/muse_glimmer/macos/.python-version
@@ -0,0 +1 @@
+3.13
diff --git a/muse_glimmer/macos/CONTRIBUTING.md b/muse_glimmer/macos/CONTRIBUTING.md
new file mode 100644
index 0000000000..6c61878e69
--- /dev/null
+++ b/muse_glimmer/macos/CONTRIBUTING.md
@@ -0,0 +1,16 @@
+# Contributing
+
+This subtree follows the repository contribution requirements in
+[`../../CONTRIBUTING.md`](../../CONTRIBUTING.md), including its licensing and
+Contributor License Agreement terms. The guidance below is specific to the
+local-only, source-only macOS example.
+
+- Do not commit models, native binaries, credentials, recordings, logs, caches,
+ generated output, or another repository.
+- Keep browser-visible data within the policy documented in
+ `docs/security-model.md`.
+- Keep ASR, LLM, and TTS native components on one pinned ExecuTorch revision.
+- Preserve MuseGlimmer reasoning configuration under
+ `chat_template_kwargs.reasoning_strength`; do not use `reasoning_effort`.
+- Run `make check`, `make test`, and `make publication-check` before opening a
+ pull request.
diff --git a/muse_glimmer/macos/LICENSE b/muse_glimmer/macos/LICENSE
new file mode 100644
index 0000000000..5651f75604
--- /dev/null
+++ b/muse_glimmer/macos/LICENSE
@@ -0,0 +1,30 @@
+BSD License
+
+For "ExecuTorch" software
+
+Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name Meta nor the names of its contributors may be used to
+ endorse or promote products derived from this software without specific
+ prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/muse_glimmer/macos/LICENSES/LIVEKIT-MODEL-LICENSE.txt b/muse_glimmer/macos/LICENSES/LIVEKIT-MODEL-LICENSE.txt
new file mode 100644
index 0000000000..44bea48025
--- /dev/null
+++ b/muse_glimmer/macos/LICENSES/LIVEKIT-MODEL-LICENSE.txt
@@ -0,0 +1,113 @@
+LIVEKIT MODEL LICENSE AGREEMENT
+
+1. Introduction
+
+ LiveKit Incorporated ("LiveKit") is making available its proprietary models for
+ use pursuant to the terms and conditions of this Agreement. As further
+ described below, you may use these LiveKit models freely but can only use them
+ together with the LiveKit Agents framework. You cannot use the LiveKit models
+ on a standalone basis or with any other frameworks.
+
+ BY CLICKING "I ACCEPT," OR BY DOWNLOADING, INSTALLING, OR OTHERWISE ACCESSING
+ OR USING THE LIVEKIT MATERIALS, YOU AGREE THAT YOU HAVE READ AND UNDERSTOOD,
+ AND, AS A CONDITION TO YOUR USE OF THE LIVEKIT MATERIALS, YOU AGREE TO BE
+ BOUND BY, THE FOLLOWING TERMS AND CONDITIONS.
+
+2. Definitions
+
+ "Agreement" means this LiveKit Model License Agreement.
+
+ "Documentation" means the specifications, manuals, and documentation
+ accompanying any LiveKit Model and distributed by LiveKit.
+
+ "Licensee" or "you" means the individual or entity agreeing to be bound by
+ this Agreement.
+
+ "LiveKit Agents" means the proprietary LiveKit software framework for building
+ real-time multimodal AI applications with programmable backend participants.
+
+ "LiveKit Materials" means, collectively, the LiveKit Models and Documentation.
+
+ "LiveKit Model" means any of LiveKit's proprietary software models or
+ algorithms, including machine-learning software code, model weights,
+ inference-enabling software code, training-enabling software code, and
+ fine-tuning enabling software code. Any derivative works of a LiveKit Model,
+ whether developed by LiveKit, you, or any third party, will be deemed the
+ "LiveKit Model" for the purposes of this Agreement.
+
+3. License Rights
+
+ Right to Use LiveKit Materials. Subject to the terms and conditions of this
+ Agreement, including the requirements of Section 3.b, LiveKit grants you a
+ nonexclusive, nontransferable, worldwide, royalty-free license under LiveKit's
+ intellectual property rights to use, reproduce, distribute, copy, and create
+ derivative works of the LiveKit Materials.
+
+ Limitation on Use. As a condition to your use of the LiveKit Materials, you
+ agree: (i) not to use any LiveKit Models on a standalone basis or with any
+ frameworks other than LiveKit Agents; (ii) not to use any LiveKit Materials or
+ any output from, or results of using, LiveKit Models (including any derivative
+ works thereof) to improve or otherwise develop any other models that are not
+ LiveKit Models; or (iii) distribute or otherwise make available the LiveKit
+ Materials (including any derivative works thereof) except (x) pursuant to the
+ terms of this Agreement, and (y) you reproduce the above copyright notice.
+
+4. Intellectual Property
+
+ The LiveKit Materials are owned by LiveKit and its licensors. Except for the
+ rights granted to you under this Agreement, all rights are reserved and no
+ other express or implied rights are granted.
+
+ You will own any derivative works that you created from the LiveKit Materials,
+ subject to the terms of this Agreement.
+
+5. Disclaimer
+
+ UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING, LIVEKIT PROVIDES
+ THE LIVEKIT MATERIALS, AND ANY OUTPUT OR RESULTS THEREFROM, ON AN "AS IS"
+ BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED,
+ INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
+ NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. YOU
+ ARE SOLELY RESPONSIBLE FOR DETERMINING THE APPROPRIATENESS OF USING OR
+ REDISTRIBUTING THE LIVEKIT MATERIALS AND ASSUME ANY RISKS ASSOCIATED WITH YOUR
+ USE OF THE LIVEKIT MATERIALS AND ANY OUTPUT AND RESULTS.
+
+6. Limitation of Liability
+
+ IN NO EVENT AND UNDER NO LEGAL THEORY, WHETHER IN TORT (INCLUDING NEGLIGENCE),
+ CONTRACT, OR OTHERWISE, UNLESS REQUIRED BY APPLICABLE LAW (SUCH AS DELIBERATE
+ AND GROSSLY NEGLIGENT ACTS) OR AGREED TO IN WRITING, WILL LIVEKIT BE LIABLE TO
+ YOU FOR INDIRECT DAMAGES, INCLUDING ANY SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
+ DAMAGES OF ANY CHARACTER ARISING AS A RESULT OF THIS AGREEMENT OR OUT OF THE
+ USE OR INABILITY TO USE THE LIVEKIT MATERIALS OR ANY OUTPUT OR RESULTS
+ THEREFROM (INCLUDING BUT NOT LIMITED TO DAMAGES FOR LOSS OF GOODWILL, WORK
+ STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL
+ DAMAGES OR LOSSES), EVEN IF LIVEKIT HAS BEEN ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGES.
+
+7. Trademarks
+
+ This Agreement does not grant permission to use the trade names, trademarks,
+ service marks, or product names of LiveKit, except as required for reasonable
+ and customary use in describing the origin of the LiveKit Materials.
+
+8. Term and Termination
+
+ The term of this Agreement commences upon your acceptance of this Agreement
+ and continues in effect until you cease using the LiveKit Materials or it is
+ terminated by either party (on immediate written notice to the other party).
+ This Agreement will automatically terminate if you breach any of its terms.
+ Upon termination, you must immediately cease all use of the LiveKit Materials.
+ Sections 4, 5, 6, and 9 will survive termination.
+
+9. Governing Law and Venue
+
+ This Agreement is subject to the laws of the State of California, without
+ regard to its conflict of laws principles. The UN Convention on Contracts for
+ the International Sale of Goods does not apply to this Agreement. The courts
+ located in San Francisco, California, have exclusive jurisdiction for any
+ dispute arising out of this Agreement.
+
++ + + +
+
+Last Updated: November 25, 2024
diff --git a/muse_glimmer/macos/LICENSES/OFL-1.1.txt b/muse_glimmer/macos/LICENSES/OFL-1.1.txt
new file mode 100644
index 0000000000..f1fd8ffc71
--- /dev/null
+++ b/muse_glimmer/macos/LICENSES/OFL-1.1.txt
@@ -0,0 +1,80 @@
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The fonts,
+including any derivative works, can be bundled, embedded, redistributed
+and/or sold with any software provided that any reserved names are not used
+by derivative works. The fonts and derivatives, however, cannot be released
+under any other type of license. The requirement for fonts to remain under
+this license does not apply to any document created using the fonts or their
+derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright Holder(s)
+under this license and clearly marked as such. This may include source files,
+build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting, or
+substituting -- in part or in whole -- any of the components of the Original
+Version, by changing formats or by porting the Font Software to a new
+environment.
+
+"Author" refers to any designer, engineer, programmer, technical writer or
+other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of the Font Software, to use, study, copy, merge, embed, modify, redistribute,
+and sell modified and unmodified copies of the Font Software, subject to the
+following conditions:
+
+1) Neither the Font Software nor any of its individual components, in
+Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy contains
+the above copyright notice and this license. These can be included either as
+stand-alone text files, human-readable headers or in the appropriate
+machine-readable metadata fields within text or binary files as long as those
+fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font Name(s)
+unless explicit written permission is granted by the corresponding Copyright
+Holder. This restriction only applies to the primary font name as presented
+to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any Modified
+Version, except to acknowledge the contribution(s) of the Copyright Holder(s)
+and the Author(s) or with their explicit written permission.
+
+5) The Font Software, modified or unmodified, in part or in whole, must be
+distributed entirely under this license, and must not be distributed under
+any other license. The requirement for fonts to remain under this license
+does not apply to any document created using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
+TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR
+ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL,
+INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF
+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE
+THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/muse_glimmer/macos/Makefile b/muse_glimmer/macos/Makefile
new file mode 100644
index 0000000000..d971d68333
--- /dev/null
+++ b/muse_glimmer/macos/Makefile
@@ -0,0 +1,49 @@
+SHELL := /bin/bash
+PYTHON := .venv/bin/python
+BOOTSTRAP_PYTHON := uv run --no-project --python 3.13 python
+STACK := $(PYTHON) -m scripts.dev_stack
+
+.PHONY: bootstrap prepare-artifacts dev up down restart status logs check test e2e publication-check
+
+bootstrap:
+ @$(BOOTSTRAP_PYTHON) -m scripts.bootstrap
+
+prepare-artifacts:
+ @$(PYTHON) -m scripts.prepare_artifacts
+
+dev: up
+
+up:
+ @$(STACK) up
+
+down:
+ @$(STACK) down
+
+restart:
+ @$(STACK) restart
+
+status:
+ @$(STACK) status
+
+logs:
+ @$(STACK) logs
+
+check:
+ @$(PYTHON) -m scripts.validate_manifests
+ @$(PYTHON) -m scripts.publication_check
+ @uv run --all-packages --all-groups ruff check .
+ @uv run --all-packages --all-groups ruff format --check .
+ @npm --prefix apps/web run lint
+ @npm --prefix apps/web run typecheck
+ @npm --prefix apps/web run format:check
+
+test:
+ @uv run --all-packages --all-groups pytest
+ @npm --prefix apps/web test
+ @npm --prefix apps/web run build
+
+e2e:
+ @uv run --all-packages --all-groups pytest -m e2e
+
+publication-check:
+ @python3 -m scripts.publication_check
diff --git a/muse_glimmer/macos/PROVENANCE.md b/muse_glimmer/macos/PROVENANCE.md
new file mode 100644
index 0000000000..649001a72d
--- /dev/null
+++ b/muse_glimmer/macos/PROVENANCE.md
@@ -0,0 +1,41 @@
+# Provenance
+
+## Canonical source
+
+This macOS example is product-owned source in the canonical
+[`meta-pytorch/executorch-examples`](https://github.com/meta-pytorch/executorch-examples)
+repository under `muse_glimmer/macos`. Product-owned source is licensed under
+BSD-3-Clause as described in `LICENSE`.
+
+The source snapshot used for this migration is
+`914fb816fe9e0f6b7fc808fd843eb2e97df31dcf`. That snapshot records development
+history; the canonical maintained source and ownership are in
+`meta-pytorch/executorch-examples`.
+
+## Original source and API integrations
+
+The application, token service, worker, web UI, launchers, packaging, lifecycle
+code, and LiveKit ExecuTorch adapters are original product source. They
+integrate with public LiveKit APIs but do not copy LiveKit implementation
+source.
+
+The worker and adapter integrations were developed against the public
+[`livekit/agents`](https://github.com/livekit/agents) API at commit
+`bc5f3df3a2bd1b3b8c5d1df742be57b063374991`. Package-specific source mappings
+and integration details are recorded in:
+
+- `apps/worker/PROVENANCE.md`
+- `packages/livekit-plugins-executorch/PROVENANCE.md`
+
+The token service integrates with the LiveKit API to issue short-lived tokens;
+it does not include LiveKit implementation source.
+
+## Excluded components
+
+This source subtree does not include ExecuTorch source, native runners, model
+weights, exported programs, tokenizers, voice styles, recordings, generated
+output, or dependency source. Those components retain their independent
+licenses and notices as documented in `THIRD_PARTY_NOTICES.md` and `LICENSES/`.
+
+Muse Glimmer, ExecuTorch, LiveKit, and other names may be trademarks of their
+respective owners. The BSD-3-Clause license does not grant trademark rights.
diff --git a/muse_glimmer/macos/README.md b/muse_glimmer/macos/README.md
new file mode 100644
index 0000000000..35e367b7d1
--- /dev/null
+++ b/muse_glimmer/macos/README.md
@@ -0,0 +1,113 @@
+# Muse Glimmer Voice Agent
+
+A fully local voice agent for macOS on Apple silicon. The browser captures the
+microphone, loopback-only LiveKit carries audio, and local ExecuTorch runtimes
+perform Parakeet speech recognition, Muse Glimmer generation, and Supertonic
+speech synthesis.
+
+```text
+browser microphone
+ -> 127.0.0.1 LiveKit
+ -> Parakeet ASR
+ -> Muse Glimmer LLM
+ -> Supertonic TTS
+ -> browser speaker
+```
+
+No LiveKit Cloud account or cloud inference service is used.
+
+## Status
+
+The public source repository is ready for development, but the native
+compatibility lock is intentionally gated. A release pin requires one
+ExecuTorch commit containing both bounded MuseGlimmer worker cancellation and
+persistent Supertonic JSONL mode. See `docs/upstream-pins.md`.
+
+## Supported platform
+
+- macOS on Apple silicon
+- Python 3.13
+- Node.js 22
+- A compatible Xcode/CMake toolchain
+- LiveKit Server 1.x
+
+Other platforms are not part of the first milestone.
+
+## First-time setup
+
+Run application commands from the subtree root:
+
+```bash
+cd muse_glimmer/macos
+```
+
+Review the independent model and runtime licenses before providing artifacts.
+Models and native binaries are stored only under ignored `.local/` paths. Source
+checks and package builds are available now:
+
+```bash
+make check
+make test
+```
+
+Runtime provisioning remains intentionally unavailable while the compatibility
+commit is null:
+
+```bash
+make bootstrap
+make prepare-artifacts
+```
+
+Once the release gate is resolved, `make bootstrap` validates the locked toolchain
+and installs source dependencies. `make prepare-artifacts` validates the single
+pinned ExecuTorch checkout and every model/native artifact, then writes an ignored
+compatibility receipt. Neither operation runs during normal startup.
+
+## Daily development
+
+```bash
+make dev
+make status
+make logs
+make restart
+make down
+```
+
+`make dev up` is also supported and starts the stack exactly once. Once
+artifacts have been prepared, startup requires no external network access.
+
+The UI opens at `http://127.0.0.1:5173`.
+
+## Local security boundary
+
+- LiveKit signaling: `127.0.0.1:7880`
+- LiveKit media: `127.0.0.1:7882/udp`
+- Token service: `127.0.0.1:8787`
+- Browser UI: `127.0.0.1:5173`
+- MuseGlimmer server: backend-only `127.0.0.1:8000`
+- Short-lived participant tokens grant microphone publication only.
+- Runtime LiveKit credentials are generated locally per stack run.
+- Browser code never receives model identifiers, artifact paths, the LLM
+ endpoint, native worker details, or server credentials.
+
+See `docs/security-model.md` for the local-process trust model.
+
+## Development checks
+
+From `muse_glimmer/macos`:
+
+```bash
+make check
+make test
+make publication-check
+```
+
+The publication check rejects secrets, models, native binaries, recordings,
+generated output, nested repositories, absolute workstation paths, internal
+URLs, and AGPL avatar dependencies.
+
+## License
+
+Product-owned source is BSD-3-Clause. Models, exported programs, native
+binaries, fonts, and third-party packages retain their independent licenses.
+See `LICENSE`, `PROVENANCE.md`, `THIRD_PARTY_NOTICES.md`, and `LICENSES/`.
diff --git a/muse_glimmer/macos/SECURITY.md b/muse_glimmer/macos/SECURITY.md
new file mode 100644
index 0000000000..6ed66b5b21
--- /dev/null
+++ b/muse_glimmer/macos/SECURITY.md
@@ -0,0 +1,23 @@
+# Security Policy
+
+## Supported configuration
+
+The initial supported configuration is macOS on Apple silicon with every
+service bound to loopback. Do not expose the web, token, LiveKit, LLM, or worker
+ports to a LAN or the internet.
+
+## Reporting
+
+Do not include credentials, transcripts, audio, model paths, or runtime logs in
+a public issue. Use the repository host's private security advisory mechanism.
+
+## Local trust boundary
+
+A process running as the same operating-system user can reach loopback services
+and read files that user can access. The token service is not an authentication
+boundary against local malware. Runtime credentials are ephemeral, mode 0600,
+and removed by normal shutdown.
+
+The browser receives only a short-lived participant token and the fixed local
+LiveKit URL. It must never receive model identifiers, artifact variants, local
+paths, the LLM endpoint, native worker details, or cloud metadata.
diff --git a/muse_glimmer/macos/THIRD_PARTY_NOTICES.md b/muse_glimmer/macos/THIRD_PARTY_NOTICES.md
new file mode 100644
index 0000000000..1e47ad0e83
--- /dev/null
+++ b/muse_glimmer/macos/THIRD_PARTY_NOTICES.md
@@ -0,0 +1,31 @@
+# Third-Party Notices
+
+Product-owned source in this subtree is licensed under BSD-3-Clause. It
+integrates with software and assets governed by independent terms; BSD-3-Clause
+does not relicense models, exported programs, native binaries, fonts, or
+third-party packages.
+
+## Runtime dependencies
+
+- **ExecuTorch**: BSD-3-Clause. Source and runtime artifacts are provisioned
+ separately. Release builds require the immutable revision recorded in
+ `config/dependencies/compatibility.lock.json`; that revision is intentionally
+ unset while upstream integration remains gated.
+- **LiveKit Agents and LiveKit Server**: Apache-2.0. The temporary
+ `packages/livekit-plugins-executorch` package records its API baseline and
+ product-owned source provenance in `PROVENANCE.md`.
+- **LiveKit Local Inference**: installed transitively by LiveKit Agents and
+ distributed under `Apache-2.0 AND LicenseRef-LiveKit-Model`. The model-license
+ terms restrict LiveKit model use to the LiveKit Agents framework; see
+ `LICENSES/LIVEKIT-MODEL-LICENSE.txt`. This repository does not redistribute
+ those model assets.
+- **Supertonic**: consult the upstream source and model licenses before
+ downloading or exporting assets. Assets are never committed here.
+- **Muse Glimmer and Parakeet models**: use is governed by their respective
+ model licenses. Model weights and exported programs are never committed here.
+- **Inter**: Copyright 2016 The Inter Project Authors
+ (https://github.com/rsms/inter), SIL Open Font License 1.1, consumed through
+ `@fontsource/inter`.
+
+A release must run the repository's license and publication checks and update
+this file when any dependency, model, or asset changes.
diff --git a/muse_glimmer/macos/apps/muse-glimmer-server/README.md b/muse_glimmer/macos/apps/muse-glimmer-server/README.md
new file mode 100644
index 0000000000..38d763e8fb
--- /dev/null
+++ b/muse_glimmer/macos/apps/muse-glimmer-server/README.md
@@ -0,0 +1,9 @@
+# MuseGlimmer server launcher
+
+`launch.py` validates the prepared artifact receipt and directly executes the
+OpenAI-compatible server from the single pinned ExecuTorch checkout. It is not
+a proxy and does not reimplement the upstream API.
+
+The launcher forces `127.0.0.1:8000`, a 131072-token context limit, DFlash
+artifact mode, no tool parser, and the prepared cancellation-capable native
+worker.
diff --git a/muse_glimmer/macos/apps/muse-glimmer-server/launch.py b/muse_glimmer/macos/apps/muse-glimmer-server/launch.py
new file mode 100644
index 0000000000..5a38aaf996
--- /dev/null
+++ b/muse_glimmer/macos/apps/muse-glimmer-server/launch.py
@@ -0,0 +1,64 @@
+from __future__ import annotations
+
+import os
+import sys
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[2]
+sys.path.insert(0, str(ROOT))
+
+from scripts.repository import load_valid_receipt, relative_local_path # noqa: E402
+
+MODEL_ID = "muse-glimmer-k-quant-17G-128K-text-dflash-metal"
+
+
+def main() -> None:
+ receipt = load_valid_receipt()
+ artifacts = receipt["artifacts"]
+ checkout = Path(receipt["executorch_checkout"]).resolve()
+ worker = relative_local_path(artifacts["muse_glimmer_worker"]["path"])
+ model = relative_local_path(artifacts["muse_glimmer_model"]["path"])
+ tokenizer = relative_local_path(artifacts["muse_glimmer_tokenizer"]["path"])
+ tokenizer_root = tokenizer.parent
+
+ pythonpath = os.pathsep.join(
+ value for value in (str(checkout / "src"), os.environ.get("PYTHONPATH", "")) if value
+ )
+ environment = {
+ "HOME": os.environ.get("HOME", ""),
+ "LANG": os.environ.get("LANG", "C.UTF-8"),
+ "PATH": os.environ.get("PATH", "/usr/bin:/bin"),
+ "PYTHONPATH": pythonpath,
+ "TMPDIR": os.environ.get("TMPDIR", "/tmp"),
+ }
+ command = [
+ sys.executable,
+ "-m",
+ "executorch.examples.models.muse_glimmer.serving.serve",
+ "--model-path",
+ str(model),
+ "--tokenizer-path",
+ str(tokenizer),
+ "--hf-tokenizer",
+ str(tokenizer_root),
+ "--worker-bin",
+ str(worker),
+ "--model-id",
+ MODEL_ID,
+ "--artifact-mode",
+ "dflash",
+ "--max-context",
+ "131072",
+ "--tool-parser",
+ "none",
+ "--host",
+ "127.0.0.1",
+ "--port",
+ "8000",
+ ]
+ os.chdir(checkout)
+ os.execve(sys.executable, command, environment)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/muse_glimmer/macos/apps/token-service/LICENSE b/muse_glimmer/macos/apps/token-service/LICENSE
new file mode 100644
index 0000000000..5651f75604
--- /dev/null
+++ b/muse_glimmer/macos/apps/token-service/LICENSE
@@ -0,0 +1,30 @@
+BSD License
+
+For "ExecuTorch" software
+
+Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name Meta nor the names of its contributors may be used to
+ endorse or promote products derived from this software without specific
+ prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/muse_glimmer/macos/apps/token-service/pyproject.toml b/muse_glimmer/macos/apps/token-service/pyproject.toml
new file mode 100644
index 0000000000..4957a6b0ef
--- /dev/null
+++ b/muse_glimmer/macos/apps/token-service/pyproject.toml
@@ -0,0 +1,38 @@
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[project]
+name = "muse-glimmer-token-service"
+version = "0.1.0"
+description = "Local-only LiveKit token issuer for the Muse Glimmer voice UI"
+requires-python = ">=3.13,<3.14"
+license = "BSD-3-Clause"
+license-files = ["LICENSE"]
+dependencies = [
+ "fastapi>=0.116,<1",
+ "livekit-api>=1.2,<2",
+ "pydantic-settings>=2.10,<3",
+ "uvicorn[standard]>=0.35,<1",
+]
+
+[dependency-groups]
+dev = [
+ "httpx>=0.28,<1",
+ "PyJWT>=2.10,<3",
+ "pytest>=8.4,<9",
+ "ruff>=0.12,<1",
+]
+
+[project.scripts]
+muse-glimmer-token-service = "muse_glimmer_token_service.__main__:main"
+
+[tool.hatch.build]
+include = [
+ "/LICENSE",
+ "/src",
+ "/tests",
+]
+
+[tool.hatch.build.targets.wheel]
+packages = ["src/muse_glimmer_token_service"]
diff --git a/muse_glimmer/macos/apps/token-service/src/muse_glimmer_token_service/__init__.py b/muse_glimmer/macos/apps/token-service/src/muse_glimmer_token_service/__init__.py
new file mode 100644
index 0000000000..2aed1ad4a3
--- /dev/null
+++ b/muse_glimmer/macos/apps/token-service/src/muse_glimmer_token_service/__init__.py
@@ -0,0 +1,5 @@
+"""Hardened local token issuer for Muse Glimmer."""
+
+from .app import create_app
+
+__all__ = ["create_app"]
diff --git a/muse_glimmer/macos/apps/token-service/src/muse_glimmer_token_service/__main__.py b/muse_glimmer/macos/apps/token-service/src/muse_glimmer_token_service/__main__.py
new file mode 100644
index 0000000000..d6510f52df
--- /dev/null
+++ b/muse_glimmer/macos/apps/token-service/src/muse_glimmer_token_service/__main__.py
@@ -0,0 +1,22 @@
+from __future__ import annotations
+
+import uvicorn
+
+HOST = "127.0.0.1"
+PORT = 8787
+APP_FACTORY = "muse_glimmer_token_service.app:create_app"
+
+
+def main() -> None:
+ uvicorn.run(
+ APP_FACTORY,
+ factory=True,
+ host=HOST,
+ port=PORT,
+ proxy_headers=False,
+ server_header=False,
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/muse_glimmer/macos/apps/token-service/src/muse_glimmer_token_service/app.py b/muse_glimmer/macos/apps/token-service/src/muse_glimmer_token_service/app.py
new file mode 100644
index 0000000000..d0feeecb7f
--- /dev/null
+++ b/muse_glimmer/macos/apps/token-service/src/muse_glimmer_token_service/app.py
@@ -0,0 +1,110 @@
+from __future__ import annotations
+
+from ipaddress import ip_address
+from typing import Any
+
+from fastapi import FastAPI, Header, HTTPException
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.middleware.trustedhost import TrustedHostMiddleware
+from pydantic import BaseModel, ConfigDict
+from starlette.responses import JSONResponse
+from starlette.types import ASGIApp, Message, Receive, Scope, Send
+
+from .config import Settings, get_settings
+from .tokens import issue_connection
+
+
+class SecurityHeadersMiddleware:
+ def __init__(self, app: ASGIApp) -> None:
+ self.app = app
+
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
+ async def send_with_security_headers(message: Message) -> None:
+ if message["type"] == "http.response.start":
+ headers = list(message.get("headers", []))
+ headers.extend(
+ [
+ (b"cache-control", b"no-store"),
+ (b"pragma", b"no-cache"),
+ (b"referrer-policy", b"no-referrer"),
+ (b"x-content-type-options", b"nosniff"),
+ ]
+ )
+ message["headers"] = headers
+ await send(message)
+
+ await self.app(scope, receive, send_with_security_headers)
+
+
+class LoopbackClientMiddleware:
+ def __init__(self, app: ASGIApp) -> None:
+ self.app = app
+
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
+ if scope["type"] == "http" and not _is_loopback_client(scope.get("client")):
+ response = JSONResponse({"detail": "Loopback clients only"}, status_code=403)
+ await response(scope, receive, send)
+ return
+ await self.app(scope, receive, send)
+
+
+def _is_loopback_client(client: Any) -> bool:
+ if not isinstance(client, tuple) or not client:
+ return False
+ try:
+ return ip_address(client[0]).is_loopback
+ except ValueError:
+ return False
+
+
+def _to_camel(value: str) -> str:
+ head, *tail = value.split("_")
+ return head + "".join(part.title() for part in tail)
+
+
+class ConnectionResponse(BaseModel):
+ model_config = ConfigDict(alias_generator=_to_camel, populate_by_name=True)
+
+ server_url: str
+ participant_token: str
+ room_name: str
+ participant_identity: str
+
+
+def create_app(settings: Settings | None = None) -> FastAPI:
+ resolved_settings = settings or get_settings()
+ app = FastAPI(
+ title="Muse Glimmer local token service",
+ version="0.1.0",
+ docs_url=None,
+ redoc_url=None,
+ openapi_url=None,
+ )
+ app.add_middleware(TrustedHostMiddleware, allowed_hosts=["127.0.0.1"])
+ app.add_middleware(
+ CORSMiddleware,
+ allow_origins=list(resolved_settings.allowed_web_origins),
+ allow_credentials=False,
+ allow_methods=["POST"],
+ allow_headers=["Accept"],
+ )
+ app.add_middleware(LoopbackClientMiddleware)
+ app.add_middleware(SecurityHeadersMiddleware)
+
+ @app.get("/healthz")
+ async def healthz() -> dict[str, str]:
+ return {"status": "ok"}
+
+ @app.post("/api/token", response_model=ConnectionResponse, response_model_by_alias=True)
+ async def token(origin: str | None = Header(default=None)) -> ConnectionResponse:
+ if origin is not None and origin not in resolved_settings.allowed_web_origins:
+ raise HTTPException(status_code=403, detail="Origin is not allowed")
+ details = issue_connection(resolved_settings)
+ return ConnectionResponse(
+ server_url=details.server_url,
+ participant_token=details.participant_token,
+ room_name=details.room_name,
+ participant_identity=details.participant_identity,
+ )
+
+ return app
diff --git a/muse_glimmer/macos/apps/token-service/src/muse_glimmer_token_service/config.py b/muse_glimmer/macos/apps/token-service/src/muse_glimmer_token_service/config.py
new file mode 100644
index 0000000000..9ee0e851a6
--- /dev/null
+++ b/muse_glimmer/macos/apps/token-service/src/muse_glimmer_token_service/config.py
@@ -0,0 +1,56 @@
+from __future__ import annotations
+
+from typing import ClassVar, Final
+
+from pydantic import Field, SecretStr, field_validator
+from pydantic_settings import BaseSettings, SettingsConfigDict
+
+LIVEKIT_SERVER_URL: Final = "ws://127.0.0.1:7880"
+ALLOWED_WEB_ORIGINS: Final = ("http://127.0.0.1:5173",)
+
+
+class Settings(BaseSettings):
+ """Environment-only credentials and bounded token lifetime."""
+
+ model_config = SettingsConfigDict(
+ env_file=None,
+ case_sensitive=True,
+ extra="ignore",
+ frozen=True,
+ )
+
+ livekit_api_key: SecretStr = Field(validation_alias="LIVEKIT_API_KEY", min_length=1)
+ livekit_api_secret: SecretStr = Field(validation_alias="LIVEKIT_API_SECRET", min_length=1)
+ livekit_url: str = Field(
+ default=LIVEKIT_SERVER_URL,
+ validation_alias="LIVEKIT_URL",
+ )
+ token_ttl_seconds: int = Field(
+ default=600,
+ validation_alias="TOKEN_TTL_SECONDS",
+ ge=60,
+ le=3600,
+ )
+ allowed_web_origins: ClassVar[tuple[str, ...]] = ALLOWED_WEB_ORIGINS
+
+ @field_validator("livekit_api_key", "livekit_api_secret", mode="before")
+ @classmethod
+ def strip_credential(cls, value: object) -> object:
+ if isinstance(value, SecretStr):
+ value = value.get_secret_value()
+ if isinstance(value, str):
+ value = value.strip()
+ if not value:
+ raise ValueError("LiveKit credentials must be non-empty")
+ return value
+
+ @field_validator("livekit_url")
+ @classmethod
+ def require_local_livekit_url(cls, value: str) -> str:
+ if value != LIVEKIT_SERVER_URL:
+ raise ValueError(f"LIVEKIT_URL must be exactly {LIVEKIT_SERVER_URL}")
+ return value
+
+
+def get_settings() -> Settings:
+ return Settings() # type: ignore[call-arg]
diff --git a/muse_glimmer/macos/apps/token-service/src/muse_glimmer_token_service/tokens.py b/muse_glimmer/macos/apps/token-service/src/muse_glimmer_token_service/tokens.py
new file mode 100644
index 0000000000..910c2eef40
--- /dev/null
+++ b/muse_glimmer/macos/apps/token-service/src/muse_glimmer_token_service/tokens.py
@@ -0,0 +1,55 @@
+from __future__ import annotations
+
+import uuid
+from dataclasses import dataclass
+from datetime import timedelta
+from typing import Final
+
+from livekit import api
+
+from .config import Settings
+
+AGENT_NAME: Final = "assistant"
+
+
+@dataclass(frozen=True, slots=True)
+class ConnectionDetails:
+ server_url: str
+ participant_token: str
+ room_name: str
+ participant_identity: str
+
+
+def issue_connection(settings: Settings) -> ConnectionDetails:
+ room_name = f"r_{uuid.uuid4().hex}"
+ participant_identity = f"p_{uuid.uuid4().hex}"
+ grants = api.VideoGrants(
+ room_join=True,
+ room=room_name,
+ can_publish=True,
+ can_subscribe=True,
+ can_publish_data=False,
+ can_publish_sources=["microphone"],
+ )
+ room_config = api.RoomConfiguration(
+ agents=[api.RoomAgentDispatch(agent_name=AGENT_NAME)],
+ )
+ participant_token = (
+ api.AccessToken(
+ settings.livekit_api_key.get_secret_value(),
+ settings.livekit_api_secret.get_secret_value(),
+ )
+ .with_identity(participant_identity)
+ .with_name("Local voice participant")
+ .with_ttl(timedelta(seconds=settings.token_ttl_seconds))
+ .with_grants(grants)
+ .with_room_config(room_config)
+ .to_jwt()
+ )
+
+ return ConnectionDetails(
+ server_url=settings.livekit_url,
+ participant_token=participant_token,
+ room_name=room_name,
+ participant_identity=participant_identity,
+ )
diff --git a/muse_glimmer/macos/apps/token-service/tests/test_app.py b/muse_glimmer/macos/apps/token-service/tests/test_app.py
new file mode 100644
index 0000000000..4019b158b3
--- /dev/null
+++ b/muse_glimmer/macos/apps/token-service/tests/test_app.py
@@ -0,0 +1,186 @@
+from __future__ import annotations
+
+import time
+
+import jwt
+import pytest
+from fastapi.testclient import TestClient
+from muse_glimmer_token_service.app import create_app
+from muse_glimmer_token_service.config import ALLOWED_WEB_ORIGINS, Settings
+from muse_glimmer_token_service.tokens import AGENT_NAME
+from pydantic import SecretStr
+
+_API_KEY = "test-key"
+_SECRET = "test-secret-with-at-least-thirty-two-characters"
+
+
+def _settings() -> Settings:
+ return Settings(
+ LIVEKIT_API_KEY=SecretStr(_API_KEY),
+ LIVEKIT_API_SECRET=SecretStr(_SECRET),
+ TOKEN_TTL_SECONDS=600,
+ )
+
+
+def _client(*, host: str = "127.0.0.1", client_host: str = "127.0.0.1") -> TestClient:
+ return TestClient(
+ create_app(_settings()),
+ base_url=f"http://{host}",
+ client=(client_host, 50000),
+ )
+
+
+def test_token_has_restricted_grants_fixed_dispatch_and_exact_response() -> None:
+ with _client() as client:
+ response = client.post("/api/token", headers={"Origin": ALLOWED_WEB_ORIGINS[0]})
+
+ assert response.status_code == 200
+ assert response.headers["cache-control"] == "no-store"
+ assert response.headers["pragma"] == "no-cache"
+ assert response.headers["x-content-type-options"] == "nosniff"
+ assert response.headers["access-control-allow-origin"] == ALLOWED_WEB_ORIGINS[0]
+ body = response.json()
+ assert set(body) == {
+ "participantIdentity",
+ "participantToken",
+ "roomName",
+ "serverUrl",
+ }
+ assert body["serverUrl"] == "ws://127.0.0.1:7880"
+ assert body["roomName"].startswith("r_")
+ assert body["participantIdentity"].startswith("p_")
+
+ assert jwt.get_unverified_header(body["participantToken"])["alg"] == "HS256"
+ claims = jwt.decode(
+ body["participantToken"],
+ _SECRET,
+ algorithms=["HS256"],
+ options={"verify_aud": False},
+ )
+ assert claims["iss"] == _API_KEY
+ assert claims["sub"] == body["participantIdentity"]
+ assert 590 <= claims["exp"] - int(time.time()) <= 600
+ assert claims["video"] == {
+ "canPublish": True,
+ "canPublishData": False,
+ "canPublishSources": ["microphone"],
+ "canSubscribe": True,
+ "room": body["roomName"],
+ "roomJoin": True,
+ }
+ assert claims["roomConfig"] == {"agents": [{"agentName": AGENT_NAME}]}
+ assert "admin" not in claims
+ assert "recorder" not in claims
+
+
+def test_each_request_gets_random_room_and_participant() -> None:
+ with _client() as client:
+ first = client.post("/api/token", headers={"Origin": ALLOWED_WEB_ORIGINS[0]}).json()
+ second = client.post("/api/token", headers={"Origin": ALLOWED_WEB_ORIGINS[0]}).json()
+
+ assert first["roomName"] != second["roomName"]
+ assert first["participantIdentity"] != second["participantIdentity"]
+
+
+def test_client_cannot_choose_room_or_agent() -> None:
+ with _client() as client:
+ response = client.post(
+ "/api/token",
+ headers={"Origin": ALLOWED_WEB_ORIGINS[0]},
+ json={"roomName": "attacker-room", "agentName": "other-agent"},
+ )
+
+ assert response.status_code == 200
+ assert response.json()["roomName"] != "attacker-room"
+
+
+@pytest.mark.parametrize("origin", ALLOWED_WEB_ORIGINS)
+def test_exact_web_origins_are_allowed(origin: str) -> None:
+ with _client() as client:
+ response = client.post("/api/token", headers={"Origin": origin})
+
+ assert response.status_code == 200
+ assert response.headers["access-control-allow-origin"] == origin
+
+
+@pytest.mark.parametrize(
+ "origin",
+ [
+ "http://localhost:5173",
+ "http://127.0.0.1:5173/",
+ "http://127.0.0.1:5174",
+ "https://127.0.0.1:5173",
+ "https://unapproved.example",
+ ],
+)
+def test_inexact_origin_is_rejected(origin: str) -> None:
+ headers = {"Origin": origin}
+ with _client() as client:
+ response = client.post("/api/token", headers=headers)
+
+ assert response.status_code == 403
+ assert "access-control-allow-origin" not in response.headers
+ assert _API_KEY not in response.text
+ assert _SECRET not in response.text
+
+
+def test_missing_origin_is_allowed_for_loopback_native_clients() -> None:
+ with _client() as client:
+ response = client.post("/api/token")
+
+ assert response.status_code == 200
+ assert "access-control-allow-origin" not in response.headers
+
+
+def test_cors_preflight_allows_only_expected_origin_and_method() -> None:
+ with _client() as client:
+ allowed = client.options(
+ "/api/token",
+ headers={
+ "Origin": ALLOWED_WEB_ORIGINS[0],
+ "Access-Control-Request-Method": "POST",
+ "Access-Control-Request-Headers": "Accept",
+ },
+ )
+ disallowed = client.options(
+ "/api/token",
+ headers={
+ "Origin": "http://localhost:5173",
+ "Access-Control-Request-Method": "POST",
+ },
+ )
+
+ assert allowed.status_code == 200
+ assert allowed.headers["access-control-allow-origin"] == ALLOWED_WEB_ORIGINS[0]
+ assert disallowed.status_code == 400
+ assert "access-control-allow-origin" not in disallowed.headers
+
+
+def test_untrusted_host_is_rejected() -> None:
+ with _client(host="attacker.example") as client:
+ response = client.get("/healthz")
+
+ assert response.status_code == 400
+ assert response.headers["cache-control"] == "no-store"
+
+
+def test_non_loopback_client_is_rejected() -> None:
+ with _client(client_host="203.0.113.10") as client:
+ response = client.get("/healthz")
+
+ assert response.status_code == 403
+ assert response.json() == {"detail": "Loopback clients only"}
+ assert response.headers["cache-control"] == "no-store"
+
+
+def test_health_and_disabled_documentation_expose_no_configuration() -> None:
+ with _client() as client:
+ health = client.get("/healthz")
+ docs = [client.get(path) for path in ("/docs", "/redoc", "/openapi.json")]
+
+ assert health.status_code == 200
+ assert health.json() == {"status": "ok"}
+ assert health.headers["cache-control"] == "no-store"
+ assert _API_KEY not in health.text
+ assert _SECRET not in health.text
+ assert all(response.status_code == 404 for response in docs)
diff --git a/muse_glimmer/macos/apps/token-service/tests/test_cli.py b/muse_glimmer/macos/apps/token-service/tests/test_cli.py
new file mode 100644
index 0000000000..69dfc986c4
--- /dev/null
+++ b/muse_glimmer/macos/apps/token-service/tests/test_cli.py
@@ -0,0 +1,28 @@
+from __future__ import annotations
+
+from typing import Any
+
+from muse_glimmer_token_service import __main__
+
+
+def test_launcher_uses_fixed_loopback_address(monkeypatch: Any) -> None:
+ invocation: dict[str, Any] = {}
+
+ def fake_run(app: str, **kwargs: Any) -> None:
+ invocation["app"] = app
+ invocation.update(kwargs)
+
+ monkeypatch.setattr(__main__.uvicorn, "run", fake_run)
+ monkeypatch.setenv("HOST", "0.0.0.0")
+ monkeypatch.setenv("PORT", "9999")
+
+ __main__.main()
+
+ assert invocation == {
+ "app": "muse_glimmer_token_service.app:create_app",
+ "factory": True,
+ "host": "127.0.0.1",
+ "port": 8787,
+ "proxy_headers": False,
+ "server_header": False,
+ }
diff --git a/muse_glimmer/macos/apps/token-service/tests/test_config.py b/muse_glimmer/macos/apps/token-service/tests/test_config.py
new file mode 100644
index 0000000000..57f74df32c
--- /dev/null
+++ b/muse_glimmer/macos/apps/token-service/tests/test_config.py
@@ -0,0 +1,101 @@
+from __future__ import annotations
+
+from pathlib import Path
+
+import pytest
+from muse_glimmer_token_service.config import ALLOWED_WEB_ORIGINS, Settings
+from pydantic import SecretStr, ValidationError
+
+_SECRET = "test-secret-with-at-least-thirty-two-characters"
+
+
+def _values(**overrides: object) -> dict[str, object]:
+ values: dict[str, object] = {
+ "LIVEKIT_API_KEY": "test-key",
+ "LIVEKIT_API_SECRET": _SECRET,
+ }
+ values.update(overrides)
+ return values
+
+
+def test_settings_read_credentials_from_environment(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setenv("LIVEKIT_API_KEY", " environment-key ")
+ monkeypatch.setenv("LIVEKIT_API_SECRET", f" {_SECRET} ")
+
+ settings = Settings() # type: ignore[call-arg]
+
+ assert settings.livekit_api_key.get_secret_value() == "environment-key"
+ assert settings.livekit_api_secret.get_secret_value() == _SECRET
+ assert settings.livekit_url == "ws://127.0.0.1:7880"
+ assert settings.allowed_web_origins == ("http://127.0.0.1:5173",)
+
+
+def test_dotenv_file_is_never_loaded(
+ monkeypatch: pytest.MonkeyPatch,
+ tmp_path: Path,
+) -> None:
+ monkeypatch.chdir(tmp_path)
+ monkeypatch.delenv("LIVEKIT_API_KEY", raising=False)
+ monkeypatch.delenv("LIVEKIT_API_SECRET", raising=False)
+ (tmp_path / ".env").write_text(
+ f"LIVEKIT_API_KEY=dotenv-key\nLIVEKIT_API_SECRET={_SECRET}\n",
+ encoding="utf-8",
+ )
+
+ with pytest.raises(ValidationError):
+ Settings() # type: ignore[call-arg]
+
+
+@pytest.mark.parametrize("field", ["LIVEKIT_API_KEY", "LIVEKIT_API_SECRET"])
+def test_blank_credentials_are_rejected(field: str) -> None:
+ with pytest.raises(ValidationError):
+ Settings(**_values(**{field: " "})) # type: ignore[arg-type]
+
+
+def test_credentials_are_redacted_from_settings_representation() -> None:
+ settings = Settings(
+ **_values(
+ LIVEKIT_API_KEY=SecretStr("test-key"),
+ LIVEKIT_API_SECRET=SecretStr(_SECRET),
+ )
+ )
+
+ assert "test-key" not in repr(settings)
+ assert _SECRET not in repr(settings)
+
+
+@pytest.mark.parametrize(
+ "url",
+ [
+ "ws://localhost:7880",
+ "ws://127.0.0.1:7880/",
+ "ws://127.0.0.1:7880/path",
+ "ws://127.0.0.1:7880?query=yes",
+ "wss://127.0.0.1:7880",
+ "wss://example.livekit.cloud",
+ ],
+)
+def test_livekit_url_variations_are_rejected(url: str) -> None:
+ with pytest.raises(ValidationError, match="must be exactly"):
+ Settings(**_values(LIVEKIT_URL=url))
+
+
+@pytest.mark.parametrize("ttl", [60, 3600])
+def test_token_ttl_boundaries_are_allowed(ttl: int) -> None:
+ assert Settings(**_values(TOKEN_TTL_SECONDS=ttl)).token_ttl_seconds == ttl
+
+
+@pytest.mark.parametrize("ttl", [59, 3601])
+def test_token_ttl_outside_bounds_is_rejected(ttl: int) -> None:
+ with pytest.raises(ValidationError):
+ Settings(**_values(TOKEN_TTL_SECONDS=ttl))
+
+
+def test_allowed_origins_cannot_be_overridden_by_environment(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ monkeypatch.setenv("ALLOWED_ORIGINS", "https://attacker.example")
+
+ settings = Settings(**_values())
+
+ assert settings.allowed_web_origins == ALLOWED_WEB_ORIGINS
diff --git a/muse_glimmer/macos/apps/web/.gitignore b/muse_glimmer/macos/apps/web/.gitignore
new file mode 100644
index 0000000000..c4e5510013
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/.gitignore
@@ -0,0 +1,4 @@
+dist/
+node_modules/
+*.tsbuildinfo
+.DS_Store
diff --git a/muse_glimmer/macos/apps/web/README.md b/muse_glimmer/macos/apps/web/README.md
new file mode 100644
index 0000000000..d8b6da03be
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/README.md
@@ -0,0 +1,41 @@
+# Muse Glimmer local voice web app
+
+A redistributable React interface for a private Muse Glimmer voice conversation. The browser requests a short-lived token from the fixed local endpoint `http://127.0.0.1:8787/api/token` and accepts media connections only to `ws://127.0.0.1:7880`.
+
+## Requirements
+
+- Node.js 22.12 or newer
+- A local token service on `127.0.0.1:8787`
+- A local LiveKit server on `127.0.0.1:7880`
+- A LiveKit voice agent registered with the public name `assistant`
+
+## Development
+
+```bash
+npm ci
+npm run dev
+```
+
+Open `http://127.0.0.1:5173`. The development server binds only to loopback.
+
+## Production
+
+```bash
+npm ci
+npm run build
+npm run serve
+```
+
+The production server binds only to `127.0.0.1` on port `5173` by default. The supervisor uses the constrained command `npm run serve -- --host 127.0.0.1 --port 5173`; all other host values are rejected. The server delivers the built application with a Content Security Policy and defensive browser headers. Production source maps are disabled.
+
+## Quality checks
+
+```bash
+npm run typecheck
+npm test
+npm run lint
+npm run format:check
+npm run build
+```
+
+The package contains no token secrets, generated distribution files, third-party avatar definitions, or image branding assets.
diff --git a/muse_glimmer/macos/apps/web/eslint.config.js b/muse_glimmer/macos/apps/web/eslint.config.js
new file mode 100644
index 0000000000..f2f5a3411e
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/eslint.config.js
@@ -0,0 +1,43 @@
+import js from "@eslint/js";
+import reactHooks from "eslint-plugin-react-hooks";
+import reactRefresh from "eslint-plugin-react-refresh";
+import globals from "globals";
+import tseslint from "typescript-eslint";
+
+export default tseslint.config(
+ { ignores: ["dist", "node_modules"] },
+ {
+ files: ["*.{js,mjs}"],
+ ...js.configs.recommended,
+ languageOptions: {
+ ecmaVersion: 2023,
+ globals: globals.node,
+ },
+ },
+ {
+ files: ["**/*.{ts,tsx}"],
+ extends: [
+ js.configs.recommended,
+ ...tseslint.configs.recommendedTypeChecked,
+ ],
+ languageOptions: {
+ ecmaVersion: 2022,
+ globals: globals.browser,
+ parserOptions: {
+ project: ["./tsconfig.app.json", "./tsconfig.node.json"],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ },
+ plugins: {
+ "react-hooks": reactHooks,
+ "react-refresh": reactRefresh,
+ },
+ rules: {
+ ...reactHooks.configs.recommended.rules,
+ "react-refresh/only-export-components": [
+ "warn",
+ { allowConstantExport: true },
+ ],
+ },
+ },
+);
diff --git a/muse_glimmer/macos/apps/web/index.html b/muse_glimmer/macos/apps/web/index.html
new file mode 100644
index 0000000000..fc278e2aa0
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+ Muse Glimmer | Local voice conversation
+
+
+
+
+
+
diff --git a/muse_glimmer/macos/apps/web/package-lock.json b/muse_glimmer/macos/apps/web/package-lock.json
new file mode 100644
index 0000000000..6473367c82
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/package-lock.json
@@ -0,0 +1,4563 @@
+{
+ "name": "@muse-glimmer/web",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "@muse-glimmer/web",
+ "version": "0.1.0",
+ "dependencies": {
+ "@fontsource/inter": "5.3.0",
+ "@livekit/components-react": "2.9.24",
+ "livekit-client": "2.22.0",
+ "react": "19.2.4",
+ "react-dom": "19.2.4"
+ },
+ "devDependencies": {
+ "@eslint/js": "9.34.0",
+ "@testing-library/jest-dom": "6.8.0",
+ "@testing-library/react": "16.3.0",
+ "@types/node": "24.3.0",
+ "@types/react": "19.1.12",
+ "@types/react-dom": "19.1.9",
+ "@vitejs/plugin-react": "6.0.2",
+ "eslint": "9.34.0",
+ "eslint-plugin-react-hooks": "7.0.0",
+ "eslint-plugin-react-refresh": "0.4.20",
+ "globals": "16.3.0",
+ "jsdom": "26.1.0",
+ "prettier": "3.6.2",
+ "typescript": "5.9.3",
+ "typescript-eslint": "8.41.0",
+ "vite": "8.2.2",
+ "vitest": "4.1.11"
+ },
+ "engines": {
+ "node": ">=22.12.0"
+ }
+ },
+ "node_modules/@adobe/css-tools": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.5.0.tgz",
+ "integrity": "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@asamuzakjp/css-color": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",
+ "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/css-calc": "^2.1.3",
+ "@csstools/css-color-parser": "^3.0.9",
+ "@csstools/css-parser-algorithms": "^3.0.4",
+ "@csstools/css-tokenizer": "^3.0.3",
+ "lru-cache": "^10.4.3"
+ }
+ },
+ "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
+ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
+ "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
+ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-compilation-targets": "^7.29.7",
+ "@babel/helper-module-transforms": "^7.29.7",
+ "@babel/helpers": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz",
+ "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.8",
+ "@babel/types": "^7.29.8",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
+ "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.29.7",
+ "@babel/helper-validator-option": "^7.29.7",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
+ "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
+ "dev": true,
+ "license": "MIT",
+ "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",
+ "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
+ "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.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",
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
+ "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
+ "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz",
+ "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.8"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz",
+ "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
+ "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz",
+ "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.8",
+ "@babel/helper-globals": "^7.29.7",
+ "@babel/parser": "^7.29.8",
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.8",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
+ "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@bufbuild/protobuf": {
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.1.tgz",
+ "integrity": "sha512-wJ8ReQbHxsAfXhrf9ixl0aYbZorRuOWpBNzm8pL8ftmSxQx/wnJD5Eg861NwJU/czy2VXFIebCeZnZrI9rktIQ==",
+ "license": "(Apache-2.0 AND BSD-3-Clause)"
+ },
+ "node_modules/@csstools/color-helpers": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
+ "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@csstools/css-calc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
+ "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-color-parser": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
+ "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/color-helpers": "^5.1.0",
+ "@csstools/css-calc": "^2.1.4"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-parser-algorithms": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
+ "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-tokenizer": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
+ "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz",
+ "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.21.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz",
+ "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.7",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.5"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz",
+ "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.15.2",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz",
+ "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz",
+ "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.14.0",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.3.0",
+ "minimatch": "^3.1.5",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.34.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.34.0.tgz",
+ "integrity": "sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
+ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz",
+ "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.15.2",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@floating-ui/core": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz",
+ "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.12"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.6",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz",
+ "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.5",
+ "@floating-ui/utils": "^0.2.11"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.12",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz",
+ "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==",
+ "license": "MIT"
+ },
+ "node_modules/@fontsource/inter": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-5.3.0.tgz",
+ "integrity": "sha512-RofMylZmjlJEfELXeNHFWBRcSs75rGU/6bV2S2jfnvv/3rPXPGe0LgUJTklcHZ9lM4OZmAVFhcJPnACfb91A3g==",
+ "license": "OFL-1.1",
+ "funding": {
+ "url": "https://github.com/sponsors/ayuhito"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
+ "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/types": "^0.15.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.8",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz",
+ "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.2",
+ "@humanfs/types": "^0.15.0",
+ "@humanwhocodes/retry": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/types": {
+ "version": "0.15.0",
+ "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz",
+ "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@livekit/components-core": {
+ "version": "0.12.15",
+ "resolved": "https://registry.npmjs.org/@livekit/components-core/-/components-core-0.12.15.tgz",
+ "integrity": "sha512-bDkK+jkPMgyT4a5lZmdcuc/hZ8fZUrXW+qQPNHVeSpXCBMzk5q9QWzx+HPtTsPJ8hnncWGfoT81RnXHBsy+Z8g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@floating-ui/dom": "1.7.6",
+ "loglevel": "1.9.1",
+ "rxjs": "7.8.2"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "livekit-client": "^2.20.1",
+ "tslib": "^2.6.2"
+ }
+ },
+ "node_modules/@livekit/components-react": {
+ "version": "2.9.24",
+ "resolved": "https://registry.npmjs.org/@livekit/components-react/-/components-react-2.9.24.tgz",
+ "integrity": "sha512-qw5Oy1EfPg1f/xrKYrFielSrmtxY8BZ9mDt6299G5aikotQIztmldBfTNosrVS7VSNit7qeYfOFSyKaML20W6Q==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@livekit/components-core": "0.12.15",
+ "clsx": "2.1.1",
+ "events": "^3.3.0",
+ "jose": "^6.0.12",
+ "usehooks-ts": "3.1.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@livekit/krisp-noise-filter": "^0.2.12 || ^0.3.0 || ^0.4.0",
+ "livekit-client": "^2.20.1",
+ "react": ">=18",
+ "react-dom": ">=18",
+ "tslib": "^2.6.2"
+ },
+ "peerDependenciesMeta": {
+ "@livekit/krisp-noise-filter": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@livekit/mutex": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@livekit/mutex/-/mutex-1.1.1.tgz",
+ "integrity": "sha512-EsshAucklmpuUAfkABPxJNhzj9v2sG7JuzFDL4ML1oJQSV14sqrpTYnsaOudMAw9yOaW53NU3QQTlUQoRs4czw==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@livekit/protocol": {
+ "version": "1.50.4",
+ "resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.50.4.tgz",
+ "integrity": "sha512-L1uggNQAqyY21smQY8AllyOYbcv9Me9TaxwuLytL1R8ck9nbYPmQLNwEDi3pOFGAMa5F8I2nUi2Jc59W5awxlA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@bufbuild/protobuf": "^1.10.0"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@oxc-project/types": {
+ "version": "0.146.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.146.0.tgz",
+ "integrity": "sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm-eabi": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.5.tgz",
+ "integrity": "sha512-DLe/i+l8ynIBY7XEQ191TeZvCoowIGa18R+dIV30GW7DiOtp74i/xX8hs8GUjW5ARV7VZuie3d6AumSmCwbeRA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.5.tgz",
+ "integrity": "sha512-zXcwKlQApYAOELHd8PwKDFkagYF9Wy4e0RJ+0qnzl9Pjnpj75TEG8ufv40p2J7kCEfwZAsNiuzRIyNNMWT38ig==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.5.tgz",
+ "integrity": "sha512-dK4QakI42nzWgJT5sm4y4y/O//D4OxM75/cH28RLV+nzIN9AY+YsbuUVrUTjlLjXR6vpyxFbSsbmNuJ6BP9sww==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.5.tgz",
+ "integrity": "sha512-fqSALaUu1Wjd1nK2uW2kJDWdLCc8lx1IcY+MTY26Aurfdx19anlzhqXOgCFbBFQnlFDTn4TC1/7Nz4Bl2mLP3A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.5.tgz",
+ "integrity": "sha512-/vCnNxlkxs9tKxNDcyWUePpJ/PgTzxIaVhoM5SmG8UV+GR/IcPam4VYxi7GIMo7PSDuNqlJqvprqii9NqqVCMw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.5.tgz",
+ "integrity": "sha512-abk0NLA519LxRCszmbE0jYKuQ9YPocOXTiOXOo6Yr+YAT95VH+PtqYAjOJvGKt3viEd/x4qzabAlwd5bHOOARg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.5.tgz",
+ "integrity": "sha512-Y7eALiJ8lr0M2HH103Js+g7V34wf6snlpZLAsHI90uLhr3PVlNsbFVAXJC9d/V6BnPyKtpSwI+NcB/RLxsQxuA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.5.tgz",
+ "integrity": "sha512-xMvZgnbZg4YVnR/AX2b3oOPDTFYJvUVaJg5FedA/LuvexAtXibZQej4cnTkw3rjsJ/ggUROB64TdtETiim+FYA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.5.tgz",
+ "integrity": "sha512-GRjeqTUDHTo5GwntsLaAMcBahG3nlpjftXWZLN73HiYQlhwEowvarFgQnRnQZtIp4keXX7quXFbG38uPZBa2EA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.5.tgz",
+ "integrity": "sha512-vLNTR45F2Uwc8AufkNXPmB4VliaXs+FvcheEogIzOXzO4l+LzieXF5A/TWxLy5HtqpsRCHUfd0lPVrrdgXdLHQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.5.tgz",
+ "integrity": "sha512-Mgj59/HTuYeK9Gz2MA+mBWKnHsAgkBSec15ZMb1st3oIfFbX7gCjOae7GydHhzcyQi9Z/7M1QuN9bR3oFqF0jQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.5.tgz",
+ "integrity": "sha512-mY8AP0/ichsbhAxGnLa3d3+MwV0EfgrPND2bplI3Ym8T6R2pJ0N87bvrKVwNXmdy3jnr6eQBecdqx/HMknBmpA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.5.tgz",
+ "integrity": "sha512-8SLssA2oweAxyRgDp789ACfRb/3P+zNRJpzZxSizxF9m8NUDQ4+3xjo8ttjhVGGw6Qxb70oZiEtIjaKikCO7Yw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.5.tgz",
+ "integrity": "sha512-vGbruD5zquhoc8D9SViXgN2FBJtNdTyQ4DtG+SWiEGlJiAzoKcZ2xp+xuXCffhubVdt0NJlTZqkeRuERy7g8Cw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.5.tgz",
+ "integrity": "sha512-e/SXpgISz+IoqVcSSI0rx/d/he8zqLex+/rCWpnHpmVfmPIUjag9H6P7zotf0gJHwPUhQxZ/mF8tr6acebT9yw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
+ "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@testing-library/dom": {
+ "version": "10.4.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
+ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^5.0.1",
+ "aria-query": "5.3.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.5.0",
+ "picocolors": "1.1.1",
+ "pretty-format": "^27.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@testing-library/jest-dom": {
+ "version": "6.8.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.8.0.tgz",
+ "integrity": "sha512-WgXcWzVM6idy5JaftTVC8Vs83NKRmGJz4Hqs4oyOuO2J4r/y79vvKZsb+CaGyCSEbUPI6OsewfPd0G1A0/TUZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@adobe/css-tools": "^4.4.0",
+ "aria-query": "^5.0.0",
+ "css.escape": "^1.5.1",
+ "dom-accessibility-api": "^0.6.3",
+ "picocolors": "^1.1.1",
+ "redent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=14",
+ "npm": ">=6",
+ "yarn": ">=1"
+ }
+ },
+ "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
+ "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@testing-library/react": {
+ "version": "16.3.0",
+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz",
+ "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": "^10.0.0",
+ "@types/react": "^18.0.0 || ^19.0.0",
+ "@types/react-dom": "^18.0.0 || ^19.0.0",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@types/aria-query": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
+ "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/chai": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
+ "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/deep-eql": "*",
+ "assertion-error": "^2.0.1"
+ }
+ },
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/dom-mediacapture-record": {
+ "version": "1.0.22",
+ "resolved": "https://registry.npmjs.org/@types/dom-mediacapture-record/-/dom-mediacapture-record-1.0.22.tgz",
+ "integrity": "sha512-mUMZLK3NvwRLcAAT9qmcK+9p7tpU2FHdDsntR3YI4+GY88XrgG4XiE7u1Q2LAN2/FZOz/tdMDC3GQCR4T8nFuw==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "24.3.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz",
+ "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.10.0"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "19.1.12",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz",
+ "integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.1.9",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.9.tgz",
+ "integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.41.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.41.0.tgz",
+ "integrity": "sha512-8fz6oa6wEKZrhXWro/S3n2eRJqlRcIa6SlDh59FXJ5Wp5XRZ8B9ixpJDcjadHq47hMx0u+HW6SNa6LjJQ6NLtw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.10.0",
+ "@typescript-eslint/scope-manager": "8.41.0",
+ "@typescript-eslint/type-utils": "8.41.0",
+ "@typescript-eslint/utils": "8.41.0",
+ "@typescript-eslint/visitor-keys": "8.41.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^7.0.0",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.41.0",
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz",
+ "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.41.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.41.0.tgz",
+ "integrity": "sha512-gTtSdWX9xiMPA/7MV9STjJOOYtWwIJIYxkQxnSV1U3xcE+mnJSH3f6zI0RYP+ew66WSlZ5ed+h0VCxsvdC1jJg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.41.0",
+ "@typescript-eslint/types": "8.41.0",
+ "@typescript-eslint/typescript-estree": "8.41.0",
+ "@typescript-eslint/visitor-keys": "8.41.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.41.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.41.0.tgz",
+ "integrity": "sha512-b8V9SdGBQzQdjJ/IO3eDifGpDBJfvrNTp2QD9P2BeqWTGrRibgfgIlBSw6z3b6R7dPzg752tOs4u/7yCLxksSQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.41.0",
+ "@typescript-eslint/types": "^8.41.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.41.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.41.0.tgz",
+ "integrity": "sha512-n6m05bXn/Cd6DZDGyrpXrELCPVaTnLdPToyhBoFkLIMznRUQUEQdSp96s/pcWSQdqOhrgR1mzJ+yItK7T+WPMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.41.0",
+ "@typescript-eslint/visitor-keys": "8.41.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.41.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.41.0.tgz",
+ "integrity": "sha512-TDhxYFPUYRFxFhuU5hTIJk+auzM/wKvWgoNYOPcOf6i4ReYlOoYN8q1dV5kOTjNQNJgzWN3TUUQMtlLOcUgdUw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.41.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.41.0.tgz",
+ "integrity": "sha512-63qt1h91vg3KsjVVonFJWjgSK7pZHSQFKH6uwqxAH9bBrsyRhO6ONoKyXxyVBzG1lJnFAJcKAcxLS54N1ee1OQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.41.0",
+ "@typescript-eslint/typescript-estree": "8.41.0",
+ "@typescript-eslint/utils": "8.41.0",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.41.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.41.0.tgz",
+ "integrity": "sha512-9EwxsWdVqh42afLbHP90n2VdHaWU/oWgbH2P0CfcNfdKL7CuKpwMQGjwev56vWu9cSKU7FWSu6r9zck6CVfnag==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.41.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.41.0.tgz",
+ "integrity": "sha512-D43UwUYJmGhuwHfY7MtNKRZMmfd8+p/eNSfFe6tH5mbVDto+VQCayeAt35rOx3Cs6wxD16DQtIKw/YXxt5E0UQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.41.0",
+ "@typescript-eslint/tsconfig-utils": "8.41.0",
+ "@typescript-eslint/types": "8.41.0",
+ "@typescript-eslint/visitor-keys": "8.41.0",
+ "debug": "^4.3.4",
+ "fast-glob": "^3.3.2",
+ "is-glob": "^4.0.3",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "ts-api-utils": "^2.1.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
+ "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+ "version": "7.8.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
+ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.41.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.41.0.tgz",
+ "integrity": "sha512-udbCVstxZ5jiPIXrdH+BZWnPatjlYwJuJkDA4Tbo3WyYLh8NvB+h/bKeSZHDOFKfphsZYJQqaFtLeXEqurQn1A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.7.0",
+ "@typescript-eslint/scope-manager": "8.41.0",
+ "@typescript-eslint/types": "8.41.0",
+ "@typescript-eslint/typescript-estree": "8.41.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.41.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.41.0.tgz",
+ "integrity": "sha512-+GeGMebMCy0elMNg67LRNoVnUFPIm37iu5CmHESVx56/9Jsfdpsvbv605DQ81Pi/x11IdKUsS5nzgTYbCQU9fg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.41.0",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.2.tgz",
+ "integrity": "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rolldown/pluginutils": "^1.0.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
+ "babel-plugin-react-compiler": "^1.0.0",
+ "vite": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@rolldown/plugin-babel": {
+ "optional": true
+ },
+ "babel-plugin-react-compiler": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/expect": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz",
+ "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.1.0",
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "4.1.11",
+ "@vitest/utils": "4.1.11",
+ "chai": "^6.2.2",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz",
+ "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "4.1.11",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.21"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz",
+ "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz",
+ "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "4.1.11",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz",
+ "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.11",
+ "@vitest/utils": "4.1.11",
+ "magic-string": "^0.30.21",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz",
+ "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz",
+ "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.11",
+ "convert-source-map": "^2.0.0",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz",
+ "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
+ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/aria-query": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "dequal": "^2.0.3"
+ }
+ },
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.11.18",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.18.tgz",
+ "integrity": "sha512-1iEmLEYSiE1SeBoAfPo/Mnx3PzfzHUkDK61ASkCpuk3YXugYLH5DYK1SzqV55F8FMI6s0F+/tCP7Polz1QRjxw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.18",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
+ "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.8",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz",
+ "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.11.12",
+ "caniuse-lite": "^1.0.30001809",
+ "electron-to-chromium": "^1.5.402",
+ "node-releases": "^2.0.53",
+ "update-browserslist-db": "^1.3.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001809",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz",
+ "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chai": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
+ "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/css.escape": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
+ "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cssstyle": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz",
+ "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@asamuzakjp/css-color": "^3.2.0",
+ "rrweb-cssom": "^0.8.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/data-urls": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
+ "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decimal.js": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
+ "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dom-accessibility-api": {
+ "version": "0.5.16",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
+ "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.412",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.412.tgz",
+ "integrity": "sha512-z4rMe3esBzlzovKHj4gxJnsCGZRK5l4baUvm+gCGJBPE+gsyUMKsuU9tnEUtI1dOebXz1ytAPGjvXhmQ7rIPwA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/entities": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz",
+ "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.34.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.34.0.tgz",
+ "integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==",
+ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.0",
+ "@eslint/config-helpers": "^0.3.1",
+ "@eslint/core": "^0.15.2",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.34.0",
+ "@eslint/plugin-kit": "^0.3.5",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "@types/json-schema": "^7.0.15",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.0.tgz",
+ "integrity": "sha512-fNXaOwvKwq2+pXiRpXc825Vd63+KM4DLL40Rtlycb8m7fYpp6efrTp1sa6ZbP/Ap58K2bEKFXRmhURE+CJAQWw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.24.4",
+ "@babel/parser": "^7.24.4",
+ "hermes-parser": "^0.25.1",
+ "zod": "^3.22.4 || ^4.0.0",
+ "zod-validation-error": "^3.0.3 || ^4.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.4.20",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz",
+ "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "eslint": ">=8.40"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/events": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
+ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.x"
+ }
+ },
+ "node_modules/expect-type": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz",
+ "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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==",
+ "dev": true,
+ "license": "MIT",
+ "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==",
+ "dev": true,
+ "license": "ISC",
+ "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",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fastq": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.4.4",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz",
+ "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "16.3.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz",
+ "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/hermes-estree": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
+ "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/hermes-parser": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
+ "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hermes-estree": "0.25.1"
+ }
+ },
+ "node_modules/html-encoding-sniffer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
+ "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-encoding": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/http-proxy-agent": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/indent-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-potential-custom-element-name": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/jose": {
+ "version": "6.2.10",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.10.tgz",
+ "integrity": "sha512-iiW7J9qRFlGxvCOIBDBDxFePQSn7ZMAnrYGhrrOo6siO/MIqwfyilLR27pkfDgUk+raLuzADS8A3S/KLBisc0g==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz",
+ "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsdom": {
+ "version": "26.1.0",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",
+ "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssstyle": "^4.2.1",
+ "data-urls": "^5.0.0",
+ "decimal.js": "^10.5.0",
+ "html-encoding-sniffer": "^4.0.0",
+ "http-proxy-agent": "^7.0.2",
+ "https-proxy-agent": "^7.0.6",
+ "is-potential-custom-element-name": "^1.0.1",
+ "nwsapi": "^2.2.16",
+ "parse5": "^7.2.1",
+ "rrweb-cssom": "^0.8.0",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^5.1.1",
+ "w3c-xmlserializer": "^5.0.0",
+ "webidl-conversions": "^7.0.0",
+ "whatwg-encoding": "^3.1.1",
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.1.1",
+ "ws": "^8.18.0",
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "canvas": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
+ "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.33.0",
+ "lightningcss-darwin-arm64": "1.33.0",
+ "lightningcss-darwin-x64": "1.33.0",
+ "lightningcss-freebsd-x64": "1.33.0",
+ "lightningcss-linux-arm-gnueabihf": "1.33.0",
+ "lightningcss-linux-arm64-gnu": "1.33.0",
+ "lightningcss-linux-arm64-musl": "1.33.0",
+ "lightningcss-linux-x64-gnu": "1.33.0",
+ "lightningcss-linux-x64-musl": "1.33.0",
+ "lightningcss-win32-arm64-msvc": "1.33.0",
+ "lightningcss-win32-x64-msvc": "1.33.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
+ "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz",
+ "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz",
+ "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz",
+ "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz",
+ "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz",
+ "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz",
+ "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz",
+ "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz",
+ "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz",
+ "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz",
+ "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/livekit-client": {
+ "version": "2.22.0",
+ "resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.22.0.tgz",
+ "integrity": "sha512-GLtYQfRh/RsvXaOX1x609bFZ17yyKmKWDqu9JmkMKn9vIFLi2GsapRv9gT8OJO/1R2dsitrkbGmloyUfxWQsaA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@livekit/mutex": "1.1.1",
+ "@livekit/protocol": "1.50.4",
+ "events": "^3.3.0",
+ "jose": "^6.1.0",
+ "loglevel": "^1.9.2",
+ "sdp-transform": "^2.15.0",
+ "tslib": "2.8.1",
+ "typed-emitter": "^2.1.0",
+ "webrtc-adapter": "9.0.6"
+ },
+ "peerDependencies": {
+ "@types/dom-mediacapture-record": "^1"
+ }
+ },
+ "node_modules/livekit-client/node_modules/loglevel": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz",
+ "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6.0"
+ },
+ "funding": {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/loglevel"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.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"
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/loglevel": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz",
+ "integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6.0"
+ },
+ "funding": {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/loglevel"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lz-string": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
+ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "lz-string": "bin/bin.js"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.18",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
+ "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.53",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz",
+ "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/nwsapi": {
+ "version": "2.2.24",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.24.tgz",
+ "integrity": "sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/obug": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz",
+ "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse5": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
+ "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.26",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
+ "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.17",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
+ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/pretty-format": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^17.0.1"
+ },
+ "engines": {
+ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/react": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
+ "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
+ "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.4"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/redent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
+ "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "indent-string": "^4.0.0",
+ "strip-indent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rolldown": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.5.tgz",
+ "integrity": "sha512-VD2IE5PUG4Oj8zz2VGykiYd5wbnjdIiSsNQb8Qu5B+noEp+A78mu2iVvpp27g8es14Tk9rofNs5Tku9iQCS4fA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.146.0",
+ "@rolldown/pluginutils": "^1.0.0"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm-eabi": "1.2.5",
+ "@rolldown/binding-android-arm64": "1.2.5",
+ "@rolldown/binding-darwin-arm64": "1.2.5",
+ "@rolldown/binding-darwin-x64": "1.2.5",
+ "@rolldown/binding-freebsd-x64": "1.2.5",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.2.5",
+ "@rolldown/binding-linux-arm64-gnu": "1.2.5",
+ "@rolldown/binding-linux-arm64-musl": "1.2.5",
+ "@rolldown/binding-linux-ppc64-gnu": "1.2.5",
+ "@rolldown/binding-linux-s390x-gnu": "1.2.5",
+ "@rolldown/binding-linux-x64-gnu": "1.2.5",
+ "@rolldown/binding-linux-x64-musl": "1.2.5",
+ "@rolldown/binding-openharmony-arm64": "1.2.5",
+ "@rolldown/binding-win32-arm64-msvc": "1.2.5",
+ "@rolldown/binding-win32-x64-msvc": "1.2.5"
+ }
+ },
+ "node_modules/rrweb-cssom": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
+ "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/rxjs": {
+ "version": "7.8.2",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
+ "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/saxes": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "xmlchars": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=v12.22.7"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "node_modules/sdp": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.2.tgz",
+ "integrity": "sha512-xZocWwfyp4hkbN4hLWxMjmv2Q8aNa9MhmOZ7L9aCZPT+dZsgRr6wZRrSYE3HTdyk/2pZKPSgqI7ns7Een1xMSA==",
+ "license": "MIT"
+ },
+ "node_modules/sdp-transform": {
+ "version": "2.15.0",
+ "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.15.0.tgz",
+ "integrity": "sha512-KrOH82c/W+GYQ0LHqtr3caRpM3ITglq3ljGUIb8LTki7ByacJZ9z+piSGiwZDsRyhQbYBOBJgr2k6X4BZXi3Kw==",
+ "license": "MIT",
+ "bin": {
+ "sdp-verify": "checker.js"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/std-env": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz",
+ "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "min-indent": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/symbol-tree": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz",
+ "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyglobby/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz",
+ "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tldts": {
+ "version": "6.1.86",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
+ "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tldts-core": "^6.1.86"
+ },
+ "bin": {
+ "tldts": "bin/cli.js"
+ }
+ },
+ "node_modules/tldts-core": {
+ "version": "6.1.86",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
+ "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tough-cookie": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
+ "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tldts": "^6.1.32"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
+ "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
+ "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/typed-emitter": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz",
+ "integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==",
+ "license": "MIT",
+ "optionalDependencies": {
+ "rxjs": "*"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/typescript-eslint": {
+ "version": "8.41.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.41.0.tgz",
+ "integrity": "sha512-n66rzs5OBXW3SFSnZHr2T685q1i4ODm2nulFJhMZBotaTavsS8TrI3d7bDlRSs9yWo7HmyWrN9qDu14Qv7Y0Dw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "8.41.0",
+ "@typescript-eslint/parser": "8.41.0",
+ "@typescript-eslint/typescript-estree": "8.41.0",
+ "@typescript-eslint/utils": "8.41.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.10.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
+ "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz",
+ "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/usehooks-ts": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-3.1.1.tgz",
+ "integrity": "sha512-I4diPp9Cq6ieSUH2wu+fDAVQO43xwtulo+fKEidHUwZPnYImbtkTjzIJYcDcJqxgmX31GVqNFURodvcgHcW0pA==",
+ "license": "MIT",
+ "dependencies": {
+ "lodash.debounce": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=16.15.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17 || ^18 || ^19 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/vite": {
+ "version": "8.2.2",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz",
+ "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lightningcss": "^1.33.0",
+ "picomatch": "^4.0.5",
+ "postcss": "^8.5.26",
+ "rolldown": "~1.2.4",
+ "tinyglobby": "^0.2.17"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.4.0 || ^0.5.0",
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite/node_modules/picomatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz",
+ "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "4.1.11",
+ "@vitest/mocker": "4.1.11",
+ "@vitest/pretty-format": "4.1.11",
+ "@vitest/runner": "4.1.11",
+ "@vitest/snapshot": "4.1.11",
+ "@vitest/spy": "4.1.11",
+ "@vitest/utils": "4.1.11",
+ "es-module-lexer": "^2.0.0",
+ "expect-type": "^1.3.0",
+ "magic-string": "^0.30.21",
+ "obug": "^2.1.1",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.3",
+ "std-env": "^4.0.0-rc.1",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^1.0.2",
+ "tinyglobby": "^0.2.15",
+ "tinyrainbow": "^3.1.0",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@opentelemetry/api": "^1.9.0",
+ "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
+ "@vitest/browser-playwright": "4.1.11",
+ "@vitest/browser-preview": "4.1.11",
+ "@vitest/browser-webdriverio": "4.1.11",
+ "@vitest/coverage-istanbul": "4.1.11",
+ "@vitest/coverage-v8": "4.1.11",
+ "@vitest/ui": "4.1.11",
+ "happy-dom": "*",
+ "jsdom": "*",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser-playwright": {
+ "optional": true
+ },
+ "@vitest/browser-preview": {
+ "optional": true
+ },
+ "@vitest/browser-webdriverio": {
+ "optional": true
+ },
+ "@vitest/coverage-istanbul": {
+ "optional": true
+ },
+ "@vitest/coverage-v8": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ },
+ "vite": {
+ "optional": false
+ }
+ }
+ },
+ "node_modules/vitest/node_modules/picomatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/w3c-xmlserializer": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
+ "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/webrtc-adapter": {
+ "version": "9.0.6",
+ "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-9.0.6.tgz",
+ "integrity": "sha512-CHbl2ZQbxx164IgWRgzJno4hWtM4tFbRam1QfI3Yxhs3w/DvqluVxVWeXs3oL5/fbGkSNLKo0Ty5MgUWceNhog==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "sdp": "^3.2.0"
+ },
+ "engines": {
+ "node": ">=6.0.0",
+ "npm": ">=3.10.0"
+ }
+ },
+ "node_modules/whatwg-encoding": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
+ "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
+ "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "iconv-lite": "0.6.3"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-mimetype": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
+ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "14.2.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
+ "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "^5.1.0",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/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",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.21.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz",
+ "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xml-name-validator": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
+ "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/xmlchars": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zod": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
+ "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-validation-error": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
+ "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "zod": "^3.25.0 || ^4.0.0"
+ }
+ }
+ }
+}
diff --git a/muse_glimmer/macos/apps/web/package.json b/muse_glimmer/macos/apps/web/package.json
new file mode 100644
index 0000000000..6303ae896a
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/package.json
@@ -0,0 +1,46 @@
+{
+ "name": "@muse-glimmer/web",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "engines": {
+ "node": ">=22.12.0"
+ },
+ "scripts": {
+ "dev": "vite --host 127.0.0.1 --port 5173",
+ "build": "tsc -b && vite build",
+ "serve": "node ./server.mjs",
+ "preview": "npm run serve",
+ "typecheck": "tsc -b",
+ "test": "vitest run && node --test server.test.mjs",
+ "test:watch": "vitest",
+ "lint": "eslint . --max-warnings=0",
+ "format:check": "prettier --check ."
+ },
+ "dependencies": {
+ "@fontsource/inter": "5.3.0",
+ "@livekit/components-react": "2.9.24",
+ "livekit-client": "2.22.0",
+ "react": "19.2.4",
+ "react-dom": "19.2.4"
+ },
+ "devDependencies": {
+ "@eslint/js": "9.34.0",
+ "@testing-library/jest-dom": "6.8.0",
+ "@testing-library/react": "16.3.0",
+ "@types/node": "24.3.0",
+ "@types/react": "19.1.12",
+ "@types/react-dom": "19.1.9",
+ "@vitejs/plugin-react": "6.0.2",
+ "eslint": "9.34.0",
+ "eslint-plugin-react-hooks": "7.0.0",
+ "eslint-plugin-react-refresh": "0.4.20",
+ "globals": "16.3.0",
+ "jsdom": "26.1.0",
+ "prettier": "3.6.2",
+ "typescript": "5.9.3",
+ "typescript-eslint": "8.41.0",
+ "vite": "8.2.2",
+ "vitest": "4.1.11"
+ }
+}
diff --git a/muse_glimmer/macos/apps/web/server.mjs b/muse_glimmer/macos/apps/web/server.mjs
new file mode 100644
index 0000000000..9a8c8c5d4e
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/server.mjs
@@ -0,0 +1,154 @@
+import { createReadStream, existsSync, statSync } from "node:fs";
+import { createServer } from "node:http";
+import { extname, join, normalize, resolve, sep } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const HOST = "127.0.0.1";
+const DEFAULT_PORT = 5173;
+const APP_DIRECTORY = resolve(
+ fileURLToPath(new URL("./dist", import.meta.url)),
+);
+const CSP = [
+ "default-src 'self'",
+ "base-uri 'none'",
+ "connect-src 'self' http://127.0.0.1:8787 ws://127.0.0.1:7880",
+ "font-src 'self'",
+ "form-action 'none'",
+ "frame-ancestors 'none'",
+ "img-src 'self' data:",
+ "media-src 'self' blob:",
+ "object-src 'none'",
+ "script-src 'self'",
+ "style-src 'self'",
+ "worker-src 'self' blob:",
+].join("; ");
+
+const MIME_TYPES = new Map([
+ [".css", "text/css; charset=utf-8"],
+ [".html", "text/html; charset=utf-8"],
+ [".js", "text/javascript; charset=utf-8"],
+ [".json", "application/json; charset=utf-8"],
+ [".svg", "image/svg+xml"],
+ [".woff2", "font/woff2"],
+]);
+
+function applySecurityHeaders(response) {
+ response.setHeader("Cache-Control", "no-store");
+ response.setHeader("Content-Security-Policy", CSP);
+ response.setHeader("Cross-Origin-Opener-Policy", "same-origin");
+ response.setHeader(
+ "Permissions-Policy",
+ "camera=(), geolocation=(), microphone=(self)",
+ );
+ response.setHeader("Referrer-Policy", "no-referrer");
+ response.setHeader("X-Content-Type-Options", "nosniff");
+ response.setHeader("X-Frame-Options", "DENY");
+}
+
+function resolveRequestPath(requestUrl) {
+ const pathname = decodeURIComponent(
+ new URL(requestUrl ?? "/", `http://${HOST}`).pathname,
+ );
+ const normalizedPath = normalize(pathname).replace(/^[/\\]+/, "");
+ const requestedPath = resolve(join(APP_DIRECTORY, normalizedPath));
+ if (
+ requestedPath !== APP_DIRECTORY &&
+ !requestedPath.startsWith(`${APP_DIRECTORY}${sep}`)
+ ) {
+ return undefined;
+ }
+ if (existsSync(requestedPath) && statSync(requestedPath).isFile()) {
+ return requestedPath;
+ }
+ return join(APP_DIRECTORY, "index.html");
+}
+
+export function createAppServer() {
+ if (!existsSync(join(APP_DIRECTORY, "index.html"))) {
+ throw new Error("Production assets are missing. Run npm run build first.");
+ }
+
+ return createServer((request, response) => {
+ applySecurityHeaders(response);
+ if (request.method !== "GET" && request.method !== "HEAD") {
+ response.writeHead(405, { Allow: "GET, HEAD" });
+ response.end("Method Not Allowed");
+ return;
+ }
+
+ let filePath;
+ try {
+ filePath = resolveRequestPath(request.url);
+ } catch {
+ response.writeHead(400);
+ response.end("Bad Request");
+ return;
+ }
+ if (!filePath) {
+ response.writeHead(403);
+ response.end("Forbidden");
+ return;
+ }
+
+ response.setHeader(
+ "Content-Type",
+ MIME_TYPES.get(extname(filePath)) ?? "application/octet-stream",
+ );
+ response.writeHead(200);
+ if (request.method === "HEAD") {
+ response.end();
+ return;
+ }
+ createReadStream(filePath).pipe(response);
+ });
+}
+
+export function parseServeOptions(args, environment = process.env) {
+ let host = HOST;
+ let port = environment.PORT ?? String(DEFAULT_PORT);
+ const provided = new Set();
+
+ for (let index = 0; index < args.length; index += 1) {
+ const argument = args[index];
+ const [flag, inlineValue] = argument.split("=", 2);
+ if (flag !== "--host" && flag !== "--port") {
+ throw new Error(`Unknown serve argument: ${flag}`);
+ }
+ if (provided.has(flag)) {
+ throw new Error(`Duplicate serve argument: ${flag}`);
+ }
+
+ const value = inlineValue ?? args[++index];
+ if (!value || value.startsWith("--")) {
+ throw new Error(`${flag} requires a value.`);
+ }
+ provided.add(flag);
+ if (flag === "--host") {
+ host = value;
+ } else {
+ port = value;
+ }
+ }
+
+ if (host !== HOST) {
+ throw new Error(`Host must be ${HOST}.`);
+ }
+ if (!/^\d+$/.test(port)) {
+ throw new Error("Port must be an integer between 1 and 65535.");
+ }
+ const portValue = Number.parseInt(port, 10);
+ if (portValue !== DEFAULT_PORT) {
+ throw new Error(`Port must be ${DEFAULT_PORT}.`);
+ }
+
+ return { host, port: portValue };
+}
+
+const entryPoint = process.argv[1] ? resolve(process.argv[1]) : undefined;
+if (entryPoint === fileURLToPath(import.meta.url)) {
+ const { host, port } = parseServeOptions(process.argv.slice(2));
+ const server = createAppServer();
+ server.listen(port, host, () => {
+ console.log(`Muse Glimmer web app listening at http://${host}:${port}`);
+ });
+}
diff --git a/muse_glimmer/macos/apps/web/server.test.mjs b/muse_glimmer/macos/apps/web/server.test.mjs
new file mode 100644
index 0000000000..402ef314bc
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/server.test.mjs
@@ -0,0 +1,62 @@
+import assert from "node:assert/strict";
+import test from "node:test";
+
+import { parseServeOptions } from "./server.mjs";
+
+test("defaults to the approved loopback endpoint", () => {
+ assert.deepEqual(parseServeOptions([], {}), {
+ host: "127.0.0.1",
+ port: 5173,
+ });
+});
+
+test("accepts the supervisor serve contract", () => {
+ assert.deepEqual(
+ parseServeOptions(["--host", "127.0.0.1", "--port", "5173"], {}),
+ {
+ host: "127.0.0.1",
+ port: 5173,
+ },
+ );
+});
+
+test("accepts equals-style options", () => {
+ assert.deepEqual(parseServeOptions(["--host=127.0.0.1", "--port=5173"], {}), {
+ host: "127.0.0.1",
+ port: 5173,
+ });
+});
+
+test("rejects non-loopback host overrides", () => {
+ assert.throws(
+ () => parseServeOptions(["--host", "0.0.0.0"], {}),
+ /Host must be 127\.0\.0\.1/,
+ );
+ assert.throws(
+ () => parseServeOptions(["--host", "localhost"], {}),
+ /Host must be 127\.0\.0\.1/,
+ );
+});
+
+test("rejects malformed ports, unknown flags, and duplicates", () => {
+ assert.throws(
+ () => parseServeOptions(["--port", "5173oops"], {}),
+ /Port must be an integer/,
+ );
+ assert.throws(
+ () => parseServeOptions(["--port", "0"], {}),
+ /Port must be 5173/,
+ );
+ assert.throws(
+ () => parseServeOptions(["--port", "4173"], {}),
+ /Port must be 5173/,
+ );
+ assert.throws(
+ () => parseServeOptions(["--public"], {}),
+ /Unknown serve argument/,
+ );
+ assert.throws(
+ () => parseServeOptions(["--host", "127.0.0.1", "--host", "127.0.0.1"], {}),
+ /Duplicate serve argument/,
+ );
+});
diff --git a/muse_glimmer/macos/apps/web/src/App.test.tsx b/muse_glimmer/macos/apps/web/src/App.test.tsx
new file mode 100644
index 0000000000..62d8609361
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/src/App.test.tsx
@@ -0,0 +1,91 @@
+import { fireEvent, render, screen, waitFor } from "@testing-library/react";
+import type { ReactNode } from "react";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+const mocks = vi.hoisted(() => ({
+ requestConnection: vi.fn(),
+ disconnectRoom: undefined as (() => void) | undefined,
+}));
+
+vi.mock("./lib/tokenClient", () => ({
+ requestConnection: mocks.requestConnection,
+}));
+vi.mock("./avatar/MuseAvatar", () => ({ MuseAvatar: () => Muse
}));
+vi.mock("./components/RuntimeBadge", () => ({
+ RuntimeBadge: () => ExecuTorch
,
+}));
+vi.mock("@livekit/components-react", () => ({
+ LiveKitRoom: ({
+ children,
+ onDisconnected,
+ }: {
+ children: ReactNode;
+ onDisconnected: () => void;
+ }) => {
+ mocks.disconnectRoom = onDisconnected;
+ return {children}
;
+ },
+}));
+vi.mock("./components/VoiceSession", () => ({
+ VoiceSession: ({
+ onEnding,
+ onEnded,
+ }: {
+ onEnding: () => void;
+ onEnded: () => void;
+ }) => (
+
+
+ Mark ending
+
+
+ Finish
+
+
+ ),
+}));
+
+import App from "./App";
+
+beforeEach(() => {
+ mocks.disconnectRoom = undefined;
+ mocks.requestConnection.mockReset();
+ mocks.requestConnection.mockResolvedValue({
+ serverUrl: "ws://127.0.0.1:7880",
+ participantToken: "token",
+ roomName: "room",
+ participantIdentity: "participant",
+ });
+});
+
+async function start() {
+ render( );
+ fireEvent.click(screen.getByRole("button", { name: /Start conversation/ }));
+ await screen.findByRole("button", { name: "Mark ending" });
+}
+
+describe("conversation disconnect handling", () => {
+ it("shows an error after an unexpected disconnect", async () => {
+ await start();
+
+ mocks.disconnectRoom?.();
+
+ expect(await screen.findByRole("alert")).toHaveTextContent(
+ "The local voice connection failed.",
+ );
+ });
+
+ it("returns to idle after an intentional disconnect", async () => {
+ await start();
+ fireEvent.click(screen.getByRole("button", { name: "Mark ending" }));
+
+ mocks.disconnectRoom?.();
+
+ await waitFor(() =>
+ expect(
+ screen.getByRole("button", { name: /Start conversation/ }),
+ ).toBeEnabled(),
+ );
+ expect(screen.queryByRole("alert")).not.toBeInTheDocument();
+ });
+});
diff --git a/muse_glimmer/macos/apps/web/src/App.tsx b/muse_glimmer/macos/apps/web/src/App.tsx
new file mode 100644
index 0000000000..0ee9fe5665
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/src/App.tsx
@@ -0,0 +1,155 @@
+import { LiveKitRoom } from "@livekit/components-react";
+import { useRef, useState } from "react";
+
+import { MuseAvatar } from "./avatar/MuseAvatar";
+import { RuntimeBadge } from "./components/RuntimeBadge";
+import { VoiceSession } from "./components/VoiceSession";
+import { usePrefersReducedMotion } from "./hooks/usePrefersReducedMotion";
+import {
+ getAgentPresentation,
+ type SessionPhase,
+} from "./lib/agentPresentation";
+import { requestConnection, type ConnectionDetails } from "./lib/tokenClient";
+
+export default function App() {
+ const [phase, setPhase] = useState("idle");
+ const [connection, setConnection] = useState();
+ const [error, setError] = useState();
+ const intentionalDisconnect = useRef(false);
+ const reducedMotion = usePrefersReducedMotion();
+
+ const startConversation = async () => {
+ intentionalDisconnect.current = false;
+ setPhase("requesting");
+ setError(undefined);
+ try {
+ const details = await requestConnection();
+ setConnection(details);
+ setPhase("active");
+ } catch (caught) {
+ setConnection(undefined);
+ setPhase("error");
+ setError(
+ caught instanceof Error
+ ? caught.message
+ : "The conversation could not start.",
+ );
+ }
+ };
+
+ const finishConversation = () => {
+ setConnection(undefined);
+ setError(undefined);
+ setPhase("idle");
+ };
+
+ const failConversation = () => {
+ setConnection(undefined);
+ setError("The local voice connection failed.");
+ setPhase("error");
+ };
+
+ const handleDisconnected = () => {
+ if (intentionalDisconnect.current) {
+ finishConversation();
+ } else {
+ failConversation();
+ }
+ };
+
+ if (connection && phase === "active") {
+ return (
+ {
+ setConnection(undefined);
+ setError(
+ "Chrome could not use the microphone. Check its site permission and try again.",
+ );
+ setPhase("error");
+ }}
+ data-lk-theme="default"
+ >
+ {
+ intentionalDisconnect.current = true;
+ }}
+ onEnded={finishConversation}
+ />
+
+ );
+ }
+
+ const presentation = getAgentPresentation(phase, false, false);
+ const isRequesting = phase === "requesting";
+
+ return (
+
+
+
+
+
Local Voice Agent
+
Talk with Muse Glimmer
+
+
+
+
+
+
+
+ One voice agent.
+ Entirely on-device.
+
+
+ Local ASR hears you, an on-device LLM thinks, and local TTS speaks
+ back. No cloud required.
+
+
+
+
+ {error ? (
+
+ {error}
+
+ ) : (
+
+ Your microphone starts only after you choose to begin.
+
+ )}
+
void startConversation()}
+ disabled={isRequesting}
+ >
+
+ {isRequesting ? "Preparing conversation" : "Start conversation"}
+
+
+
+
+
+ );
+}
+
+function ArrowIcon() {
+ return (
+
+
+
+ );
+}
diff --git a/muse_glimmer/macos/apps/web/src/app.css b/muse_glimmer/macos/apps/web/src/app.css
new file mode 100644
index 0000000000..20d55c6e9c
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/src/app.css
@@ -0,0 +1,836 @@
+:root {
+ color: #172b4d;
+ background: #f5f9ff;
+ font-family: "Inter", sans-serif;
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ --ink: #172b4d;
+ --slate: #24385b;
+ --meta-blue: #0668e1;
+ --meta-blue-deep: #0050b3;
+ --meta-blue-soft: #8cc8ff;
+ --paper: #f7fbff;
+ --paper-rich: #e7f3ff;
+ --error: #9e332f;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+html,
+body,
+#root {
+ min-height: 100%;
+ margin: 0;
+}
+
+body {
+ min-width: 320px;
+ min-height: 100dvh;
+ overflow-x: hidden;
+}
+
+button {
+ font: inherit;
+}
+
+button:focus-visible {
+ outline: 3px solid var(--meta-blue);
+ outline-offset: 4px;
+}
+
+.voice-shell {
+ position: relative;
+ display: grid;
+ grid-template-rows: auto minmax(0, 1fr) auto;
+ min-height: 100dvh;
+ overflow: hidden;
+ color: var(--ink);
+ background:
+ linear-gradient(rgba(6, 104, 225, 0.055) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(6, 104, 225, 0.055) 1px, transparent 1px),
+ linear-gradient(145deg, #fbfdff 0%, #eef6ff 55%, #dceeff 100%);
+ background-size:
+ 48px 48px,
+ 48px 48px,
+ auto;
+ isolation: isolate;
+}
+
+.voice-shell::before {
+ position: absolute;
+ inset: 0;
+ z-index: -1;
+ background: linear-gradient(
+ 115deg,
+ rgba(255, 255, 255, 0.65),
+ transparent 38% 72%,
+ rgba(6, 104, 225, 0.08)
+ );
+ content: "";
+ pointer-events: none;
+}
+
+.app-header {
+ z-index: 3;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
+ align-items: flex-start;
+ padding: max(28px, env(safe-area-inset-top)) clamp(24px, 5vw, 72px) 0;
+}
+
+.header-title {
+ display: grid;
+ grid-column: 2;
+ justify-items: center;
+ text-align: center;
+}
+
+.header-title .runtime-badge {
+ margin-bottom: 20px;
+}
+
+.eyebrow {
+ margin: 0 0 8px;
+ color: var(--meta-blue);
+ font-size: 0.7rem;
+ font-weight: 700;
+ letter-spacing: 0;
+ text-transform: uppercase;
+}
+
+.app-header h1 {
+ margin: 0;
+ font-size: 2rem;
+ font-weight: 600;
+ letter-spacing: 0;
+ line-height: 1.1;
+}
+
+.runtime-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 11px;
+ min-height: 46px;
+ padding: 6px 16px 6px 8px;
+ color: #53627a;
+ border: 1px solid rgba(6, 104, 225, 0.18);
+ border-radius: 999px;
+ background: rgba(247, 251, 255, 0.86);
+ box-shadow: 0 10px 30px rgba(6, 75, 150, 0.08);
+ font-size: 0.84rem;
+ white-space: nowrap;
+ backdrop-filter: blur(12px);
+}
+
+.runtime-chip {
+ position: relative;
+ display: grid;
+ width: 32px;
+ height: 32px;
+ place-items: center;
+ color: #fff;
+ border: 1px solid #032b62;
+ border-radius: 8px;
+ background: #061a35;
+ box-shadow:
+ inset 0 0 0 3px #fff,
+ inset 0 0 0 4px #82bbf6;
+ font-size: 0.55rem;
+ font-weight: 700;
+}
+
+.runtime-chip::before,
+.runtime-chip::after {
+ position: absolute;
+ inset: 5px -4px;
+ border-top: 1px solid #061a35;
+ border-bottom: 1px solid #061a35;
+ content: "";
+}
+
+.runtime-chip::after {
+ inset: -4px 5px;
+ border: 0;
+ border-right: 1px solid #061a35;
+ border-left: 1px solid #061a35;
+}
+
+.runtime-badge strong {
+ color: var(--ink);
+ font-size: 0.92rem;
+ font-weight: 700;
+}
+
+.header-status {
+ margin: 7px 0 0;
+ color: #52647d;
+ font-size: 0.78rem;
+ font-weight: 500;
+}
+
+.presence {
+ position: relative;
+ z-index: 1;
+ display: grid;
+ align-content: center;
+ justify-items: center;
+ min-height: 0;
+ padding: 18px 24px;
+}
+
+.presence-halo {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: min(52vw, 570px);
+ aspect-ratio: 1;
+ border: 1px solid rgba(62, 78, 101, 0.1);
+ border-radius: 50%;
+ background: repeating-radial-gradient(
+ circle,
+ rgba(255, 255, 255, 0.44) 0,
+ rgba(255, 255, 255, 0.44) 1px,
+ transparent 1px,
+ transparent 34px
+ );
+ transform: translate(-50%, -53%);
+ transition:
+ scale 700ms ease,
+ opacity 700ms ease;
+}
+
+.muse-avatar {
+ position: relative;
+ width: clamp(260px, 34vw, 480px);
+ aspect-ratio: 1;
+ filter: drop-shadow(0 32px 35px rgba(6, 75, 150, 0.13));
+ transform: translateZ(0);
+ transition:
+ transform 500ms ease,
+ filter 500ms ease;
+}
+
+.muse-avatar-art {
+ display: block;
+ width: 100%;
+ height: 100%;
+ overflow: visible;
+}
+
+.muse-orbit {
+ fill: none;
+ stroke: rgba(6, 104, 225, 0.16);
+ stroke-dasharray: 3 9;
+ stroke-linecap: round;
+ stroke-width: 1.5;
+ transform-origin: center;
+ animation: orbit 32s linear infinite;
+}
+
+.muse-orbit path {
+ opacity: 0.55;
+ stroke-dasharray: 1 12;
+}
+
+.muse-character {
+ transform-origin: 180px 190px;
+ animation: breathe 4.8s ease-in-out infinite;
+}
+
+.muse-ear {
+ fill: #0575e6;
+ stroke: rgba(255, 255, 255, 0.28);
+ stroke-width: 2;
+ transform-origin: center;
+ transition: transform 360ms ease;
+}
+
+.muse-body {
+ stroke: rgba(255, 255, 255, 0.58);
+ stroke-width: 2;
+}
+
+.muse-body-glow {
+ mix-blend-mode: screen;
+ pointer-events: none;
+}
+
+.muse-face {
+ transform-origin: 180px 178px;
+ transition: transform 360ms ease;
+}
+
+.muse-eye {
+ fill: #fff;
+ transform-box: fill-box;
+ transform-origin: center;
+ transition: transform 280ms ease;
+ animation: blink 6.4s ease-in-out infinite;
+}
+
+.muse-mouth {
+ fill: none;
+ stroke: rgba(255, 255, 255, 0.8);
+ stroke-linecap: round;
+ stroke-width: 4;
+ transition: d 300ms ease;
+}
+
+.muse-voice {
+ fill: none;
+ opacity: 0;
+ stroke: #fff;
+ stroke-linecap: round;
+ stroke-width: 5;
+ transition: opacity 240ms ease;
+}
+
+.muse-voice path {
+ transform-box: fill-box;
+ transform-origin: center;
+}
+
+.muse-avatar[data-animation="listening"] .muse-character {
+ animation: attentive 2.4s ease-in-out infinite;
+}
+
+.muse-avatar[data-animation="listening"] .muse-ear-left {
+ transform: rotate(-7deg) translate(-2px, -3px);
+}
+
+.muse-avatar[data-animation="listening"] .muse-ear-right {
+ transform: rotate(7deg) translate(2px, -3px);
+}
+
+.muse-avatar[data-animation="listening"] .muse-eye {
+ transform: scaleY(1.08);
+}
+
+.muse-avatar[data-animation="thinking"] .muse-character {
+ animation: ponder 3.2s ease-in-out infinite;
+}
+
+.muse-avatar[data-animation="thinking"] .muse-face {
+ transform: translate(9px, -7px) rotate(2deg);
+}
+
+.muse-avatar[data-animation="thinking"] .muse-eye-right {
+ transform: scaleY(0.7);
+}
+
+.muse-avatar[data-animation="working"] .muse-character {
+ animation: working 1.5s ease-in-out infinite;
+}
+
+.muse-avatar[data-animation="working"] .muse-orbit {
+ animation-duration: 8s;
+}
+
+.muse-avatar[data-animation="happy"] .muse-character {
+ animation: speaking 800ms ease-in-out infinite alternate;
+}
+
+.muse-avatar[data-animation="happy"] .muse-eye {
+ transform: scaleY(0.82);
+}
+
+.muse-avatar[data-animation="happy"] .muse-voice {
+ opacity: 0.88;
+}
+
+.muse-avatar[data-animation="happy"] .muse-voice path:nth-child(odd) {
+ animation: waveform 620ms ease-in-out infinite alternate;
+}
+
+.muse-avatar[data-animation="happy"] .muse-voice path:nth-child(even) {
+ animation: waveform 780ms 120ms ease-in-out infinite alternate-reverse;
+}
+
+.tone-active .presence-halo {
+ opacity: 0.82;
+ scale: 1.045;
+}
+
+.tone-speaking .presence-halo {
+ opacity: 1;
+ scale: 1.09;
+}
+
+.tone-speaking .muse-avatar {
+ filter: drop-shadow(0 34px 38px rgba(6, 104, 225, 0.26));
+}
+
+.tone-error .muse-avatar {
+ filter: grayscale(0.15) drop-shadow(0 30px 30px rgba(158, 51, 47, 0.12));
+}
+
+.state-caption {
+ display: flex;
+ align-items: center;
+ min-height: 30px;
+ margin-top: -10px;
+ color: #40536f;
+ font-size: 0.84rem;
+ font-weight: 600;
+}
+
+.state-mark {
+ width: 7px;
+ height: 7px;
+ margin-right: 9px;
+ border-radius: 50%;
+ background: var(--meta-blue-soft);
+ box-shadow: 0 0 0 5px rgba(6, 104, 225, 0.13);
+}
+
+.tone-speaking .state-mark {
+ background: var(--meta-blue);
+ box-shadow: 0 0 0 5px rgba(6, 104, 225, 0.16);
+}
+
+.muted-note {
+ color: #52647d;
+ font-weight: 400;
+}
+
+.conversation-dock {
+ z-index: 3;
+ display: grid;
+ justify-items: center;
+ padding: 0 clamp(20px, 5vw, 72px)
+ max(28px, calc(env(safe-area-inset-bottom) + 20px));
+}
+
+.transcript {
+ display: grid;
+ align-items: end;
+ width: min(720px, 100%);
+ min-height: 84px;
+ max-height: 152px;
+ margin-bottom: 18px;
+ overflow: hidden;
+ mask-image: linear-gradient(to bottom, transparent 0, black 24%, black 100%);
+}
+
+.transcript-empty {
+ align-self: center;
+ margin: 0;
+ color: #52647d;
+ font-size: 1.05rem;
+ font-weight: 500;
+ text-align: center;
+}
+
+.transcript-list {
+ display: grid;
+ gap: 7px;
+ margin: 0;
+ padding: 20px 0 0;
+ list-style: none;
+}
+
+.transcript-line {
+ display: grid;
+ grid-template-columns: 48px minmax(0, 1fr);
+ gap: 12px;
+ margin: 0;
+ color: #40536f;
+ font-size: 0.9rem;
+ line-height: 1.45;
+}
+
+.transcript-line--agent {
+ color: var(--ink);
+}
+
+.transcript-speaker {
+ color: var(--meta-blue);
+ font-size: 0.67rem;
+ font-weight: 700;
+ letter-spacing: 0;
+ text-align: right;
+ text-transform: uppercase;
+}
+
+.transcript-line--agent .transcript-speaker {
+ color: var(--meta-blue-deep);
+}
+
+.transcript-interim {
+ color: #435672;
+ font-style: italic;
+}
+
+.controls-wrap {
+ display: grid;
+ justify-items: center;
+}
+
+.controls {
+ display: flex;
+ gap: 10px;
+ padding: 8px;
+ border: 1px solid rgba(6, 104, 225, 0.14);
+ border-radius: 999px;
+ background: rgba(247, 251, 255, 0.84);
+ box-shadow: 0 15px 45px rgba(6, 75, 150, 0.12);
+ backdrop-filter: blur(18px);
+}
+
+.control-button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ min-width: 98px;
+ min-height: 48px;
+ padding: 0 18px;
+ color: var(--ink);
+ border: 0;
+ border-radius: 999px;
+ background: transparent;
+ cursor: pointer;
+ transition:
+ color 180ms ease,
+ background 180ms ease,
+ transform 180ms ease;
+}
+
+.control-button:hover:not(:disabled) {
+ background: rgba(62, 78, 101, 0.08);
+ transform: translateY(-1px);
+}
+
+.control-button:disabled {
+ cursor: wait;
+ opacity: 0.55;
+}
+
+.control-button svg,
+.start-button svg {
+ width: 20px;
+ fill: none;
+ stroke: currentColor;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+ stroke-width: 1.8;
+}
+
+.control-button--mic.is-muted {
+ color: #7a3f3a;
+ background: rgba(226, 96, 85, 0.12);
+}
+
+.control-button--end {
+ color: #fff;
+ background: var(--meta-blue);
+}
+
+.control-button--end:hover:not(:disabled) {
+ background: var(--meta-blue-deep);
+}
+
+.control-error,
+.welcome-error {
+ margin: 0 0 12px;
+ color: var(--error);
+ font-size: 0.78rem;
+ text-align: center;
+}
+
+.audio-unlock {
+ min-height: 42px;
+ margin-bottom: 10px;
+ padding: 0 18px;
+ color: var(--ink);
+ border: 1px solid rgba(62, 78, 101, 0.18);
+ border-radius: 999px;
+ background: var(--paper);
+ cursor: pointer;
+}
+
+.welcome-presence {
+ align-content: center;
+ padding-bottom: 8px;
+}
+
+.welcome-presence .muse-avatar {
+ width: clamp(270px, 36vw, 500px);
+}
+
+.welcome-copy {
+ width: min(560px, 92vw);
+ margin-top: -18px;
+ text-align: center;
+}
+
+.welcome-copy h2 {
+ display: grid;
+ gap: 6px;
+ margin: 0;
+ font-size: 2.2rem;
+ font-weight: 600;
+ letter-spacing: 0;
+ line-height: 1.1;
+}
+
+.welcome-copy h2 span {
+ display: block;
+}
+
+.welcome-copy p {
+ width: min(460px, 100%);
+ margin: 10px auto 0;
+ color: #485c77;
+ font-size: 0.91rem;
+ line-height: 1.55;
+}
+
+.welcome-actions {
+ z-index: 3;
+ display: grid;
+ justify-items: center;
+ padding: 12px 24px max(36px, calc(env(safe-area-inset-bottom) + 22px));
+}
+
+.privacy-note {
+ margin: 0 0 13px;
+ color: #52647d;
+ font-size: 0.72rem;
+}
+
+.start-button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ min-height: 58px;
+ padding: 0 22px 0 26px;
+ color: #fff;
+ border: 0;
+ border-radius: 999px;
+ background: var(--meta-blue);
+ box-shadow: 0 18px 38px rgba(6, 104, 225, 0.24);
+ cursor: pointer;
+ font-weight: 600;
+ transition:
+ background 180ms ease,
+ transform 180ms ease,
+ box-shadow 180ms ease;
+}
+
+.start-button:hover:not(:disabled) {
+ background: var(--meta-blue-deep);
+ box-shadow: 0 20px 42px rgba(6, 80, 179, 0.3);
+ transform: translateY(-2px);
+}
+
+.start-button:disabled {
+ cursor: wait;
+ opacity: 0.72;
+}
+
+@keyframes breathe {
+ 0%,
+ 100% {
+ transform: translateY(0) scale(1);
+ }
+ 50% {
+ transform: translateY(-4px) scale(1.008);
+ }
+}
+
+@keyframes blink {
+ 0%,
+ 44%,
+ 48%,
+ 100% {
+ transform: scaleY(1);
+ }
+ 46% {
+ transform: scaleY(0.08);
+ }
+}
+
+@keyframes orbit {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@keyframes attentive {
+ 0%,
+ 100% {
+ transform: translateY(0) rotate(-1deg);
+ }
+ 50% {
+ transform: translateY(-7px) rotate(1deg);
+ }
+}
+
+@keyframes ponder {
+ 0%,
+ 100% {
+ transform: rotate(-2deg) translateY(0);
+ }
+ 50% {
+ transform: rotate(3deg) translateY(-5px);
+ }
+}
+
+@keyframes working {
+ 0%,
+ 100% {
+ transform: scale(0.985);
+ }
+ 50% {
+ transform: scale(1.018);
+ }
+}
+
+@keyframes speaking {
+ from {
+ transform: translateY(1px) scale(0.995);
+ }
+ to {
+ transform: translateY(-5px) scale(1.015);
+ }
+}
+
+@keyframes waveform {
+ from {
+ transform: scaleY(0.45);
+ }
+ to {
+ transform: scaleY(1.15);
+ }
+}
+
+@media (max-height: 740px) and (min-width: 681px) {
+ .muse-avatar,
+ .welcome-presence .muse-avatar {
+ width: min(38vh, 340px);
+ }
+
+ .presence-halo {
+ width: min(48vh, 430px);
+ }
+
+ .transcript {
+ min-height: 65px;
+ max-height: 95px;
+ }
+
+ .welcome-copy h2 {
+ font-size: 1.8rem;
+ }
+}
+
+@media (max-width: 680px) {
+ .voice-shell {
+ background-size:
+ 36px 36px,
+ 36px 36px,
+ auto;
+ }
+
+ .app-header {
+ grid-template-columns: minmax(0, 1fr);
+ justify-items: center;
+ gap: 14px;
+ padding-right: 20px;
+ padding-left: 20px;
+ }
+
+ .header-title {
+ grid-column: 1;
+ }
+
+ .app-header h1 {
+ max-width: 100%;
+ font-size: 1.55rem;
+ line-height: 1.05;
+ }
+
+ .runtime-badge {
+ gap: 7px;
+ min-height: 36px;
+ padding: 4px 11px 4px 5px;
+ font-size: 0.68rem;
+ }
+
+ .runtime-chip {
+ width: 26px;
+ height: 26px;
+ border-radius: 7px;
+ font-size: 0.48rem;
+ }
+
+ .runtime-badge strong {
+ font-size: 0.75rem;
+ }
+
+ .presence {
+ padding: 8px 16px;
+ }
+
+ .presence-halo {
+ width: min(94vw, 470px);
+ }
+
+ .muse-avatar,
+ .welcome-presence .muse-avatar {
+ width: min(77vw, 380px);
+ }
+
+ .welcome-copy {
+ margin-top: -8px;
+ }
+
+ .welcome-copy h2 {
+ font-size: 1.7rem;
+ }
+
+ .welcome-copy p {
+ font-size: 0.84rem;
+ }
+
+ .conversation-dock {
+ padding-right: 16px;
+ padding-left: 16px;
+ }
+
+ .transcript {
+ min-height: 76px;
+ max-height: 126px;
+ }
+
+ .transcript-line {
+ grid-template-columns: 42px minmax(0, 1fr);
+ gap: 9px;
+ font-size: 0.82rem;
+ }
+
+ .privacy-note {
+ max-width: 260px;
+ text-align: center;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ scroll-behavior: auto !important;
+ transition-duration: 0.01ms !important;
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ }
+}
+
+.muse-avatar[data-reduced-motion="true"] * {
+ animation: none !important;
+ transition: none !important;
+}
diff --git a/muse_glimmer/macos/apps/web/src/avatar/MuseAvatar.test.tsx b/muse_glimmer/macos/apps/web/src/avatar/MuseAvatar.test.tsx
new file mode 100644
index 0000000000..f3ceeca405
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/src/avatar/MuseAvatar.test.tsx
@@ -0,0 +1,34 @@
+import { render, screen } from "@testing-library/react";
+import { describe, expect, it } from "vitest";
+
+import { MuseAvatar } from "./MuseAvatar";
+
+describe("Muse avatar", () => {
+ it("describes the current voice state to assistive technology", () => {
+ render( );
+
+ expect(
+ screen.getByRole("img", { name: /Muse Glimmer/i }),
+ ).toHaveAccessibleDescription(
+ "An abstract blue voice companion, currently listening.",
+ );
+ });
+
+ it("holds the visual animation at idle when reduced motion is requested", () => {
+ const { container } = render(
+ ,
+ );
+
+ expect(container.firstElementChild).toHaveAttribute(
+ "data-animation",
+ "idle",
+ );
+ expect(container.firstElementChild).toHaveAttribute(
+ "data-reduced-motion",
+ "true",
+ );
+ expect(screen.getByRole("img")).toHaveAccessibleDescription(
+ "An abstract blue voice companion, currently speaking.",
+ );
+ });
+});
diff --git a/muse_glimmer/macos/apps/web/src/avatar/MuseAvatar.tsx b/muse_glimmer/macos/apps/web/src/avatar/MuseAvatar.tsx
new file mode 100644
index 0000000000..b2720d7c83
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/src/avatar/MuseAvatar.tsx
@@ -0,0 +1,128 @@
+import { useId } from "react";
+
+import type { MuseAnimation } from "../lib/agentPresentation";
+
+interface MuseAvatarProps {
+ animation: MuseAnimation;
+ reducedMotion: boolean;
+}
+
+const animationLabels: Record = {
+ idle: "ready",
+ listening: "listening",
+ thinking: "thinking",
+ working: "connecting",
+ happy: "speaking",
+};
+
+export function MuseAvatar({ animation, reducedMotion }: MuseAvatarProps) {
+ const titleId = useId();
+ const descriptionId = useId();
+ const visibleAnimation = reducedMotion ? "idle" : animation;
+
+ return (
+
+
+ Muse Glimmer
+
+ An abstract blue voice companion, currently{" "}
+ {animationLabels[animation]}.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/muse_glimmer/macos/apps/web/src/components/CompactTranscript.test.tsx b/muse_glimmer/macos/apps/web/src/components/CompactTranscript.test.tsx
new file mode 100644
index 0000000000..fa7c7ec05f
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/src/components/CompactTranscript.test.tsx
@@ -0,0 +1,51 @@
+import { render, screen } from "@testing-library/react";
+import { describe, expect, it } from "vitest";
+
+import type { TranscriptEntry } from "../lib/transcript";
+import { CompactTranscript } from "./CompactTranscript";
+
+function entry(
+ index: number,
+ speaker: "user" | "agent" = "user",
+): TranscriptEntry {
+ return {
+ key: `${speaker}-${index}`,
+ segmentId: String(index),
+ participantIdentity: speaker,
+ speaker,
+ text: `Line ${index}`,
+ final: index % 2 === 0,
+ order: index,
+ };
+}
+
+describe("compact transcript", () => {
+ it("gives useful empty guidance for microphone state", () => {
+ const { rerender } = render(
+ ,
+ );
+ expect(screen.getByText("Say something to begin.")).toBeVisible();
+
+ rerender( );
+ expect(
+ screen.getByText("Unmute when you are ready to speak."),
+ ).toBeVisible();
+ });
+
+ it("renders only the six newest entries with speaker labels and interim styling", () => {
+ render(
+
+ entry(index, index % 2 ? "agent" : "user"),
+ )}
+ isMuted={false}
+ />,
+ );
+
+ expect(screen.queryByText("Line 1")).not.toBeInTheDocument();
+ expect(screen.getByText("Line 2")).toBeVisible();
+ expect(screen.getByText("Line 7")).toHaveClass("transcript-interim");
+ expect(screen.getAllByText("Muse")).toHaveLength(3);
+ expect(screen.getAllByText("You")).toHaveLength(3);
+ });
+});
diff --git a/muse_glimmer/macos/apps/web/src/components/CompactTranscript.tsx b/muse_glimmer/macos/apps/web/src/components/CompactTranscript.tsx
new file mode 100644
index 0000000000..6d620d1a5f
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/src/components/CompactTranscript.tsx
@@ -0,0 +1,45 @@
+import type { TranscriptEntry } from "../lib/transcript";
+
+interface CompactTranscriptProps {
+ entries: TranscriptEntry[];
+ isMuted: boolean;
+}
+
+export function CompactTranscript({
+ entries,
+ isMuted,
+}: CompactTranscriptProps) {
+ const visibleEntries = entries.slice(-6);
+
+ return (
+
+ {visibleEntries.length === 0 ? (
+
+ {isMuted
+ ? "Unmute when you are ready to speak."
+ : "Say something to begin."}
+
+ ) : (
+
+ {visibleEntries.map((entry) => (
+
+
+ {entry.speaker === "user" ? "You" : "Muse"}
+
+
+ {entry.text}
+
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/muse_glimmer/macos/apps/web/src/components/RuntimeBadge.tsx b/muse_glimmer/macos/apps/web/src/components/RuntimeBadge.tsx
new file mode 100644
index 0000000000..7c4cfc95a6
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/src/components/RuntimeBadge.tsx
@@ -0,0 +1,12 @@
+export function RuntimeBadge() {
+ return (
+
+
+ ET
+
+
+ Running on ExecuTorch
+
+
+ );
+}
diff --git a/muse_glimmer/macos/apps/web/src/components/SessionControls.test.tsx b/muse_glimmer/macos/apps/web/src/components/SessionControls.test.tsx
new file mode 100644
index 0000000000..264d45491a
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/src/components/SessionControls.test.tsx
@@ -0,0 +1,76 @@
+import { fireEvent, render, screen, waitFor } from "@testing-library/react";
+import { afterEach, describe, expect, it, vi } from "vitest";
+
+const disconnect = vi.fn();
+const setMicrophoneEnabled = vi.fn();
+let isMicrophoneEnabled = true;
+
+vi.mock("@livekit/components-react", () => ({
+ useRoomContext: () => ({ disconnect }),
+ useLocalParticipant: () => ({
+ isMicrophoneEnabled,
+ localParticipant: { setMicrophoneEnabled },
+ }),
+}));
+
+import { SessionControls } from "./SessionControls";
+
+afterEach(() => {
+ disconnect.mockReset();
+ setMicrophoneEnabled.mockReset();
+ isMicrophoneEnabled = true;
+});
+
+describe("session controls", () => {
+ it("mutes an active microphone", async () => {
+ setMicrophoneEnabled.mockResolvedValue(undefined);
+ render( );
+
+ fireEvent.click(screen.getByRole("button", { name: "Mute" }));
+
+ await waitFor(() =>
+ expect(setMicrophoneEnabled).toHaveBeenCalledWith(false),
+ );
+ expect(disconnect).not.toHaveBeenCalled();
+ });
+
+ it("shows a safe error when microphone control fails", async () => {
+ setMicrophoneEnabled.mockRejectedValue(new Error("device details"));
+ render( );
+
+ fireEvent.click(screen.getByRole("button", { name: "Mute" }));
+
+ expect(await screen.findByRole("alert")).toHaveTextContent(
+ "Microphone access was not available.",
+ );
+ });
+
+ it("completes local teardown when disconnect rejects", async () => {
+ const onEnding = vi.fn();
+ const onEnded = vi.fn();
+ setMicrophoneEnabled.mockResolvedValue(undefined);
+ disconnect.mockRejectedValue(new Error("transport closed"));
+ render( );
+
+ fireEvent.click(screen.getByRole("button", { name: "End" }));
+
+ await waitFor(() => expect(onEnded).toHaveBeenCalledOnce());
+ expect(onEnding).toHaveBeenCalledOnce();
+ expect(disconnect).toHaveBeenCalledOnce();
+ });
+
+ it("disables the microphone, disconnects, and reports a completed end action", async () => {
+ const onEnding = vi.fn();
+ const onEnded = vi.fn();
+ setMicrophoneEnabled.mockResolvedValue(undefined);
+ disconnect.mockResolvedValue(undefined);
+ render( );
+
+ fireEvent.click(screen.getByRole("button", { name: "End" }));
+
+ await waitFor(() => expect(onEnded).toHaveBeenCalledOnce());
+ expect(onEnding).toHaveBeenCalledOnce();
+ expect(setMicrophoneEnabled).toHaveBeenCalledWith(false);
+ expect(disconnect).toHaveBeenCalledOnce();
+ });
+});
diff --git a/muse_glimmer/macos/apps/web/src/components/SessionControls.tsx b/muse_glimmer/macos/apps/web/src/components/SessionControls.tsx
new file mode 100644
index 0000000000..c8bc11f9a3
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/src/components/SessionControls.tsx
@@ -0,0 +1,94 @@
+import { useLocalParticipant, useRoomContext } from "@livekit/components-react";
+import { useState } from "react";
+
+interface SessionControlsProps {
+ onEnding: () => void;
+ onEnded: () => void;
+}
+
+export function SessionControls({ onEnding, onEnded }: SessionControlsProps) {
+ const room = useRoomContext();
+ const { isMicrophoneEnabled, localParticipant } = useLocalParticipant();
+ const [pendingAction, setPendingAction] = useState<"microphone" | "end">();
+ const [controlError, setControlError] = useState();
+
+ const toggleMicrophone = async () => {
+ setPendingAction("microphone");
+ setControlError(undefined);
+ try {
+ await localParticipant.setMicrophoneEnabled(!isMicrophoneEnabled);
+ } catch {
+ setControlError("Microphone access was not available.");
+ } finally {
+ setPendingAction(undefined);
+ }
+ };
+
+ const endConversation = async () => {
+ setPendingAction("end");
+ onEnding();
+ try {
+ await localParticipant.setMicrophoneEnabled(false);
+ } catch {
+ // Disconnect even when the browser has already removed the track.
+ }
+ try {
+ await room.disconnect();
+ } catch {
+ // Local teardown still completes when the transport has already failed.
+ } finally {
+ onEnded();
+ }
+ };
+
+ return (
+
+ {controlError ? (
+
+ {controlError}
+
+ ) : null}
+
+ void toggleMicrophone()}
+ disabled={pendingAction !== undefined}
+ aria-pressed={!isMicrophoneEnabled}
+ >
+
+ {isMicrophoneEnabled ? "Mute" : "Unmute"}
+
+ void endConversation()}
+ disabled={pendingAction !== undefined}
+ >
+
+ End
+
+
+
+ );
+}
+
+function MicrophoneIcon({ muted }: { muted: boolean }) {
+ return (
+
+ {muted ? (
+
+ ) : (
+
+ )}
+
+ );
+}
+
+function EndIcon() {
+ return (
+
+
+
+ );
+}
diff --git a/muse_glimmer/macos/apps/web/src/components/VoiceSession.tsx b/muse_glimmer/macos/apps/web/src/components/VoiceSession.tsx
new file mode 100644
index 0000000000..48e291aa33
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/src/components/VoiceSession.tsx
@@ -0,0 +1,84 @@
+import {
+ RoomAudioRenderer,
+ StartAudio,
+ useConnectionState,
+ useLocalParticipant,
+} from "@livekit/components-react";
+import { ConnectionState } from "livekit-client";
+
+import { MuseAvatar } from "../avatar/MuseAvatar";
+import { useNamedAgentState } from "../hooks/useNamedAgentState";
+import { usePrefersReducedMotion } from "../hooks/usePrefersReducedMotion";
+import { useTranscriptionSegments } from "../hooks/useTranscriptionSegments";
+import { getAgentPresentation } from "../lib/agentPresentation";
+import { CompactTranscript } from "./CompactTranscript";
+import { RuntimeBadge } from "./RuntimeBadge";
+import { SessionControls } from "./SessionControls";
+
+interface VoiceSessionProps {
+ participantIdentity: string;
+ onEnding: () => void;
+ onEnded: () => void;
+}
+
+export function VoiceSession({
+ participantIdentity,
+ onEnding,
+ onEnded,
+}: VoiceSessionProps) {
+ const connectionState = useConnectionState();
+ const { isMicrophoneEnabled } = useLocalParticipant();
+ const namedAgent = useNamedAgentState();
+ const transcript = useTranscriptionSegments(participantIdentity);
+ const reducedMotion = usePrefersReducedMotion();
+ const presentation = getAgentPresentation(
+ "active",
+ connectionState === ConnectionState.Connected,
+ namedAgent.hasNamedAgent,
+ namedAgent.agentState,
+ );
+
+ return (
+
+
+
+
+
+
+
+ {presentation.status}
+ {!isMicrophoneEnabled ? (
+ | Microphone muted
+ ) : null}
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function Header({ status }: { status: string }) {
+ return (
+
+
+
+
Local Voice Agent
+
Talk with Muse Glimmer
+
+ {status}
+
+
+
+ );
+}
diff --git a/muse_glimmer/macos/apps/web/src/config.ts b/muse_glimmer/macos/apps/web/src/config.ts
new file mode 100644
index 0000000000..be06ac849d
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/src/config.ts
@@ -0,0 +1,3 @@
+export const AGENT_NAME = "assistant";
+export const TOKEN_ENDPOINT = "http://127.0.0.1:8787/api/token";
+export const LIVEKIT_SERVER_URL = "ws://127.0.0.1:7880";
diff --git a/muse_glimmer/macos/apps/web/src/hooks/useNamedAgentState.ts b/muse_glimmer/macos/apps/web/src/hooks/useNamedAgentState.ts
new file mode 100644
index 0000000000..b71c50bb9a
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/src/hooks/useNamedAgentState.ts
@@ -0,0 +1,24 @@
+import { useVoiceAssistant } from "@livekit/components-react";
+
+import { AGENT_NAME } from "../config";
+import { normalizeAgentState, type AgentState } from "../lib/agentPresentation";
+
+export interface NamedAgentState {
+ hasNamedAgent: boolean;
+ agentState?: AgentState;
+ agentIdentity?: string;
+}
+
+export function useNamedAgentState(): NamedAgentState {
+ const assistant = useVoiceAssistant();
+ const agentName = assistant.agent?.attributes["lk.agent.name"];
+ const hasNamedAgent = agentName === AGENT_NAME;
+
+ return {
+ hasNamedAgent,
+ agentState: hasNamedAgent
+ ? normalizeAgentState(assistant.state)
+ : undefined,
+ agentIdentity: hasNamedAgent ? assistant.agent?.identity : undefined,
+ };
+}
diff --git a/muse_glimmer/macos/apps/web/src/hooks/usePrefersReducedMotion.ts b/muse_glimmer/macos/apps/web/src/hooks/usePrefersReducedMotion.ts
new file mode 100644
index 0000000000..d039556f17
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/src/hooks/usePrefersReducedMotion.ts
@@ -0,0 +1,18 @@
+import { useEffect, useState } from "react";
+
+export function usePrefersReducedMotion(): boolean {
+ const [reducedMotion, setReducedMotion] = useState(() =>
+ typeof window === "undefined"
+ ? false
+ : window.matchMedia("(prefers-reduced-motion: reduce)").matches,
+ );
+
+ useEffect(() => {
+ const media = window.matchMedia("(prefers-reduced-motion: reduce)");
+ const update = () => setReducedMotion(media.matches);
+ media.addEventListener("change", update);
+ return () => media.removeEventListener("change", update);
+ }, []);
+
+ return reducedMotion;
+}
diff --git a/muse_glimmer/macos/apps/web/src/hooks/useTranscriptionSegments.ts b/muse_glimmer/macos/apps/web/src/hooks/useTranscriptionSegments.ts
new file mode 100644
index 0000000000..7bdb9c40f2
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/src/hooks/useTranscriptionSegments.ts
@@ -0,0 +1,59 @@
+import { useRoomContext } from "@livekit/components-react";
+import {
+ RoomEvent,
+ type Participant,
+ type TranscriptionSegment,
+} from "livekit-client";
+import { useEffect, useState } from "react";
+
+import { AGENT_NAME } from "../config";
+import {
+ mergeTranscriptUpdates,
+ type TranscriptEntry,
+ type TranscriptUpdate,
+} from "../lib/transcript";
+
+export function useTranscriptionSegments(
+ localParticipantIdentity: string,
+): TranscriptEntry[] {
+ const room = useRoomContext();
+ const [entries, setEntries] = useState([]);
+
+ useEffect(() => {
+ const handleTranscription = (
+ segments: TranscriptionSegment[],
+ participant?: Participant,
+ ) => {
+ if (!participant) return;
+
+ const speaker =
+ participant.identity === localParticipantIdentity
+ ? "user"
+ : participant.attributes["lk.agent.name"] === AGENT_NAME
+ ? "agent"
+ : undefined;
+ if (!speaker) return;
+
+ const updates: TranscriptUpdate[] = segments
+ .filter((segment) => segment.text.trim().length > 0)
+ .map((segment) => ({
+ segmentId: segment.id,
+ participantIdentity: participant.identity,
+ speaker,
+ text: segment.text,
+ final: segment.final,
+ }));
+
+ if (updates.length > 0) {
+ setEntries((current) => mergeTranscriptUpdates(current, updates));
+ }
+ };
+
+ room.on(RoomEvent.TranscriptionReceived, handleTranscription);
+ return () => {
+ room.off(RoomEvent.TranscriptionReceived, handleTranscription);
+ };
+ }, [localParticipantIdentity, room]);
+
+ return entries;
+}
diff --git a/muse_glimmer/macos/apps/web/src/lib/agentPresentation.test.ts b/muse_glimmer/macos/apps/web/src/lib/agentPresentation.test.ts
new file mode 100644
index 0000000000..7cde56e3ff
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/src/lib/agentPresentation.test.ts
@@ -0,0 +1,47 @@
+import { describe, expect, it } from "vitest";
+
+import { getAgentPresentation, normalizeAgentState } from "./agentPresentation";
+
+describe("agent presentation", () => {
+ it.each([
+ ["idle", "idle", "Ready when you are", "ready"],
+ ["listening", "listening", "Listening", "active"],
+ ["thinking", "thinking", "Thinking", "active"],
+ ["speaking", "happy", "Speaking", "speaking"],
+ ] as const)(
+ "maps %s to its visible voice state",
+ (state, animation, status, tone) => {
+ expect(getAgentPresentation("active", true, true, state)).toEqual({
+ animation,
+ status,
+ tone,
+ });
+ },
+ );
+
+ it("prioritizes connection and named agent discovery", () => {
+ expect(getAgentPresentation("active", false, false)).toMatchObject({
+ status: "Joining",
+ });
+ expect(getAgentPresentation("active", true, false)).toMatchObject({
+ status: "Waking up Muse",
+ });
+ });
+
+ it("represents requesting and error phases independently of the room", () => {
+ expect(getAgentPresentation("requesting", false, false)).toMatchObject({
+ animation: "working",
+ status: "Preparing your conversation",
+ });
+ expect(getAgentPresentation("error", false, false)).toMatchObject({
+ status: "Could not connect",
+ tone: "error",
+ });
+ });
+
+ it("normalizes only known states", () => {
+ expect(normalizeAgentState("thinking")).toBe("thinking");
+ expect(normalizeAgentState("disconnected")).toBeUndefined();
+ expect(normalizeAgentState({ state: "idle" })).toBeUndefined();
+ });
+});
diff --git a/muse_glimmer/macos/apps/web/src/lib/agentPresentation.ts b/muse_glimmer/macos/apps/web/src/lib/agentPresentation.ts
new file mode 100644
index 0000000000..13f034bbee
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/src/lib/agentPresentation.ts
@@ -0,0 +1,80 @@
+export type AgentState =
+ | "initializing"
+ | "idle"
+ | "listening"
+ | "thinking"
+ | "speaking";
+export type SessionPhase =
+ | "idle"
+ | "requesting"
+ | "active"
+ | "ending"
+ | "error";
+export type MuseAnimation =
+ | "idle"
+ | "listening"
+ | "thinking"
+ | "working"
+ | "happy";
+
+export interface AgentPresentation {
+ animation: MuseAnimation;
+ status: string;
+ tone: "ready" | "active" | "speaking" | "error";
+}
+
+export function normalizeAgentState(value: unknown): AgentState | undefined {
+ switch (value) {
+ case "initializing":
+ case "idle":
+ case "listening":
+ case "thinking":
+ case "speaking":
+ return value;
+ default:
+ return undefined;
+ }
+}
+
+export function getAgentPresentation(
+ phase: SessionPhase,
+ isConnected: boolean,
+ hasNamedAgent: boolean,
+ agentState?: AgentState,
+): AgentPresentation {
+ if (phase === "error") {
+ return { animation: "idle", status: "Could not connect", tone: "error" };
+ }
+ if (phase === "requesting" || phase === "ending") {
+ return {
+ animation: "working",
+ status:
+ phase === "requesting"
+ ? "Preparing your conversation"
+ : "Ending conversation",
+ tone: "active",
+ };
+ }
+ if (phase === "idle") {
+ return { animation: "idle", status: "Ready to talk", tone: "ready" };
+ }
+ if (!isConnected) {
+ return { animation: "working", status: "Joining", tone: "active" };
+ }
+ if (!hasNamedAgent || agentState === "initializing") {
+ return { animation: "working", status: "Waking up Muse", tone: "active" };
+ }
+
+ switch (agentState) {
+ case "idle":
+ return { animation: "idle", status: "Ready when you are", tone: "ready" };
+ case "listening":
+ return { animation: "listening", status: "Listening", tone: "active" };
+ case "thinking":
+ return { animation: "thinking", status: "Thinking", tone: "active" };
+ case "speaking":
+ return { animation: "happy", status: "Speaking", tone: "speaking" };
+ default:
+ return { animation: "working", status: "Working", tone: "active" };
+ }
+}
diff --git a/muse_glimmer/macos/apps/web/src/lib/tokenClient.test.ts b/muse_glimmer/macos/apps/web/src/lib/tokenClient.test.ts
new file mode 100644
index 0000000000..231caf0f1b
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/src/lib/tokenClient.test.ts
@@ -0,0 +1,97 @@
+import { afterEach, describe, expect, it, vi } from "vitest";
+
+import { LIVEKIT_SERVER_URL, TOKEN_ENDPOINT } from "../config";
+import { requestConnection } from "./tokenClient";
+
+const validConnection = {
+ serverUrl: LIVEKIT_SERVER_URL,
+ participantToken: "local-token",
+ roomName: "glimmer-one",
+ participantIdentity: "web-one",
+};
+
+function mockResponse(body: unknown, status = 200) {
+ vi.stubGlobal(
+ "fetch",
+ vi.fn().mockResolvedValue(
+ new Response(JSON.stringify(body), {
+ status,
+ headers: { "Content-Type": "application/json" },
+ }),
+ ),
+ );
+}
+
+afterEach(() => vi.restoreAllMocks());
+
+describe("token client", () => {
+ it("posts to the fixed local endpoint without credentials or referrer data", async () => {
+ mockResponse(validConnection);
+
+ await expect(requestConnection()).resolves.toEqual(validConnection);
+ expect(fetch).toHaveBeenCalledOnce();
+ expect(fetch).toHaveBeenCalledWith(
+ TOKEN_ENDPOINT,
+ expect.objectContaining({
+ method: "POST",
+ cache: "no-store",
+ credentials: "omit",
+ referrerPolicy: "no-referrer",
+ headers: { Accept: "application/json" },
+ }),
+ );
+ });
+
+ it.each([
+ "wss://127.0.0.1:7880",
+ "ws://localhost:7880",
+ "ws://127.0.0.1:7881",
+ "ws://192.168.1.5:7880",
+ ])("rejects the unapproved LiveKit URL %s", async (serverUrl) => {
+ mockResponse({ ...validConnection, serverUrl });
+
+ await expect(requestConnection()).rejects.toThrow("unapproved media URL");
+ });
+
+ it("rejects unknown response fields", async () => {
+ mockResponse({
+ ...validConnection,
+ debug: "should-not-cross-the-browser-boundary",
+ });
+
+ await expect(requestConnection()).rejects.toThrow("unexpected response");
+ });
+
+ it("rejects missing or empty approved fields without exposing response values", async () => {
+ mockResponse({ ...validConnection, participantToken: " " });
+
+ await expect(requestConnection()).rejects.toThrow("incomplete response");
+ });
+
+ it("uses a generic error when the service returns malformed JSON", async () => {
+ vi.stubGlobal(
+ "fetch",
+ vi.fn().mockResolvedValue(new Response("{", { status: 200 })),
+ );
+
+ await expect(requestConnection()).rejects.toThrow("invalid response");
+ });
+
+ it("uses a generic error when the local service is unavailable", async () => {
+ vi.stubGlobal(
+ "fetch",
+ vi.fn().mockRejectedValue(new TypeError("connection refused")),
+ );
+
+ await expect(requestConnection()).rejects.toThrow(
+ "local connection service is not available",
+ );
+ });
+
+ it("preserves abort errors for cancellation handling", async () => {
+ const abortError = new DOMException("aborted", "AbortError");
+ vi.stubGlobal("fetch", vi.fn().mockRejectedValue(abortError));
+
+ await expect(requestConnection()).rejects.toBe(abortError);
+ });
+});
diff --git a/muse_glimmer/macos/apps/web/src/lib/tokenClient.ts b/muse_glimmer/macos/apps/web/src/lib/tokenClient.ts
new file mode 100644
index 0000000000..7f9cbe64dd
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/src/lib/tokenClient.ts
@@ -0,0 +1,95 @@
+import { LIVEKIT_SERVER_URL, TOKEN_ENDPOINT } from "../config";
+
+export interface ConnectionDetails {
+ serverUrl: string;
+ participantToken: string;
+ roomName: string;
+ participantIdentity: string;
+}
+
+const RESPONSE_FIELDS = [
+ "participantIdentity",
+ "participantToken",
+ "roomName",
+ "serverUrl",
+] as const;
+
+const isNonEmptyString = (value: unknown): value is string =>
+ typeof value === "string" && value.trim().length > 0;
+
+export async function requestConnection(
+ signal?: AbortSignal,
+): Promise {
+ let response: Response;
+ try {
+ response = await fetch(TOKEN_ENDPOINT, {
+ method: "POST",
+ cache: "no-store",
+ credentials: "omit",
+ headers: { Accept: "application/json" },
+ referrerPolicy: "no-referrer",
+ signal,
+ });
+ } catch (error) {
+ if (error instanceof DOMException && error.name === "AbortError") {
+ throw error;
+ }
+ throw new Error("The local connection service is not available.");
+ }
+
+ if (!response.ok) {
+ throw new Error(
+ "The local connection service could not start a conversation.",
+ );
+ }
+
+ let body: unknown;
+ try {
+ body = await response.json();
+ } catch {
+ throw new Error(
+ "The local connection service returned an invalid response.",
+ );
+ }
+
+ if (typeof body !== "object" || body === null || Array.isArray(body)) {
+ throw new Error(
+ "The local connection service returned an invalid response.",
+ );
+ }
+
+ const candidate = body as Record;
+ const responseFields = Object.keys(candidate).sort();
+ if (
+ responseFields.length !== RESPONSE_FIELDS.length ||
+ responseFields.some((field, index) => field !== RESPONSE_FIELDS[index])
+ ) {
+ throw new Error(
+ "The local connection service returned an unexpected response.",
+ );
+ }
+
+ if (
+ !isNonEmptyString(candidate.serverUrl) ||
+ !isNonEmptyString(candidate.participantToken) ||
+ !isNonEmptyString(candidate.roomName) ||
+ !isNonEmptyString(candidate.participantIdentity)
+ ) {
+ throw new Error(
+ "The local connection service returned an incomplete response.",
+ );
+ }
+
+ if (candidate.serverUrl !== LIVEKIT_SERVER_URL) {
+ throw new Error(
+ "The local connection service returned an unapproved media URL.",
+ );
+ }
+
+ return {
+ serverUrl: candidate.serverUrl,
+ participantToken: candidate.participantToken,
+ roomName: candidate.roomName,
+ participantIdentity: candidate.participantIdentity,
+ };
+}
diff --git a/muse_glimmer/macos/apps/web/src/lib/transcript.test.ts b/muse_glimmer/macos/apps/web/src/lib/transcript.test.ts
new file mode 100644
index 0000000000..2f9b033d04
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/src/lib/transcript.test.ts
@@ -0,0 +1,79 @@
+import { describe, expect, it } from "vitest";
+
+import { mergeTranscriptUpdates } from "./transcript";
+
+describe("transcript reducer", () => {
+ it("replaces interim text and preserves its order when final", () => {
+ const interim = mergeTranscriptUpdates(
+ [],
+ [
+ {
+ segmentId: "one",
+ participantIdentity: "web-1",
+ speaker: "user",
+ text: "what is the",
+ final: false,
+ },
+ ],
+ );
+ const final = mergeTranscriptUpdates(interim, [
+ {
+ segmentId: "one",
+ participantIdentity: "web-1",
+ speaker: "user",
+ text: "What is the weather like?",
+ final: true,
+ },
+ ]);
+
+ expect(final).toHaveLength(1);
+ expect(final[0]).toMatchObject({
+ text: "What is the weather like?",
+ final: true,
+ order: 0,
+ });
+ });
+
+ it("keeps reused segment ids distinct by participant", () => {
+ const transcript = mergeTranscriptUpdates(
+ [],
+ [
+ {
+ segmentId: "one",
+ participantIdentity: "web-1",
+ speaker: "user",
+ text: "Hello",
+ final: true,
+ },
+ {
+ segmentId: "one",
+ participantIdentity: "agent-1",
+ speaker: "agent",
+ text: "Hi",
+ final: true,
+ },
+ ],
+ );
+
+ expect(transcript.map((entry) => entry.key)).toEqual([
+ "web-1:one",
+ "agent-1:one",
+ ]);
+ });
+
+ it("bounds history to the newest entries", () => {
+ const transcript = mergeTranscriptUpdates(
+ [],
+ Array.from({ length: 4 }, (_, index) => ({
+ segmentId: String(index),
+ participantIdentity: "web-1",
+ speaker: "user" as const,
+ text: String(index),
+ final: true,
+ })),
+ 2,
+ );
+
+ expect(transcript.map((entry) => entry.text)).toEqual(["2", "3"]);
+ });
+});
diff --git a/muse_glimmer/macos/apps/web/src/lib/transcript.ts b/muse_glimmer/macos/apps/web/src/lib/transcript.ts
new file mode 100644
index 0000000000..a127be1e10
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/src/lib/transcript.ts
@@ -0,0 +1,47 @@
+export type TranscriptSpeaker = "user" | "agent";
+
+export interface TranscriptEntry {
+ key: string;
+ segmentId: string;
+ participantIdentity: string;
+ speaker: TranscriptSpeaker;
+ text: string;
+ final: boolean;
+ order: number;
+}
+
+export interface TranscriptUpdate {
+ segmentId: string;
+ participantIdentity: string;
+ speaker: TranscriptSpeaker;
+ text: string;
+ final: boolean;
+}
+
+export function mergeTranscriptUpdates(
+ current: TranscriptEntry[],
+ updates: TranscriptUpdate[],
+ maxEntries = 50,
+): TranscriptEntry[] {
+ const byKey = new Map(current.map((entry) => [entry.key, entry]));
+ let nextOrder =
+ current.reduce((maximum, entry) => Math.max(maximum, entry.order), -1) + 1;
+
+ for (const update of updates) {
+ const key = `${update.participantIdentity}:${update.segmentId}`;
+ const existing = byKey.get(key);
+ byKey.set(key, {
+ key,
+ segmentId: update.segmentId,
+ participantIdentity: update.participantIdentity,
+ speaker: update.speaker,
+ text: update.text,
+ final: update.final,
+ order: existing?.order ?? nextOrder++,
+ });
+ }
+
+ return Array.from(byKey.values())
+ .sort((left, right) => left.order - right.order)
+ .slice(-maxEntries);
+}
diff --git a/muse_glimmer/macos/apps/web/src/main.tsx b/muse_glimmer/macos/apps/web/src/main.tsx
new file mode 100644
index 0000000000..7b8fd60e76
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/src/main.tsx
@@ -0,0 +1,20 @@
+import "@fontsource/inter/latin-400.css";
+import "@fontsource/inter/latin-500.css";
+import "@fontsource/inter/latin-600.css";
+import "@fontsource/inter/latin-700.css";
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+
+import App from "./App";
+import "./app.css";
+
+const root = document.getElementById("root");
+if (!root) {
+ throw new Error("The application root is missing.");
+}
+
+createRoot(root).render(
+
+
+ ,
+);
diff --git a/muse_glimmer/macos/apps/web/src/test/setup.ts b/muse_glimmer/macos/apps/web/src/test/setup.ts
new file mode 100644
index 0000000000..55b2d40041
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/src/test/setup.ts
@@ -0,0 +1,19 @@
+import "@testing-library/jest-dom/vitest";
+import { cleanup } from "@testing-library/react";
+import { afterEach } from "vitest";
+
+afterEach(cleanup);
+
+Object.defineProperty(window, "matchMedia", {
+ writable: true,
+ value: (query: string) => ({
+ matches: false,
+ media: query,
+ onchange: null,
+ addEventListener: () => undefined,
+ removeEventListener: () => undefined,
+ addListener: () => undefined,
+ removeListener: () => undefined,
+ dispatchEvent: () => false,
+ }),
+});
diff --git a/muse_glimmer/macos/apps/web/src/vite-env.d.ts b/muse_glimmer/macos/apps/web/src/vite-env.d.ts
new file mode 100644
index 0000000000..11f02fe2a0
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/muse_glimmer/macos/apps/web/tsconfig.app.json b/muse_glimmer/macos/apps/web/tsconfig.app.json
new file mode 100644
index 0000000000..295a415220
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/tsconfig.app.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2022",
+ "useDefineForClassFields": true,
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "allowJs": false,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "types": ["vite/client", "vitest/globals", "@testing-library/jest-dom"]
+ },
+ "include": ["src"]
+}
diff --git a/muse_glimmer/macos/apps/web/tsconfig.json b/muse_glimmer/macos/apps/web/tsconfig.json
new file mode 100644
index 0000000000..1ffef600d9
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.app.json" },
+ { "path": "./tsconfig.node.json" }
+ ]
+}
diff --git a/muse_glimmer/macos/apps/web/tsconfig.node.json b/muse_glimmer/macos/apps/web/tsconfig.node.json
new file mode 100644
index 0000000000..bb717fdde5
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/tsconfig.node.json
@@ -0,0 +1,14 @@
+{
+ "compilerOptions": {
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2023",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "types": ["node"]
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/muse_glimmer/macos/apps/web/vite.config.ts b/muse_glimmer/macos/apps/web/vite.config.ts
new file mode 100644
index 0000000000..3eefb3afc0
--- /dev/null
+++ b/muse_glimmer/macos/apps/web/vite.config.ts
@@ -0,0 +1,20 @@
+import react from "@vitejs/plugin-react";
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ host: "127.0.0.1",
+ port: 5173,
+ strictPort: true,
+ },
+ build: {
+ sourcemap: false,
+ target: "es2022",
+ },
+ test: {
+ environment: "jsdom",
+ include: ["src/**/*.test.{ts,tsx}"],
+ setupFiles: "./src/test/setup.ts",
+ },
+});
diff --git a/muse_glimmer/macos/apps/worker/LICENSE b/muse_glimmer/macos/apps/worker/LICENSE
new file mode 100644
index 0000000000..5651f75604
--- /dev/null
+++ b/muse_glimmer/macos/apps/worker/LICENSE
@@ -0,0 +1,30 @@
+BSD License
+
+For "ExecuTorch" software
+
+Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name Meta nor the names of its contributors may be used to
+ endorse or promote products derived from this software without specific
+ prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/muse_glimmer/macos/apps/worker/PROVENANCE.md b/muse_glimmer/macos/apps/worker/PROVENANCE.md
new file mode 100644
index 0000000000..0d3570189c
--- /dev/null
+++ b/muse_glimmer/macos/apps/worker/PROVENANCE.md
@@ -0,0 +1,38 @@
+# Provenance
+
+## Ownership and source snapshots
+
+This package is original product source maintained in the canonical
+[`meta-pytorch/executorch-examples`](https://github.com/meta-pytorch/executorch-examples)
+repository under `muse_glimmer/macos/apps/worker` and licensed under
+BSD-3-Clause. The source snapshot used for the macOS subtree migration is
+`914fb816fe9e0f6b7fc808fd843eb2e97df31dcf`.
+
+The package uses the public
+[`livekit/agents`](https://github.com/livekit/agents) APIs at commit
+`bc5f3df3a2bd1b3b8c5d1df742be57b063374991`. The package did not exist in that
+upstream commit, and no LiveKit implementation source was copied into it.
+
+## Source mapping
+
+The original product files were reorganized for this standalone package:
+
+- `agents/examples/voice_agents/glimmer_agent.py` -> `src/muse_glimmer_worker/agent.py`
+- `agents/examples/voice_agents/glimmer_cli.py` -> `src/muse_glimmer_worker/cli.py`
+- `agents/examples/voice_agents/glimmer_config.py` -> `src/muse_glimmer_worker/config.py`
+- `agents/livekit-plugins/livekit-plugins-executorch/tests/test_glimmer_agent_privacy.py`
+ -> `tests/test_agent.py`
+- `agents/livekit-plugins/livekit-plugins-executorch/tests/test_glimmer_cli.py`
+ -> `tests/test_cli.py`
+- `agents/livekit-plugins/livekit-plugins-executorch/tests/test_glimmer_config.py`
+ -> `tests/test_config.py`
+
+Packaging and lifecycle code are original additions for this distribution.
+
+## Exclusions
+
+This source package does not include LiveKit or ExecuTorch implementation
+source, native runners, model weights, exported programs, tokenizers, voice
+styles, recordings, generated output, dependency source, or build and test
+caches. Those components retain their independent upstream licenses and
+notices.
diff --git a/muse_glimmer/macos/apps/worker/README.md b/muse_glimmer/macos/apps/worker/README.md
new file mode 100644
index 0000000000..7d4874cd1b
--- /dev/null
+++ b/muse_glimmer/macos/apps/worker/README.md
@@ -0,0 +1,19 @@
+# Muse Glimmer worker
+
+An installable, local-only LiveKit worker for the Muse Glimmer voice application.
+It uses local Parakeet and Supertonic ExecuTorch providers and an OpenAI-compatible
+Muse Glimmer endpoint fixed at `http://127.0.0.1:8000/v1`.
+
+After installing the workspace, run the worker with:
+
+```bash
+muse-glimmer-worker dev
+```
+
+The worker accepts only `ws://127.0.0.1:7880` for LiveKit and requires local artifact
+paths through `PARAKEET_*` and `SUPERTONIC_*` environment variables. Credentials are
+read from the environment and are never included in diagnostics.
+
+For deployment checks or a direct WAV pipeline, use `muse-glimmer-diagnostics doctor`
+or `muse-glimmer-diagnostics pipeline INPUT.wav`. Diagnostic ZIPs omit the local
+raw runtime log by default; do not attach local logs to public issues.
diff --git a/muse_glimmer/macos/apps/worker/pyproject.toml b/muse_glimmer/macos/apps/worker/pyproject.toml
new file mode 100644
index 0000000000..85f7200fa4
--- /dev/null
+++ b/muse_glimmer/macos/apps/worker/pyproject.toml
@@ -0,0 +1,66 @@
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[project]
+name = "muse-glimmer-worker"
+version = "0.1.0"
+description = "Local-only LiveKit voice worker for Muse Glimmer"
+readme = "README.md"
+requires-python = ">=3.13,<3.14"
+license = "BSD-3-Clause"
+license-files = ["LICENSE", "PROVENANCE.md"]
+classifiers = [
+ "License :: OSI Approved :: BSD License",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.13",
+]
+dependencies = [
+ "livekit-agents[openai,silero]>=1.6.9,<2",
+ "livekit-plugins-executorch==0.1.0",
+]
+
+[tool.uv.sources]
+livekit-plugins-executorch = { workspace = true }
+
+[dependency-groups]
+dev = [
+ "pytest>=8.4,<9",
+ "pytest-asyncio>=0.25,<2",
+ "ruff>=0.12,<1",
+]
+
+[project.scripts]
+muse-glimmer-worker = "muse_glimmer_worker.agent:main"
+muse-glimmer-diagnostics = "muse_glimmer_worker.cli:main"
+
+[tool.hatch.build]
+include = [
+ "/LICENSE",
+ "/PROVENANCE.md",
+ "/README.md",
+ "/src",
+]
+
+[tool.hatch.build.targets.wheel]
+packages = ["src/muse_glimmer_worker"]
+
+[tool.hatch.build.targets.sdist]
+include = [
+ "/LICENSE",
+ "/PROVENANCE.md",
+ "/README.md",
+ "/src",
+ "/tests",
+]
+
+[tool.pytest.ini_options]
+asyncio_mode = "auto"
+asyncio_default_fixture_loop_scope = "function"
+
+[tool.ruff]
+line-length = 100
+target-version = "py313"
+
+[tool.ruff.lint]
+select = ["E", "F", "I", "UP", "B", "SIM"]
diff --git a/muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/__init__.py b/muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/__init__.py
new file mode 100644
index 0000000000..334a9891bf
--- /dev/null
+++ b/muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/__init__.py
@@ -0,0 +1,5 @@
+"""Local Muse Glimmer LiveKit worker."""
+
+from .config import GlimmerConfig
+
+__all__ = ["GlimmerConfig"]
diff --git a/muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/__main__.py b/muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/__main__.py
new file mode 100644
index 0000000000..2fe7c4c2fb
--- /dev/null
+++ b/muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/__main__.py
@@ -0,0 +1,4 @@
+from .agent import main
+
+if __name__ == "__main__":
+ main()
diff --git a/muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/agent.py b/muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/agent.py
new file mode 100644
index 0000000000..a73d8b4bf5
--- /dev/null
+++ b/muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/agent.py
@@ -0,0 +1,142 @@
+from __future__ import annotations
+
+import logging
+import os
+
+from livekit.agents import (
+ Agent,
+ AgentServer,
+ AgentSession,
+ AgentStateChangedEvent,
+ CloseEvent,
+ ErrorEvent,
+ JobContext,
+ JobProcess,
+ SessionUsageUpdatedEvent,
+ TurnHandlingOptions,
+ UserInputTranscribedEvent,
+ UserStateChangedEvent,
+ UserTranscriptionTimeoutEvent,
+ cli,
+)
+from livekit.plugins import silero
+
+from .config import GlimmerConfig, create_providers
+from .lifecycle import ProviderCleanup
+
+logger = logging.getLogger("muse-glimmer-worker")
+_VAD_KEY = "glimmer_vad"
+
+
+class GlimmerAgent(Agent):
+ def __init__(self, *, instructions: str) -> None:
+ super().__init__(instructions=instructions)
+
+ async def on_enter(self) -> None:
+ self.session.generate_reply(
+ instructions="Greet the user briefly and ask how you can help.",
+ allow_interruptions=True,
+ )
+
+
+def setup_process(proc: JobProcess) -> None:
+ proc.userdata[_VAD_KEY] = silero.VAD.load()
+
+
+server = AgentServer(setup_fnc=setup_process, host="127.0.0.1")
+
+
+@server.rtc_session(agent_name="assistant")
+async def entrypoint(ctx: JobContext) -> None:
+ config = GlimmerConfig.from_env()
+ ctx.log_context_fields = {"room": ctx.room.name, "agent": config.agent_name}
+ providers = create_providers(
+ config,
+ voice_activity_detector=ctx.proc.userdata[_VAD_KEY],
+ )
+ cleanup = ProviderCleanup(
+ llm=providers.llm,
+ parakeet=providers.parakeet,
+ supertonic=providers.supertonic,
+ logger=logger,
+ )
+ ctx.add_shutdown_callback(cleanup.close)
+
+ try:
+ await providers.parakeet.start()
+ session: AgentSession[None] = AgentSession(
+ stt=providers.session_stt,
+ llm=providers.llm,
+ tts=providers.session_tts,
+ turn_handling=TurnHandlingOptions(
+ interruption={
+ "enabled": True,
+ "resume_false_interruption": True,
+ "false_interruption_timeout": 1.0,
+ },
+ preemptive_generation={"enabled": False},
+ ),
+ tts_text_transforms=["filter_markdown", "filter_emoji"],
+ )
+ _attach_session_logging(session)
+ await session.start(
+ agent=GlimmerAgent(instructions=config.instructions),
+ room=ctx.room,
+ )
+ except BaseException:
+ await cleanup.close()
+ raise
+
+
+def _attach_session_logging(session: AgentSession[None]) -> None:
+ @session.on("user_state_changed")
+ def _on_user_state_changed(event: UserStateChangedEvent) -> None:
+ logger.info("USER: %s -> %s", event.old_state, event.new_state)
+
+ @session.on("user_input_transcribed")
+ def _on_user_input_transcribed(event: UserInputTranscribedEvent) -> None:
+ if event.is_final:
+ logger.info("STT: final transcript received (%d characters)", len(event.transcript))
+ logger.debug("STT: final=%s transcript=%r", event.is_final, event.transcript)
+
+ @session.on("user_transcription_timeout")
+ def _on_user_transcription_timeout(event: UserTranscriptionTimeoutEvent) -> None:
+ logger.warning(
+ "STT: no transcript after %.2fs of VAD-detected speech",
+ event.speech_duration,
+ )
+
+ @session.on("agent_state_changed")
+ def _on_agent_state_changed(event: AgentStateChangedEvent) -> None:
+ logger.info("AGENT: %s -> %s", event.old_state, event.new_state)
+
+ @session.on("error")
+ def _on_error(event: ErrorEvent) -> None:
+ logger.error("PIPELINE ERROR: %s", event.model_dump(mode="json"))
+
+ @session.on("close")
+ def _on_close(event: CloseEvent) -> None:
+ logger.info("SESSION CLOSED: reason=%s error=%s", event.reason.value, event.error)
+
+ last_usage: str | None = None
+
+ @session.on("session_usage_updated")
+ def _on_usage_updated(event: SessionUsageUpdatedEvent) -> None:
+ nonlocal last_usage
+ snapshot = repr(event.usage)
+ if snapshot == last_usage:
+ return
+ last_usage = snapshot
+ logger.debug("Glimmer session usage changed: %s", event.usage)
+
+
+def main() -> None:
+ config = GlimmerConfig.from_env()
+ os.environ["LIVEKIT_URL"] = config.livekit_url
+ os.environ["LIVEKIT_API_KEY"] = config.livekit_api_key
+ os.environ["LIVEKIT_API_SECRET"] = config.livekit_api_secret
+ cli.run_app(server)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/cli.py b/muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/cli.py
new file mode 100644
index 0000000000..db51ac91a2
--- /dev/null
+++ b/muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/cli.py
@@ -0,0 +1,762 @@
+from __future__ import annotations
+
+import argparse
+import asyncio
+import json
+import logging
+import os
+import platform
+import sys
+import time
+import traceback
+import urllib.error
+import urllib.request
+import uuid
+import wave
+import zipfile
+from datetime import UTC, datetime
+from importlib import metadata
+from pathlib import Path
+from typing import Any, TextIO
+from urllib.parse import urlsplit, urlunsplit
+
+from livekit import rtc
+from livekit.agents import APIConnectOptions, llm
+from livekit.agents.utils.audio import AudioByteStream
+
+from .agent import main as worker_main
+from .config import REASONING_STRENGTH, GlimmerConfig, LocalProviders, create_local_providers
+from .lifecycle import ProviderCleanup
+
+logger = logging.getLogger("museglimmer-cli")
+
+_APP_NAME = "MuseGlimmer-VoiceAgent"
+_REPORT_SCHEMA_VERSION = 1
+_DEFAULT_TIMEOUT = 300.0
+
+
+class JsonlReporter:
+ def __init__(self, report_dir: Path) -> None:
+ self.report_dir = report_dir
+ self.events_path = report_dir / "events.jsonl"
+ self._stream: TextIO = self.events_path.open("w", encoding="utf-8")
+
+ def emit(self, event: str, **fields: object) -> None:
+ payload = {
+ "timestamp": datetime.now(UTC).isoformat(),
+ "event": event,
+ **fields,
+ }
+ self._stream.write(json.dumps(payload, ensure_ascii=True, default=str) + "\n")
+ self._stream.flush()
+ message = str(fields.get("message", event))
+ if event.endswith("failed"):
+ logger.error("%s: %s", event, message)
+ else:
+ logger.info("%s: %s", event, message)
+
+ def close(self) -> None:
+ self._stream.close()
+
+
+class StageTimer:
+ def __init__(self, reporter: JsonlReporter, stage: str, durations: dict[str, float]) -> None:
+ self._reporter = reporter
+ self._stage = stage
+ self._durations = durations
+ self._started = 0.0
+
+ def __enter__(self) -> StageTimer:
+ self._started = time.perf_counter()
+ self._reporter.emit("stage_started", stage=self._stage, message=self._stage)
+ return self
+
+ def __exit__(self, exc_type: object, exc: object, exc_tb: object) -> None:
+ duration = time.perf_counter() - self._started
+ self._durations[self._stage] = duration
+ if exc is None:
+ self._reporter.emit(
+ "stage_completed",
+ stage=self._stage,
+ duration_seconds=round(duration, 6),
+ message=self._stage,
+ )
+ else:
+ self._reporter.emit(
+ "stage_failed",
+ stage=self._stage,
+ duration_seconds=round(duration, 6),
+ error_type=type(exc).__name__,
+ message=str(exc),
+ )
+
+
+def _parser() -> argparse.ArgumentParser:
+ parser = argparse.ArgumentParser(
+ prog="glimmer_cli.py",
+ description="Diagnose and exercise the MuseGlimmer-VoiceAgent pipeline.",
+ )
+ parser.add_argument(
+ "--log-level",
+ choices=("DEBUG", "INFO", "WARNING", "ERROR"),
+ default="INFO",
+ )
+ commands = parser.add_subparsers(dest="command", required=True)
+
+ doctor = commands.add_parser(
+ "doctor",
+ help="Validate artifacts, native helper startup, and MuseGlimmer HTTP readiness.",
+ )
+ doctor.add_argument("--report-dir", type=Path)
+ doctor.add_argument("--timeout", type=float, default=_DEFAULT_TIMEOUT)
+
+ pipeline = commands.add_parser(
+ "pipeline",
+ help="Run a PCM WAV through Parakeet, MuseGlimmer, and Supertonic.",
+ )
+ pipeline.add_argument("input_wav", type=Path)
+ pipeline.add_argument("--output-wav", type=Path)
+ pipeline.add_argument("--report-dir", type=Path)
+ pipeline.add_argument("--timeout", type=float, default=_DEFAULT_TIMEOUT)
+ pipeline.add_argument(
+ "--force",
+ action="store_true",
+ help="Replace an existing output WAV after a successful run.",
+ )
+ pipeline.add_argument(
+ "--include-content",
+ action="store_true",
+ help="Include transcript and response text in the issue report.",
+ )
+
+ console = commands.add_parser(
+ "console",
+ help="Run the LiveKit microphone/speaker console with the same providers.",
+ )
+ console.add_argument("--input-device")
+ console.add_argument("--output-device")
+ console.add_argument("--list-devices", action="store_true")
+ console.add_argument("--text", action="store_true")
+ console.add_argument("--record", action="store_true")
+ console.add_argument(
+ "--console-log-level",
+ choices=("trace", "debug", "info", "warn", "error", "critical"),
+ default="debug",
+ help="Log level passed to the LiveKit console process.",
+ )
+ return parser
+
+
+class RedactingFormatter(logging.Formatter):
+ def __init__(self, fmt: str) -> None:
+ super().__init__(fmt)
+ self.config: GlimmerConfig | None = None
+
+ def format(self, record: logging.LogRecord) -> str:
+ return _redact_text(super().format(record), self.config)
+
+
+def _attach_runtime_log(report_dir: Path) -> logging.FileHandler:
+ handler = logging.FileHandler(report_dir / "runtime.log", encoding="utf-8")
+ handler.setFormatter(RedactingFormatter("%(asctime)s %(levelname)s %(name)s %(message)s"))
+ logging.getLogger().addHandler(handler)
+ return handler
+
+
+def _set_runtime_log_config(handler: logging.FileHandler, config: GlimmerConfig) -> None:
+ formatter = handler.formatter
+ if isinstance(formatter, RedactingFormatter):
+ formatter.config = config
+
+
+def _detach_runtime_log(handler: logging.FileHandler) -> None:
+ logging.getLogger().removeHandler(handler)
+ handler.close()
+
+
+def _prepare_report_dir(command: str, requested: Path | None) -> Path:
+ if requested is None:
+ stamp = datetime.now(UTC).strftime("%Y%m%dT%H%M%SZ")
+ requested = Path.cwd() / "museglimmer-reports" / f"{stamp}-{command}-{uuid.uuid4().hex[:8]}"
+ path = requested.expanduser().resolve()
+ if path.exists() and any(path.iterdir()):
+ raise ValueError(f"report directory must be empty: {path}")
+ path.mkdir(parents=True, exist_ok=True)
+ return path
+
+
+def _package_version(distribution: str) -> str | None:
+ try:
+ return metadata.version(distribution)
+ except metadata.PackageNotFoundError:
+ return None
+
+
+def _system_snapshot() -> dict[str, object]:
+ return {
+ "app": _APP_NAME,
+ "schema_version": _REPORT_SCHEMA_VERSION,
+ "timestamp": datetime.now(UTC).isoformat(),
+ "platform": platform.platform(),
+ "machine": platform.machine(),
+ "python": sys.version,
+ "packages": {
+ "livekit-agents": _package_version("livekit-agents"),
+ "livekit-plugins-executorch": _package_version("livekit-plugins-executorch"),
+ "livekit-plugins-openai": _package_version("livekit-plugins-openai"),
+ },
+ }
+
+
+def _artifact(path: Path | None) -> dict[str, object] | None:
+ if path is None:
+ return None
+ stat = path.stat()
+ return {
+ "path": f".../{path.name}",
+ "size_bytes": stat.st_size,
+ "modified_ns": stat.st_mtime_ns,
+ "executable": os.access(path, os.X_OK),
+ }
+
+
+def _safe_url(value: str) -> str:
+ parsed = urlsplit(value)
+ host = parsed.hostname or ""
+ if parsed.port is not None:
+ host = f"{host}:{parsed.port}"
+ return urlunsplit((parsed.scheme, host, parsed.path, "", ""))
+
+
+def _redacted_config(config: GlimmerConfig) -> dict[str, object]:
+ return {
+ "agent_name": config.agent_name,
+ "language": config.language,
+ "muse_glimmer": {
+ "base_url": _safe_url(config.muse_glimmer_base_url),
+ "model_id": config.muse_glimmer_model_id,
+ "temperature": config.muse_glimmer_temperature,
+ "max_tokens": config.muse_glimmer_max_tokens,
+ "reasoning_strength": REASONING_STRENGTH,
+ "api_key": "",
+ },
+ "parakeet": {
+ "helper": _artifact(config.parakeet_helper_path),
+ "model": _artifact(config.parakeet_model_path),
+ "tokenizer": _artifact(config.parakeet_tokenizer_path),
+ "delegate_data": _artifact(config.parakeet_delegate_data_path),
+ },
+ "supertonic": {
+ "runner": _artifact(config.supertonic_runner_path),
+ "pte": _artifact(config.supertonic_pte_path),
+ "asset_dir": _artifact(config.supertonic_asset_dir),
+ "voice_style": _artifact(config.supertonic_voice_style_path),
+ "speed": config.supertonic_speed,
+ "seed": config.supertonic_seed,
+ },
+ }
+
+
+def _read_pcm_wav(path: Path) -> tuple[list[rtc.AudioFrame], dict[str, object]]:
+ source = path.expanduser().resolve()
+ if not source.is_file():
+ raise ValueError(f"input WAV does not exist: {source}")
+ with wave.open(str(source), "rb") as input_wav:
+ channels = input_wav.getnchannels()
+ sample_rate = input_wav.getframerate()
+ sample_width = input_wav.getsampwidth()
+ frame_count = input_wav.getnframes()
+ compression = input_wav.getcomptype()
+ payload = input_wav.readframes(frame_count)
+ if compression != "NONE" or sample_width != 2:
+ raise ValueError("input must be uncompressed signed PCM16 WAV")
+ if channels <= 0 or sample_rate <= 0 or frame_count <= 0:
+ raise ValueError("input WAV must contain non-empty audio with a valid format")
+
+ byte_stream = AudioByteStream(
+ sample_rate=sample_rate,
+ num_channels=channels,
+ samples_per_channel=max(1, sample_rate // 10),
+ )
+ frames = [*byte_stream.push(payload), *byte_stream.flush()]
+ return frames, {
+ "path": f".../{source.name}",
+ "sample_rate": sample_rate,
+ "channels": channels,
+ "sample_width_bytes": sample_width,
+ "frames": frame_count,
+ "duration_seconds": frame_count / sample_rate,
+ "size_bytes": source.stat().st_size,
+ }
+
+
+async def _write_synthesized_wav(
+ stream: Any, output_path: Path, *, force: bool
+) -> dict[str, object]:
+ target = output_path.expanduser().resolve()
+ if target.exists() and not force:
+ raise ValueError(f"output WAV already exists; pass --force to replace it: {target}")
+ target.parent.mkdir(parents=True, exist_ok=True)
+ partial = target.with_name(f".{target.name}.{uuid.uuid4().hex}.partial")
+ sample_rate: int | None = None
+ channels: int | None = None
+ sample_count = 0
+ event_count = 0
+ request_id: str | None = None
+ output_wav: wave.Wave_write | None = None
+ try:
+ async with stream:
+ async for event in stream:
+ frame = event.frame
+ if event.request_id != request_id:
+ if output_wav is not None:
+ output_wav.close()
+ partial.unlink(missing_ok=True)
+ request_id = event.request_id
+ sample_rate = frame.sample_rate
+ channels = frame.num_channels
+ sample_count = 0
+ event_count = 0
+ output_wav = wave.open(str(partial), "wb") # noqa: SIM115
+ output_wav.setnchannels(channels)
+ output_wav.setsampwidth(2)
+ output_wav.setframerate(sample_rate)
+ elif frame.sample_rate != sample_rate or frame.num_channels != channels:
+ raise RuntimeError("TTS changed audio format during one synthesis attempt")
+ if output_wav is None:
+ raise RuntimeError("TTS stream did not initialize an output attempt")
+ output_wav.writeframesraw(frame.data.tobytes())
+ sample_count += frame.samples_per_channel
+ event_count += 1
+ if output_wav is not None:
+ output_wav.close()
+ output_wav = None
+ if sample_rate is None or channels is None or sample_count == 0:
+ raise RuntimeError("TTS returned no audio")
+ if target.exists() and not force:
+ raise ValueError(
+ f"output WAV appeared during synthesis; refusing to replace it: {target}"
+ )
+ partial.replace(target)
+ except BaseException:
+ if output_wav is not None:
+ output_wav.close()
+ partial.unlink(missing_ok=True)
+ raise
+ return {
+ "path": f".../{target.name}",
+ "sample_rate": sample_rate,
+ "channels": channels,
+ "sample_width_bytes": 2,
+ "samples_per_channel": sample_count,
+ "duration_seconds": sample_count / sample_rate,
+ "events": event_count,
+ "request_id": request_id,
+ "size_bytes": target.stat().st_size,
+ }
+
+
+async def _probe_synthesized_audio(stream: Any) -> dict[str, object]:
+ sample_count = 0
+ event_count = 0
+ request_id: str | None = None
+ async with stream:
+ async for event in stream:
+ frame = event.frame
+ if frame.sample_rate != 44100 or frame.num_channels != 1:
+ raise RuntimeError("Supertonic must return 44.1 kHz mono audio")
+ if request_id is None:
+ request_id = event.request_id
+ elif event.request_id != request_id:
+ raise RuntimeError("Supertonic changed request ID during the doctor probe")
+ sample_count += frame.samples_per_channel
+ event_count += 1
+ if request_id is None or sample_count <= 0:
+ raise RuntimeError("Supertonic returned no audio during the doctor probe")
+ return {
+ "sample_rate": 44100,
+ "channels": 1,
+ "samples_per_channel": sample_count,
+ "duration_seconds": sample_count / 44100,
+ "events": event_count,
+ "request_id": request_id,
+ }
+
+
+def _http_json(url: str, api_key: str, timeout: float) -> object:
+ headers = {"Accept": "application/json"}
+ if api_key:
+ headers["Authorization"] = f"Bearer {api_key}"
+ request = urllib.request.Request(url, headers=headers)
+ try:
+ with urllib.request.urlopen(request, timeout=timeout) as response:
+ payload = response.read()
+ except urllib.error.URLError as exc:
+ raise RuntimeError(f"HTTP readiness request failed for {_safe_url(url)}: {exc}") from exc
+ try:
+ return json.loads(payload)
+ except json.JSONDecodeError as exc:
+ raise RuntimeError(f"HTTP readiness response was not JSON: {_safe_url(url)}") from exc
+
+
+def _provider_cleanup(providers: LocalProviders) -> ProviderCleanup:
+ return ProviderCleanup(
+ llm=providers.llm,
+ parakeet=providers.parakeet,
+ supertonic=providers.supertonic,
+ logger=logger,
+ )
+
+
+def _write_json(path: Path, payload: object) -> None:
+ temporary = path.with_name(f".{path.name}.{uuid.uuid4().hex}.tmp")
+ temporary.write_text(
+ json.dumps(payload, indent=2, sort_keys=True, ensure_ascii=True, default=str) + "\n",
+ encoding="utf-8",
+ )
+ temporary.replace(path)
+
+
+def _create_issue_bundle(report_dir: Path) -> Path:
+ bundle = report_dir / "issue-report.zip"
+ with zipfile.ZipFile(bundle, "w", compression=zipfile.ZIP_DEFLATED) as archive:
+ for name in ("report.json", "events.jsonl"):
+ path = report_dir / name
+ if path.exists():
+ archive.write(path, arcname=name)
+ return bundle
+
+
+def _attach_provider_reporting(
+ providers: LocalProviders,
+ reporter: JsonlReporter,
+ config: GlimmerConfig,
+) -> None:
+ def metrics(stage: str, event: object) -> None:
+ payload = event.model_dump(mode="json") if hasattr(event, "model_dump") else str(event)
+ reporter.emit("provider_metrics", stage=stage, metrics=payload, message=stage)
+
+ def error(stage: str, event: object) -> None:
+ exception = getattr(event, "error", RuntimeError(str(event)))
+ reporter.emit(
+ "provider_error",
+ stage=stage,
+ recoverable=bool(getattr(event, "recoverable", False)),
+ error_type=type(exception).__name__,
+ message=_redact_text(str(exception), config),
+ )
+
+ for stage, provider in (
+ ("stt", providers.parakeet),
+ ("llm", providers.llm),
+ ("tts", providers.supertonic),
+ ):
+ if not hasattr(provider, "on"):
+ continue
+ provider.on("metrics_collected", lambda event, stage=stage: metrics(stage, event))
+ provider.on("error", lambda event, stage=stage: error(stage, event))
+
+
+def _redact_text(value: str, config: GlimmerConfig | None) -> str:
+ replacements: dict[str, str] = {str(Path.home()): "~"}
+ for secret_name in ("LIVEKIT_API_SECRET", "MUSE_GLIMMER_API_KEY"):
+ secret = os.getenv(secret_name, "")
+ if secret:
+ replacements[secret] = ""
+ if config is not None:
+ replacements[config.muse_glimmer_api_key] = ""
+ for path in (
+ config.parakeet_helper_path,
+ config.parakeet_model_path,
+ config.parakeet_tokenizer_path,
+ config.parakeet_delegate_data_path,
+ config.supertonic_runner_path,
+ config.supertonic_pte_path,
+ config.supertonic_asset_dir,
+ config.supertonic_voice_style_path,
+ ):
+ if path is not None:
+ replacements[str(path)] = f".../{path.name}"
+ for original, replacement in sorted(
+ replacements.items(), key=lambda item: len(item[0]), reverse=True
+ ):
+ if original:
+ value = value.replace(original, replacement)
+ return value
+
+
+def _failure(exc: BaseException, config: GlimmerConfig | None) -> dict[str, object]:
+ return {
+ "type": type(exc).__name__,
+ "message": _redact_text(str(exc), config),
+ "traceback": _redact_text("".join(traceback.format_exception(exc)), config),
+ }
+
+
+async def _run_doctor(args: argparse.Namespace) -> int:
+ report_dir = _prepare_report_dir("doctor", args.report_dir)
+ runtime_handler = _attach_runtime_log(report_dir)
+ reporter = JsonlReporter(report_dir)
+ durations: dict[str, float] = {}
+ cleanup: ProviderCleanup | None = None
+ config: GlimmerConfig | None = None
+ summary: dict[str, object] = {
+ "command": "doctor",
+ "status": "failed",
+ "system": _system_snapshot(),
+ "durations_seconds": durations,
+ }
+ exit_code = 1
+ try:
+ with StageTimer(reporter, "configuration", durations):
+ config = GlimmerConfig.from_env()
+ _set_runtime_log_config(runtime_handler, config)
+ summary["configuration"] = _redacted_config(config)
+
+ with StageTimer(reporter, "muse_glimmer_http", durations):
+ base = config.muse_glimmer_base_url.removesuffix("/v1")
+ health, models = await asyncio.gather(
+ asyncio.to_thread(
+ _http_json, f"{base}/health", config.muse_glimmer_api_key, args.timeout
+ ),
+ asyncio.to_thread(
+ _http_json,
+ f"{config.muse_glimmer_base_url}/models",
+ config.muse_glimmer_api_key,
+ args.timeout,
+ ),
+ )
+ if not isinstance(health, dict) or health.get("status") != "ok":
+ raise RuntimeError(f"MuseGlimmer health check did not return status=ok: {health!r}")
+ if not isinstance(models, dict) or not isinstance(models.get("data"), list):
+ raise RuntimeError(f"MuseGlimmer models response is invalid: {models!r}")
+ model_ids = {item.get("id") for item in models["data"] if isinstance(item, dict)}
+ if config.muse_glimmer_model_id not in model_ids:
+ raise RuntimeError(
+ f"MuseGlimmer model is missing from /v1/models: {config.muse_glimmer_model_id}"
+ )
+ summary["muse_glimmer_http"] = {"health": health, "models": models}
+
+ providers = create_local_providers(config)
+ cleanup = _provider_cleanup(providers)
+ _attach_provider_reporting(providers, reporter, config)
+ connect_options = APIConnectOptions(max_retry=0, timeout=args.timeout)
+
+ with StageTimer(reporter, "parakeet_startup", durations):
+ async with asyncio.timeout(args.timeout):
+ await providers.parakeet.start()
+
+ with StageTimer(reporter, "supertonic_synthesis", durations):
+ async with asyncio.timeout(args.timeout):
+ first = await _probe_synthesized_audio(
+ providers.supertonic.synthesize(
+ "Hello from Glimmer.", conn_options=connect_options
+ )
+ )
+ process = providers.supertonic._process
+ first_pid = process.pid if process is not None else None
+ second = await _probe_synthesized_audio(
+ providers.supertonic.synthesize(
+ "The warm voice process is reusable.", conn_options=connect_options
+ )
+ )
+ if (
+ first_pid is None
+ or providers.supertonic._process is None
+ or providers.supertonic._process.pid != first_pid
+ ):
+ raise RuntimeError("Supertonic did not reuse one warm process")
+ summary["supertonic_probe"] = {
+ "process_reused": True,
+ "utterances": [first, second],
+ }
+
+ summary["status"] = "passed"
+ exit_code = 0
+ reporter.emit("doctor_completed", message="all deployment checks passed")
+ except Exception as exc:
+ error = _failure(exc, config)
+ summary["error"] = error
+ reporter.emit("doctor_failed", error_type=type(exc).__name__, message=error["message"])
+ finally:
+ if cleanup is not None:
+ await cleanup.close()
+ _write_json(report_dir / "report.json", summary)
+ reporter.close()
+ _detach_runtime_log(runtime_handler)
+ bundle = _create_issue_bundle(report_dir)
+ print(
+ json.dumps(
+ {"status": summary["status"], "report_dir": str(report_dir), "bundle": str(bundle)}
+ )
+ )
+ return exit_code
+
+
+async def _run_pipeline(args: argparse.Namespace) -> int:
+ report_dir = _prepare_report_dir("pipeline", args.report_dir)
+ runtime_handler = _attach_runtime_log(report_dir)
+ reporter = JsonlReporter(report_dir)
+ durations: dict[str, float] = {}
+ cleanup: ProviderCleanup | None = None
+ config: GlimmerConfig | None = None
+ output_path = (args.output_wav or (report_dir / "response.wav")).expanduser().resolve()
+ summary: dict[str, object] = {
+ "command": "pipeline",
+ "status": "failed",
+ "system": _system_snapshot(),
+ "durations_seconds": durations,
+ "content_included": bool(args.include_content),
+ }
+ exit_code = 1
+ transcript = ""
+ response_text = ""
+ try:
+ with StageTimer(reporter, "configuration", durations):
+ config = GlimmerConfig.from_env()
+ _set_runtime_log_config(runtime_handler, config)
+ summary["configuration"] = _redacted_config(config)
+
+ with StageTimer(reporter, "input_wav", durations):
+ input_frames, input_metadata = _read_pcm_wav(args.input_wav)
+ summary["input_audio"] = input_metadata
+
+ with StageTimer(reporter, "provider_startup", durations):
+ providers = create_local_providers(config)
+ cleanup = _provider_cleanup(providers)
+ _attach_provider_reporting(providers, reporter, config)
+ async with asyncio.timeout(args.timeout):
+ await providers.parakeet.start()
+
+ connect_options = APIConnectOptions(max_retry=0, timeout=args.timeout)
+ with StageTimer(reporter, "stt", durations):
+ async with asyncio.timeout(args.timeout):
+ speech = await providers.parakeet.recognize(
+ input_frames,
+ language=config.language,
+ conn_options=connect_options,
+ )
+ if not speech.alternatives:
+ raise RuntimeError("Parakeet returned no transcript alternatives")
+ transcript = speech.alternatives[0].text.strip()
+ if not transcript:
+ raise RuntimeError("Parakeet returned an empty transcript")
+ summary["transcript_chars"] = len(transcript)
+
+ with StageTimer(reporter, "llm", durations):
+ chat_context = llm.ChatContext()
+ chat_context.add_message(role="system", content=config.instructions)
+ chat_context.add_message(role="user", content=transcript)
+ async with asyncio.timeout(args.timeout):
+ completion = await providers.llm.chat(
+ chat_ctx=chat_context,
+ conn_options=connect_options,
+ ).collect()
+ if completion.tool_calls:
+ raise RuntimeError("MuseGlimmer returned tool calls in the no-tools CLI pipeline")
+ response_text = completion.text.strip()
+ if not response_text:
+ raise RuntimeError("MuseGlimmer returned an empty response")
+ summary["response_chars"] = len(response_text)
+ if completion.usage is not None:
+ summary["llm_usage"] = completion.usage.model_dump(mode="json")
+
+ with StageTimer(reporter, "tts", durations):
+ async with asyncio.timeout(args.timeout):
+ audio_metadata = await _write_synthesized_wav(
+ providers.supertonic.synthesize(response_text, conn_options=connect_options),
+ output_path,
+ force=args.force,
+ )
+ summary["output_audio"] = audio_metadata
+
+ if args.include_content:
+ summary["transcript"] = transcript
+ summary["response_text"] = response_text
+ summary["status"] = "passed"
+ exit_code = 0
+ reporter.emit("pipeline_completed", message="end-to-end pipeline passed")
+ except Exception as exc:
+ error = _failure(exc, config)
+ summary["error"] = error
+ reporter.emit("pipeline_failed", error_type=type(exc).__name__, message=error["message"])
+ finally:
+ if cleanup is not None:
+ await cleanup.close()
+ _write_json(report_dir / "report.json", summary)
+ reporter.close()
+ _detach_runtime_log(runtime_handler)
+ bundle = _create_issue_bundle(report_dir)
+ result: dict[str, object] = {
+ "status": summary["status"],
+ "report_dir": str(report_dir),
+ "bundle": str(bundle),
+ }
+ if exit_code == 0:
+ result.update(
+ {
+ "transcript": transcript,
+ "response": response_text,
+ "output_wav": str(output_path),
+ }
+ )
+ print(json.dumps(result, indent=2, ensure_ascii=True))
+ return exit_code
+
+
+def _console_command(args: argparse.Namespace) -> list[str]:
+ command = [
+ sys.executable,
+ "-m",
+ "muse_glimmer_worker",
+ "console",
+ "--log-level",
+ args.console_log_level,
+ ]
+ if args.input_device:
+ command.extend(("--input-device", args.input_device))
+ if args.output_device:
+ command.extend(("--output-device", args.output_device))
+ if args.list_devices:
+ command.append("--list-devices")
+ if args.text:
+ command.append("--text")
+ if args.record:
+ command.append("--record")
+ return command
+
+
+def _exec_console(args: argparse.Namespace) -> int:
+ command = _console_command(args)
+ os.execv(sys.executable, command)
+ return 127
+
+
+def run_worker() -> None:
+ worker_main()
+
+
+def main(argv: list[str] | None = None) -> int:
+ parser = _parser()
+ args = parser.parse_args(argv)
+ logging.basicConfig(
+ level=getattr(logging, args.log_level),
+ format="%(asctime)s %(levelname)s %(name)s %(message)s",
+ )
+ if getattr(args, "timeout", 1.0) <= 0:
+ parser.error("--timeout must be positive")
+ if args.command == "console":
+ return _exec_console(args)
+ try:
+ if args.command == "doctor":
+ return asyncio.run(_run_doctor(args))
+ if args.command == "pipeline":
+ return asyncio.run(_run_pipeline(args))
+ except KeyboardInterrupt:
+ logger.error("interrupted")
+ return 130
+ parser.error(f"unsupported command: {args.command}")
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/config.py b/muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/config.py
new file mode 100644
index 0000000000..e0fa90e367
--- /dev/null
+++ b/muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/config.py
@@ -0,0 +1,222 @@
+from __future__ import annotations
+
+import math
+import os
+from dataclasses import dataclass
+from pathlib import Path
+
+from livekit.agents import llm, stt, tts, vad
+from livekit.plugins import executorch, openai
+
+LIVEKIT_URL = "ws://127.0.0.1:7880"
+MUSE_GLIMMER_BASE_URL = "http://127.0.0.1:8000/v1"
+REASONING_STRENGTH = "low"
+
+
+@dataclass(frozen=True, slots=True)
+class GlimmerConfig:
+ agent_name: str
+ instructions: str
+ language: str
+ livekit_url: str
+ livekit_api_key: str
+ livekit_api_secret: str
+ parakeet_helper_path: Path
+ parakeet_model_path: Path
+ parakeet_tokenizer_path: Path
+ parakeet_delegate_data_path: Path | None
+ muse_glimmer_base_url: str
+ muse_glimmer_model_id: str
+ muse_glimmer_api_key: str
+ muse_glimmer_temperature: float
+ muse_glimmer_max_tokens: int
+ supertonic_runner_path: Path
+ supertonic_pte_path: Path
+ supertonic_asset_dir: Path
+ supertonic_voice_style_path: Path
+ supertonic_speed: float
+ supertonic_seed: int
+
+ @classmethod
+ def from_env(cls) -> GlimmerConfig:
+ return cls(
+ agent_name=_env("GLIMMER_AGENT_NAME", "assistant"),
+ instructions=_env(
+ "GLIMMER_AGENT_INSTRUCTIONS",
+ "You are Glimmer, a concise and friendly voice assistant. "
+ "Answer naturally for speech. Do not use markdown, emoji, or long lists.",
+ ),
+ language=_env("GLIMMER_LANGUAGE", "en"),
+ livekit_url=_exact_env("LIVEKIT_URL", LIVEKIT_URL),
+ livekit_api_key=_required_env("LIVEKIT_API_KEY"),
+ livekit_api_secret=_required_env("LIVEKIT_API_SECRET"),
+ parakeet_helper_path=_required_file("PARAKEET_HELPER_PATH", executable=True),
+ parakeet_model_path=_required_file("PARAKEET_MODEL_PATH"),
+ parakeet_tokenizer_path=_required_file("PARAKEET_TOKENIZER_PATH"),
+ parakeet_delegate_data_path=_optional_file("PARAKEET_DELEGATE_DATA_PATH"),
+ muse_glimmer_base_url=_exact_env("MUSE_GLIMMER_BASE_URL", MUSE_GLIMMER_BASE_URL),
+ muse_glimmer_model_id=_env(
+ "MUSE_GLIMMER_MODEL_ID",
+ "muse-glimmer-k-quant-17G-128K-text-dflash-metal",
+ ),
+ muse_glimmer_api_key=_env("MUSE_GLIMMER_API_KEY", "local"),
+ muse_glimmer_temperature=_finite_float("MUSE_GLIMMER_TEMPERATURE", 0.0),
+ muse_glimmer_max_tokens=_positive_int("MUSE_GLIMMER_MAX_TOKENS", 128),
+ supertonic_runner_path=_required_file("SUPERTONIC_RUNNER_PATH", executable=True),
+ supertonic_pte_path=_required_file("SUPERTONIC_PTE_PATH"),
+ supertonic_asset_dir=_required_directory("SUPERTONIC_ASSET_DIR"),
+ supertonic_voice_style_path=_required_file("SUPERTONIC_VOICE_STYLE_PATH"),
+ supertonic_speed=_positive_float("SUPERTONIC_SPEED", 1.05),
+ supertonic_seed=_non_negative_int("SUPERTONIC_SEED", 42),
+ )
+
+
+@dataclass(frozen=True, slots=True)
+class LocalProviders:
+ parakeet: executorch.STT
+ llm: llm.LLM
+ supertonic: executorch.SupertonicTTS
+
+
+@dataclass(frozen=True, slots=True)
+class Providers:
+ parakeet: executorch.STT
+ session_stt: stt.STT
+ llm: llm.LLM
+ supertonic: executorch.SupertonicTTS
+ session_tts: tts.TTS
+
+
+def create_local_providers(config: GlimmerConfig) -> LocalProviders:
+ parakeet = executorch.STT(
+ helper_path=config.parakeet_helper_path,
+ model_path=config.parakeet_model_path,
+ tokenizer_path=config.parakeet_tokenizer_path,
+ delegate_data_path=config.parakeet_delegate_data_path,
+ language=config.language,
+ )
+ supertonic = executorch.SupertonicTTS(
+ runner_path=config.supertonic_runner_path,
+ pte_path=config.supertonic_pte_path,
+ asset_dir=config.supertonic_asset_dir,
+ voice_style_path=config.supertonic_voice_style_path,
+ language=config.language,
+ speed=config.supertonic_speed,
+ seed=config.supertonic_seed,
+ )
+ muse_glimmer = openai.LLM(
+ model=config.muse_glimmer_model_id,
+ api_key=config.muse_glimmer_api_key,
+ base_url=config.muse_glimmer_base_url,
+ temperature=config.muse_glimmer_temperature,
+ max_completion_tokens=config.muse_glimmer_max_tokens,
+ extra_body={
+ "chat_template_kwargs": {
+ "reasoning_strength": REASONING_STRENGTH,
+ },
+ },
+ )
+ return LocalProviders(parakeet=parakeet, llm=muse_glimmer, supertonic=supertonic)
+
+
+def create_providers(config: GlimmerConfig, *, voice_activity_detector: vad.VAD) -> Providers:
+ local = create_local_providers(config)
+ return Providers(
+ parakeet=local.parakeet,
+ session_stt=stt.StreamAdapter(stt=local.parakeet, vad=voice_activity_detector),
+ llm=local.llm,
+ supertonic=local.supertonic,
+ session_tts=local.supertonic,
+ )
+
+
+def _env(name: str, default: str) -> str:
+ value = os.getenv(name, default).strip()
+ if not value:
+ raise ValueError(f"{name} must be non-empty")
+ return value
+
+
+def _required_env(name: str) -> str:
+ value = os.getenv(name, "").strip()
+ if not value:
+ raise ValueError(f"{name} must be set and non-empty")
+ return value
+
+
+def _exact_env(name: str, expected: str) -> str:
+ value = _env(name, expected)
+ if value != expected:
+ raise ValueError(f"{name} must be exactly {expected}")
+ return value
+
+
+def _optional_env(name: str) -> str | None:
+ value = os.getenv(name, "").strip()
+ return value or None
+
+
+def _required_file(name: str, *, executable: bool = False) -> Path:
+ value = _optional_env(name)
+ if value is None:
+ raise ValueError(f"{name} must be set")
+ path = Path(value).expanduser().resolve()
+ if not path.is_file():
+ raise ValueError(f"{name} must point to a file: {path}")
+ if executable and not os.access(path, os.X_OK):
+ raise ValueError(f"{name} must point to an executable file: {path}")
+ return path
+
+
+def _required_directory(name: str) -> Path:
+ value = _optional_env(name)
+ if value is None:
+ raise ValueError(f"{name} must be set")
+ path = Path(value).expanduser().resolve()
+ if not path.is_dir():
+ raise ValueError(f"{name} must point to a directory: {path}")
+ return path
+
+
+def _optional_file(name: str) -> Path | None:
+ if _optional_env(name) is None:
+ return None
+ return _required_file(name)
+
+
+def _finite_float(name: str, default: float) -> float:
+ raw = _env(name, str(default))
+ try:
+ value = float(raw)
+ except ValueError as exc:
+ raise ValueError(f"{name} must be a number") from exc
+ if not math.isfinite(value):
+ raise ValueError(f"{name} must be finite")
+ return value
+
+
+def _positive_float(name: str, default: float) -> float:
+ value = _finite_float(name, default)
+ if value <= 0.0:
+ raise ValueError(f"{name} must be positive")
+ return value
+
+
+def _positive_int(name: str, default: int) -> int:
+ return _bounded_int(name, default, minimum=1)
+
+
+def _non_negative_int(name: str, default: int) -> int:
+ return _bounded_int(name, default, minimum=0)
+
+
+def _bounded_int(name: str, default: int, *, minimum: int) -> int:
+ raw = _env(name, str(default))
+ try:
+ value = int(raw)
+ except ValueError as exc:
+ raise ValueError(f"{name} must be an integer") from exc
+ if value < minimum:
+ constraint = "positive" if minimum == 1 else "non-negative"
+ raise ValueError(f"{name} must be {constraint}")
+ return value
diff --git a/muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/lifecycle.py b/muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/lifecycle.py
new file mode 100644
index 0000000000..62588eae74
--- /dev/null
+++ b/muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/lifecycle.py
@@ -0,0 +1,67 @@
+from __future__ import annotations
+
+import asyncio
+import logging
+from typing import Protocol
+
+
+class AsyncCloseable(Protocol):
+ async def aclose(self) -> None: ...
+
+
+class ProviderCleanup:
+ """Close every local provider once and let repeated callers await the result."""
+
+ def __init__(
+ self,
+ *,
+ llm: AsyncCloseable,
+ parakeet: AsyncCloseable,
+ supertonic: AsyncCloseable,
+ logger: logging.Logger,
+ ) -> None:
+ self._providers = (
+ ("LLM", llm),
+ ("Parakeet", parakeet),
+ ("Supertonic", supertonic),
+ )
+ self._logger = logger
+ self._lock = asyncio.Lock()
+ self._cleanup_task: asyncio.Task[None] | None = None
+
+ @property
+ def closed(self) -> bool:
+ return self._cleanup_task is not None and self._cleanup_task.done()
+
+ async def close(self) -> None:
+ async with self._lock:
+ if self._cleanup_task is None:
+ self._cleanup_task = asyncio.create_task(
+ self._close_providers(), name="muse-glimmer-provider-cleanup"
+ )
+ cleanup_task = self._cleanup_task
+
+ # Shutdown must finish even if its original caller is cancelled repeatedly.
+ while not cleanup_task.done():
+ try:
+ await asyncio.shield(cleanup_task)
+ except asyncio.CancelledError:
+ continue
+ cleanup_task.result()
+
+ async def _close_providers(self) -> None:
+ for name, _ in self._providers:
+ self._logger.info("Closing %s provider", name)
+ results = await asyncio.gather(
+ *(provider.aclose() for _, provider in self._providers),
+ return_exceptions=True,
+ )
+ for (name, _), result in zip(self._providers, results, strict=True):
+ if isinstance(result, BaseException):
+ self._logger.error(
+ "Failed to close %s provider: %s",
+ name,
+ result,
+ )
+ else:
+ self._logger.info("Closed %s provider", name)
diff --git a/muse_glimmer/macos/apps/worker/tests/conftest.py b/muse_glimmer/macos/apps/worker/tests/conftest.py
new file mode 100644
index 0000000000..3246574e56
--- /dev/null
+++ b/muse_glimmer/macos/apps/worker/tests/conftest.py
@@ -0,0 +1,10 @@
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+
+_WORKER_SRC = Path(__file__).parents[1] / "src"
+_PLUGIN_ROOT = Path(__file__).parents[3] / "packages" / "livekit-plugins-executorch"
+for path in (str(_WORKER_SRC), str(_PLUGIN_ROOT)):
+ if path not in sys.path:
+ sys.path.insert(0, path)
diff --git a/muse_glimmer/macos/apps/worker/tests/test_agent.py b/muse_glimmer/macos/apps/worker/tests/test_agent.py
new file mode 100644
index 0000000000..ecf2262562
--- /dev/null
+++ b/muse_glimmer/macos/apps/worker/tests/test_agent.py
@@ -0,0 +1,105 @@
+from __future__ import annotations
+
+from types import SimpleNamespace
+
+import pytest
+
+from muse_glimmer_worker import agent
+
+
+class Closeable:
+ def __init__(self) -> None:
+ self.calls = 0
+
+ async def aclose(self) -> None:
+ self.calls += 1
+
+
+class FailingParakeet(Closeable):
+ async def start(self) -> None:
+ raise RuntimeError("startup failed")
+
+
+class FakeContext:
+ def __init__(self) -> None:
+ self.room = SimpleNamespace(name="room")
+ self.proc = SimpleNamespace(userdata={agent._VAD_KEY: object()})
+ self.log_context_fields = {}
+ self.shutdown_callback = None
+
+ def add_shutdown_callback(self, callback) -> None:
+ self.shutdown_callback = callback
+
+
+async def test_startup_failure_and_shutdown_callback_cleanup_once(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ parakeet = FailingParakeet()
+ model = Closeable()
+ supertonic = Closeable()
+ providers = SimpleNamespace(
+ parakeet=parakeet,
+ llm=model,
+ supertonic=supertonic,
+ session_stt=object(),
+ session_tts=object(),
+ )
+ monkeypatch.setattr(agent.GlimmerConfig, "from_env", lambda: SimpleNamespace(agent_name="a"))
+ monkeypatch.setattr(agent, "create_providers", lambda *args, **kwargs: providers)
+ context = FakeContext()
+
+ with pytest.raises(RuntimeError, match="startup failed"):
+ await agent.entrypoint(context)
+ assert context.shutdown_callback is not None
+ await context.shutdown_callback()
+
+ assert (model.calls, parakeet.calls, supertonic.calls) == (1, 1, 1)
+
+
+async def test_normal_shutdown_callback_cleanup_once(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ class StartedParakeet(Closeable):
+ async def start(self) -> None:
+ return None
+
+ class FakeSession:
+ def __class_getitem__(cls, item):
+ return cls
+
+ def __init__(self, **kwargs) -> None:
+ pass
+
+ def on(self, event_name):
+ return lambda callback: callback
+
+ async def start(self, **kwargs) -> None:
+ return None
+
+ parakeet = StartedParakeet()
+ model = Closeable()
+ supertonic = Closeable()
+ providers = SimpleNamespace(
+ parakeet=parakeet,
+ llm=model,
+ supertonic=supertonic,
+ session_stt=object(),
+ session_tts=object(),
+ )
+ config = SimpleNamespace(agent_name="a", instructions="Answer briefly.")
+ monkeypatch.setattr(agent.GlimmerConfig, "from_env", lambda: config)
+ monkeypatch.setattr(agent, "create_providers", lambda *args, **kwargs: providers)
+ monkeypatch.setattr(agent, "AgentSession", FakeSession)
+ context = FakeContext()
+
+ await agent.entrypoint(context)
+ assert context.shutdown_callback is not None
+ await context.shutdown_callback()
+ await context.shutdown_callback()
+
+ assert (model.calls, parakeet.calls, supertonic.calls) == (1, 1, 1)
+
+
+def test_worker_is_loopback_only_with_neutral_agent_name() -> None:
+ assert agent.server._host == "127.0.0.1"
+ assert agent.server._agent_name == "assistant"
diff --git a/muse_glimmer/macos/apps/worker/tests/test_cli.py b/muse_glimmer/macos/apps/worker/tests/test_cli.py
new file mode 100644
index 0000000000..e126ef4dbc
--- /dev/null
+++ b/muse_glimmer/macos/apps/worker/tests/test_cli.py
@@ -0,0 +1,43 @@
+from __future__ import annotations
+
+import argparse
+import sys
+import zipfile
+from pathlib import Path
+
+from muse_glimmer_worker import cli
+
+
+def test_console_command_uses_installed_module() -> None:
+ args = argparse.Namespace(
+ input_device="Built-in Mic",
+ output_device="Built-in Output",
+ list_devices=True,
+ text=True,
+ record=False,
+ console_log_level="info",
+ )
+ assert cli._console_command(args) == [
+ sys.executable,
+ "-m",
+ "muse_glimmer_worker",
+ "console",
+ "--log-level",
+ "info",
+ "--input-device",
+ "Built-in Mic",
+ "--output-device",
+ "Built-in Output",
+ "--list-devices",
+ "--text",
+ ]
+
+
+def test_issue_bundle_omits_runtime_log(tmp_path: Path) -> None:
+ for name in ("report.json", "events.jsonl", "runtime.log"):
+ (tmp_path / name).write_text(name)
+
+ bundle = cli._create_issue_bundle(tmp_path)
+
+ with zipfile.ZipFile(bundle) as archive:
+ assert set(archive.namelist()) == {"report.json", "events.jsonl"}
diff --git a/muse_glimmer/macos/apps/worker/tests/test_config.py b/muse_glimmer/macos/apps/worker/tests/test_config.py
new file mode 100644
index 0000000000..c248536a08
--- /dev/null
+++ b/muse_glimmer/macos/apps/worker/tests/test_config.py
@@ -0,0 +1,92 @@
+from __future__ import annotations
+
+import os
+from pathlib import Path
+from typing import Any
+
+import pytest
+
+from muse_glimmer_worker import config as config_module
+
+
+def _set_required(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
+ monkeypatch.setenv("LIVEKIT_API_KEY", "local-key")
+ monkeypatch.setenv("LIVEKIT_API_SECRET", "local-secret")
+ for name in (
+ "PARAKEET_HELPER_PATH",
+ "PARAKEET_MODEL_PATH",
+ "PARAKEET_TOKENIZER_PATH",
+ "SUPERTONIC_RUNNER_PATH",
+ "SUPERTONIC_PTE_PATH",
+ "SUPERTONIC_VOICE_STYLE_PATH",
+ ):
+ path = tmp_path / name.lower()
+ path.write_bytes(b"test")
+ if name.endswith(("HELPER_PATH", "RUNNER_PATH")):
+ path.chmod(0o755)
+ monkeypatch.setenv(name, str(path))
+ assets = tmp_path / "supertonic-assets"
+ assets.mkdir()
+ monkeypatch.setenv("SUPERTONIC_ASSET_DIR", str(assets))
+
+
+def test_config_accepts_only_fixed_local_endpoints(
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
+) -> None:
+ _set_required(monkeypatch, tmp_path)
+ config = config_module.GlimmerConfig.from_env()
+ assert config.livekit_url == "ws://127.0.0.1:7880"
+ assert config.muse_glimmer_base_url == "http://127.0.0.1:8000/v1"
+
+ monkeypatch.setenv("LIVEKIT_URL", "ws://localhost:7880")
+ with pytest.raises(ValueError, match="must be exactly"):
+ config_module.GlimmerConfig.from_env()
+ monkeypatch.setenv("LIVEKIT_URL", config_module.LIVEKIT_URL)
+ monkeypatch.setenv("MUSE_GLIMMER_BASE_URL", "https://example.com/v1")
+ with pytest.raises(ValueError, match="must be exactly"):
+ config_module.GlimmerConfig.from_env()
+
+
+def test_config_requires_credentials(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
+ _set_required(monkeypatch, tmp_path)
+ monkeypatch.delenv("LIVEKIT_API_SECRET")
+ with pytest.raises(ValueError, match="LIVEKIT_API_SECRET"):
+ config_module.GlimmerConfig.from_env()
+
+
+def test_llm_uses_reasoning_strength_low_without_reasoning_effort(
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
+) -> None:
+ _set_required(monkeypatch, tmp_path)
+ captured: dict[str, Any] = {}
+
+ class FakeSTT:
+ def __init__(self, **kwargs: object) -> None:
+ captured["stt"] = kwargs
+
+ class FakeTTS:
+ def __init__(self, **kwargs: object) -> None:
+ captured["tts"] = kwargs
+
+ class FakeLLM:
+ def __init__(self, **kwargs: object) -> None:
+ captured["llm"] = kwargs
+
+ monkeypatch.setattr(config_module.executorch, "STT", FakeSTT)
+ monkeypatch.setattr(config_module.executorch, "SupertonicTTS", FakeTTS)
+ monkeypatch.setattr(config_module.openai, "LLM", FakeLLM)
+ config_module.create_local_providers(config_module.GlimmerConfig.from_env())
+
+ llm_options = captured["llm"]
+ assert llm_options["extra_body"] == {"chat_template_kwargs": {"reasoning_strength": "low"}}
+ assert "reasoning_effort" not in llm_options
+ assert "reasoning_effort" not in repr(llm_options)
+
+
+def test_environment_cannot_override_reasoning_strength(
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
+) -> None:
+ _set_required(monkeypatch, tmp_path)
+ monkeypatch.setenv("MUSE_GLIMMER_REASONING_STRENGTH", "high")
+ assert config_module.REASONING_STRENGTH == "low"
+ assert "MUSE_GLIMMER_REASONING_STRENGTH" in os.environ
diff --git a/muse_glimmer/macos/apps/worker/tests/test_lifecycle.py b/muse_glimmer/macos/apps/worker/tests/test_lifecycle.py
new file mode 100644
index 0000000000..62f63c840f
--- /dev/null
+++ b/muse_glimmer/macos/apps/worker/tests/test_lifecycle.py
@@ -0,0 +1,71 @@
+from __future__ import annotations
+
+import asyncio
+import logging
+
+from muse_glimmer_worker.lifecycle import ProviderCleanup
+
+
+class Closeable:
+ def __init__(self, *, error: Exception | None = None) -> None:
+ self.calls = 0
+ self.error = error
+
+ async def aclose(self) -> None:
+ self.calls += 1
+ await asyncio.sleep(0)
+ if self.error is not None:
+ raise self.error
+
+
+async def test_cleanup_attempts_every_provider_exactly_once(
+ caplog,
+) -> None:
+ model = Closeable(error=RuntimeError("llm close failed"))
+ parakeet = Closeable()
+ supertonic = Closeable()
+ cleanup = ProviderCleanup(
+ llm=model,
+ parakeet=parakeet,
+ supertonic=supertonic,
+ logger=logging.getLogger("cleanup-test"),
+ )
+
+ with caplog.at_level(logging.INFO):
+ await asyncio.gather(cleanup.close(), cleanup.close(), cleanup.close())
+
+ assert cleanup.closed
+ assert (model.calls, parakeet.calls, supertonic.calls) == (1, 1, 1)
+ assert "Closing LLM provider" in caplog.text
+ assert "Failed to close LLM provider: llm close failed" in caplog.text
+ assert "Closed Parakeet provider" in caplog.text
+ assert "Closed Supertonic provider" in caplog.text
+
+
+async def test_cleanup_survives_caller_cancellation() -> None:
+ release = asyncio.Event()
+
+ class BlockingCloseable(Closeable):
+ async def aclose(self) -> None:
+ self.calls += 1
+ await release.wait()
+
+ model = BlockingCloseable()
+ parakeet = BlockingCloseable()
+ supertonic = BlockingCloseable()
+ cleanup = ProviderCleanup(
+ llm=model,
+ parakeet=parakeet,
+ supertonic=supertonic,
+ logger=logging.getLogger("cleanup-cancellation-test"),
+ )
+ first = asyncio.create_task(cleanup.close())
+ await asyncio.sleep(0)
+ first.cancel()
+ release.set()
+
+ await first
+ await cleanup.close()
+
+ assert cleanup.closed
+ assert (model.calls, parakeet.calls, supertonic.calls) == (1, 1, 1)
diff --git a/muse_glimmer/macos/artifacts/README.md b/muse_glimmer/macos/artifacts/README.md
new file mode 100644
index 0000000000..eaedd12f6c
--- /dev/null
+++ b/muse_glimmer/macos/artifacts/README.md
@@ -0,0 +1,12 @@
+# Local artifacts
+
+This directory contains manifests only. Models, tokenizers, voice styles,
+exported programs, and native binaries live under ignored `.local/artifacts/`.
+
+Each artifact is governed by its own upstream license. The BSD-3-Clause
+license for product-owned source does not apply to those artifacts. Run
+`make prepare-artifacts` after reviewing the licenses and providing any
+artifacts marked `user-provided` in `macos-arm64.lock.json`.
+
+Preparation verifies checksums and writes `.local/state/prepared.json`. Daily
+startup consumes that receipt and never downloads, builds, or exports assets.
diff --git a/muse_glimmer/macos/artifacts/macos-arm64.lock.json b/muse_glimmer/macos/artifacts/macos-arm64.lock.json
new file mode 100644
index 0000000000..e4b7520f9b
--- /dev/null
+++ b/muse_glimmer/macos/artifacts/macos-arm64.lock.json
@@ -0,0 +1,146 @@
+{
+ "schema_version": 1,
+ "platform": "macos-arm64",
+ "artifacts": [
+ {
+ "role": "parakeet_helper",
+ "kind": "file",
+ "executable": true,
+ "distribution": "build",
+ "source": "executorch",
+ "revision": null,
+ "license": "BSD-3-Clause",
+ "destination": ".local/artifacts/bin/parakeet_helper",
+ "sensitive": true,
+ "sha256": null,
+ "size_bytes": null,
+ "prepare": "Build from the single pinned ExecuTorch checkout."
+ },
+ {
+ "role": "parakeet_model",
+ "kind": "file",
+ "executable": false,
+ "distribution": "user-provided",
+ "source": null,
+ "revision": null,
+ "license": "See upstream model terms",
+ "destination": ".local/artifacts/parakeet/model.pte",
+ "sensitive": true,
+ "sha256": null,
+ "size_bytes": null,
+ "prepare": "Place a licensed compatible artifact and record its checksum locally."
+ },
+ {
+ "role": "parakeet_tokenizer",
+ "kind": "file",
+ "executable": false,
+ "distribution": "user-provided",
+ "source": null,
+ "revision": null,
+ "license": "See upstream model terms",
+ "destination": ".local/artifacts/parakeet/tokenizer.model",
+ "sensitive": true,
+ "sha256": null,
+ "size_bytes": null,
+ "prepare": "Place a licensed compatible tokenizer and record its checksum locally."
+ },
+ {
+ "role": "muse_glimmer_worker",
+ "kind": "file",
+ "executable": true,
+ "distribution": "build",
+ "source": "executorch",
+ "revision": null,
+ "license": "BSD-3-Clause",
+ "destination": ".local/artifacts/bin/muse_glimmer_worker",
+ "sensitive": true,
+ "sha256": null,
+ "size_bytes": null,
+ "prepare": "Build from the single pinned ExecuTorch checkout."
+ },
+ {
+ "role": "muse_glimmer_model",
+ "kind": "file",
+ "executable": false,
+ "distribution": "user-provided",
+ "source": null,
+ "revision": null,
+ "license": "See upstream model terms",
+ "destination": ".local/artifacts/muse-glimmer/model.pte",
+ "sensitive": true,
+ "sha256": null,
+ "size_bytes": null,
+ "prepare": "Place a licensed compatible artifact and record its checksum locally."
+ },
+ {
+ "role": "muse_glimmer_tokenizer",
+ "kind": "file",
+ "executable": false,
+ "distribution": "user-provided",
+ "source": null,
+ "revision": null,
+ "license": "See upstream model terms",
+ "destination": ".local/artifacts/muse-glimmer/tokenizer.json",
+ "sensitive": true,
+ "sha256": null,
+ "size_bytes": null,
+ "prepare": "Place a licensed compatible tokenizer and record its checksum locally."
+ },
+ {
+ "role": "supertonic_runner",
+ "kind": "file",
+ "executable": true,
+ "distribution": "build",
+ "source": "executorch",
+ "revision": null,
+ "license": "BSD-3-Clause",
+ "destination": ".local/artifacts/bin/supertonic_runner",
+ "sensitive": true,
+ "sha256": null,
+ "size_bytes": null,
+ "prepare": "Build from the single pinned ExecuTorch checkout."
+ },
+ {
+ "role": "supertonic_model",
+ "kind": "file",
+ "executable": false,
+ "distribution": "build",
+ "source": "supertonic",
+ "revision": null,
+ "license": "See upstream model terms",
+ "destination": ".local/artifacts/supertonic/model.pte",
+ "sensitive": true,
+ "sha256": null,
+ "size_bytes": null,
+ "prepare": "Export from approved Supertonic assets during artifact preparation."
+ },
+ {
+ "role": "supertonic_assets",
+ "kind": "directory",
+ "executable": false,
+ "distribution": "user-provided",
+ "source": null,
+ "revision": null,
+ "license": "See upstream asset terms",
+ "destination": ".local/artifacts/supertonic/assets",
+ "sensitive": true,
+ "sha256": null,
+ "size_bytes": null,
+ "prepare": "Place licensed assets and record their tree checksum locally."
+ },
+ {
+ "role": "supertonic_voice_style",
+ "kind": "file",
+ "executable": false,
+ "distribution": "user-provided",
+ "source": null,
+ "revision": null,
+ "license": "See upstream asset terms",
+ "destination": ".local/artifacts/supertonic/voice-style.json",
+ "sensitive": true,
+ "sha256": null,
+ "size_bytes": null,
+ "prepare": "Place one licensed batch-1 voice style and record its checksum locally."
+ }
+ ]
+}
diff --git a/muse_glimmer/macos/artifacts/manifest.schema.json b/muse_glimmer/macos/artifacts/manifest.schema.json
new file mode 100644
index 0000000000..52a336a8af
--- /dev/null
+++ b/muse_glimmer/macos/artifacts/manifest.schema.json
@@ -0,0 +1,34 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://example.invalid/muse-glimmer-voice-agent/artifact-manifest.schema.json",
+ "title": "Muse Glimmer voice agent artifact manifest",
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["schema_version", "platform", "artifacts"],
+ "properties": {
+ "schema_version": {"const": 1},
+ "platform": {"const": "macos-arm64"},
+ "artifacts": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["role", "kind", "executable", "distribution", "license", "destination", "sensitive", "sha256"],
+ "properties": {
+ "role": {"type": "string", "minLength": 1},
+ "kind": {"enum": ["file", "directory"]},
+ "executable": {"type": "boolean"},
+ "distribution": {"enum": ["build", "download", "user-provided"]},
+ "source": {"type": ["string", "null"]},
+ "revision": {"type": ["string", "null"]},
+ "license": {"type": "string", "minLength": 1},
+ "destination": {"type": "string", "pattern": "^\\.local/artifacts/"},
+ "sensitive": {"type": "boolean"},
+ "sha256": {"type": ["string", "null"], "pattern": "^[0-9a-f]{64}$"},
+ "size_bytes": {"type": ["integer", "null"], "minimum": 1},
+ "prepare": {"type": ["string", "null"]}
+ }
+ }
+ }
+ }
+}
diff --git a/muse_glimmer/macos/config/dependencies/compatibility.lock.json b/muse_glimmer/macos/config/dependencies/compatibility.lock.json
new file mode 100644
index 0000000000..cae25cfa83
--- /dev/null
+++ b/muse_glimmer/macos/config/dependencies/compatibility.lock.json
@@ -0,0 +1,39 @@
+{
+ "schema_version": 1,
+ "status": "development-gated",
+ "platform": "macos-arm64",
+ "executorch": {
+ "repository": "https://github.com/pytorch/executorch.git",
+ "commit": null,
+ "required_capabilities": [
+ "parakeet_persistent_helper",
+ "muse_glimmer_dflash_mlx",
+ "supports_cancel",
+ "supertonic_server_jsonl"
+ ],
+ "gates": {
+ "supertonic_runtime": {
+ "status": "landed",
+ "pull_request": "https://github.com/pytorch/executorch/pull/22063",
+ "commit": "81969a92dd2e5515fa23ccdf9d87346cf3ba2ba2"
+ },
+ "supports_cancel": {
+ "status": "pending",
+ "pull_request": "https://github.com/pytorch/executorch/pull/22070",
+ "commit": null
+ },
+ "supertonic_server_jsonl": {
+ "status": "unsubmitted",
+ "pull_request": null,
+ "commit": null,
+ "blocker": "Protocol v1 ready frame must report sample_rate 44100."
+ }
+ }
+ },
+ "livekit": {
+ "agents_requirement": ">=1.6.9,<2",
+ "server_requirement": ">=1.9,<2"
+ },
+ "ready_for_release": false,
+ "release_blocker": "Land every required capability and select one verified descendant ExecuTorch commit."
+}
diff --git a/muse_glimmer/macos/config/dependencies/toolchain.lock.json b/muse_glimmer/macos/config/dependencies/toolchain.lock.json
new file mode 100644
index 0000000000..f7d19e4ed3
--- /dev/null
+++ b/muse_glimmer/macos/config/dependencies/toolchain.lock.json
@@ -0,0 +1,15 @@
+{
+ "schema_version": 1,
+ "platform": {
+ "system": "Darwin",
+ "machine": "arm64"
+ },
+ "tools": {
+ "python": ">=3.13,<3.14",
+ "node": ">=22.12.0,<23",
+ "npm": ">=10,<12",
+ "uv": ">=0.11,<1",
+ "cmake": ">=3.24,<5",
+ "livekit-server": ">=1.9,<2"
+ }
+}
diff --git a/muse_glimmer/macos/config/livekit/macos-arm64.yaml b/muse_glimmer/macos/config/livekit/macos-arm64.yaml
new file mode 100644
index 0000000000..e2144f2938
--- /dev/null
+++ b/muse_glimmer/macos/config/livekit/macos-arm64.yaml
@@ -0,0 +1,22 @@
+port: 7880
+bind_addresses:
+ - 127.0.0.1
+
+rtc:
+ node_ip: 127.0.0.1
+ tcp_port: 0
+ udp_port: 7882
+ use_external_ip: false
+ enable_loopback_candidate: true
+ interfaces:
+ includes:
+ - lo0
+ ips:
+ includes:
+ - 127.0.0.0/8
+
+logging:
+ level: info
+
+room:
+ auto_create: true
diff --git a/muse_glimmer/macos/docs/architecture.md b/muse_glimmer/macos/docs/architecture.md
new file mode 100644
index 0000000000..50931b089b
--- /dev/null
+++ b/muse_glimmer/macos/docs/architecture.md
@@ -0,0 +1,33 @@
+# Architecture
+
+## Runtime flow
+
+1. The React application requests a short-lived token from the local token
+ service only after the user selects **Start conversation**.
+2. The browser connects to loopback LiveKit and publishes microphone audio.
+3. The `assistant` worker receives audio and uses Silero VAD with the persistent
+ Parakeet ExecuTorch helper.
+4. Final transcripts are sent to the loopback MuseGlimmer OpenAI-compatible
+ server. One native worker owns one loaded model and reusable sessions.
+5. The worker passes visible response text to a persistent Supertonic JSONL
+ runner. The model is loaded and warmed once.
+6. Generated PCM audio is published through LiveKit to the browser.
+
+## Process ownership
+
+The repository supervisor owns five process groups in dependency order:
+MuseGlimmer, LiveKit, token service, production web server, and worker. It
+stores identity-qualified state under `.local/run`, rolls startup failures back
+in reverse order, and never kills a process based on PID alone. Shutdown owns the
+recorded process groups even after a launcher exits, and worker status probes the
+dynamic loopback health endpoint reported by LiveKit Agents.
+
+## Dependency boundary
+
+LiveKit and ExecuTorch are external dependencies. Their repositories are not
+vendored. One compatibility lock pins all ExecuTorch Python and native pieces
+together. Models, builds, and checkouts live under ignored `.local` paths.
+
+The temporary `packages/livekit-plugins-executorch` package is product-owned
+until a compatible upstream package is released. Its provenance file records
+the precise source and removal condition.
diff --git a/muse_glimmer/macos/docs/artifacts.md b/muse_glimmer/macos/docs/artifacts.md
new file mode 100644
index 0000000000..4a40e37986
--- /dev/null
+++ b/muse_glimmer/macos/docs/artifacts.md
@@ -0,0 +1,19 @@
+# Artifact Preparation
+
+`artifacts/macos-arm64.lock.json` is the source-of-truth inventory. Every entry
+records its role, distribution method, independent license, ignored
+`.local/artifacts` destination, and checksum when an approved immutable
+artifact is available.
+
+Artifacts marked `user-provided` are not downloaded automatically. Obtain them
+under their upstream terms, place them at the documented destination, and run:
+
+```bash
+make prepare-artifacts
+```
+
+Preparation accepts only the one ExecuTorch commit in
+`config/dependencies/compatibility.lock.json`, rejects a dirty or mismatched
+checkout, validates all files, and writes `.local/state/prepared.json`.
+Startup verifies that receipt and every checksum. It never installs, builds,
+downloads, exports, or repairs artifacts.
diff --git a/muse_glimmer/macos/docs/development.md b/muse_glimmer/macos/docs/development.md
new file mode 100644
index 0000000000..c777ab12b3
--- /dev/null
+++ b/muse_glimmer/macos/docs/development.md
@@ -0,0 +1,21 @@
+# Development
+
+The repository separates source setup, artifact preparation, and daily runtime:
+
+```bash
+make bootstrap
+make prepare-artifacts
+make dev
+```
+
+Use `make check` for static and publication checks, `make test` for unit tests
+and the production web build, and `make e2e` for model-heavy macOS integration.
+
+Bootstrap records the locked dependency inputs, validated tool paths, and the
+production web build digest. Daily startup rejects stale setup state and also
+revalidates that the prepared ExecuTorch checkout remains clean at the exact
+compatibility commit; it never installs or rebuilds.
+
+Do not place secrets in `.env` files. The supervisor creates ephemeral local
+LiveKit credentials. Do not add cloud provider fallbacks or browser-configured
+model endpoints.
diff --git a/muse_glimmer/macos/docs/observability.md b/muse_glimmer/macos/docs/observability.md
new file mode 100644
index 0000000000..32c50ddaf9
--- /dev/null
+++ b/muse_glimmer/macos/docs/observability.md
@@ -0,0 +1,19 @@
+# Local Observability
+
+Logs remain under ignored `.local/logs` and are not uploaded. The worker emits
+structured records for:
+
+- User speech state transitions.
+- Final ASR transcript availability and character count.
+- VAD-detected speech that produced no final transcript.
+- Agent state transitions.
+- Pipeline/provider errors without browser-visible native detail.
+- Session closure and reason.
+- LLM prompt/completion token counts, time to first token, generation duration,
+ finish reason, and cancellation outcome.
+
+Transcript text is debug-only and must not be included in public issue reports
+by default. Shareable diagnostic ZIPs contain the redacted report and structured
+events only; raw `runtime.log` remains local and is never bundled automatically.
+Native stderr is bounded and local. `make logs` follows all five managed service
+logs.
diff --git a/muse_glimmer/macos/docs/security-model.md b/muse_glimmer/macos/docs/security-model.md
new file mode 100644
index 0000000000..cfdf59876c
--- /dev/null
+++ b/muse_glimmer/macos/docs/security-model.md
@@ -0,0 +1,41 @@
+# Security Model
+
+## Guarantees
+
+The supported profile binds every network service to IPv4 loopback. LiveKit
+advertises only loopback candidates and disables external-IP discovery. The
+browser receives a short-lived room token restricted to microphone publication
+and subscription; it cannot publish camera, screen, or data tracks.
+
+Runtime credentials are generated locally for each `up`, written mode 0600,
+injected only into the LiveKit, token, and worker processes, and deleted by
+normal shutdown. They are protocol credentials required by a local LiveKit
+server, not cloud credentials.
+
+The browser may know only the public product name, `assistant` agent identity,
+room/participant identities, the participant JWT, the fixed token endpoint,
+the fixed LiveKit URL, state enums, and conversation transcripts. Model IDs,
+quantization, artifact paths, LLM port 8000, native executable details, private
+endpoints, and credentials stay server-side.
+
+## Trust boundary
+
+Loopback is a network boundary, not a same-user authentication boundary. A
+process running as the same operating-system user can call the token endpoint
+and may read files that user can access. Requests without an Origin are
+accepted for local native clients. Browser origins and Host headers are still
+restricted exactly to approved loopback values.
+
+## Enforcement
+
+- Configuration rejects non-loopback LiveKit and MuseGlimmer endpoints.
+- The web client rejects token responses containing unknown fields or any
+ LiveKit URL other than `ws://127.0.0.1:7880`.
+- A production Content Security Policy excludes the LLM endpoint.
+- The post-start privacy audit checks listeners, managed connections, token
+ responses, runtime credential permissions, and browser bundle strings.
+- The publication check prevents private artifacts and workstation paths from
+ entering source control.
+
+This design does not defend against malware executing as the same user, a
+compromised browser, or an intentionally modified local build.
diff --git a/muse_glimmer/macos/docs/upstream-pins.md b/muse_glimmer/macos/docs/upstream-pins.md
new file mode 100644
index 0000000000..d49bd0e864
--- /dev/null
+++ b/muse_glimmer/macos/docs/upstream-pins.md
@@ -0,0 +1,32 @@
+# Upstream Compatibility Pins
+
+The application source can be developed in this repository, but the native
+compatibility lock remains `development-gated` until one immutable ExecuTorch
+commit contains every required interface.
+
+## Capability status
+
+- ExecuTorch PR [#22063](https://github.com/pytorch/executorch/pull/22063):
+ base Supertonic export and native runtime landed at
+ `81969a92dd2e5515fa23ccdf9d87346cf3ba2ba2`.
+- ExecuTorch PR [#22070](https://github.com/pytorch/executorch/pull/22070):
+ bounded generic LLM worker cancellation and health propagation are pending
+ merge. The application will record the public landed commit after merge.
+- Persistent Supertonic JSONL mode: the follow-up is not submitted yet. Its
+ protocol-v1 ready frame must report `sample_rate: 44100` together with load
+ and warmup timing so the Python adapter and native runtime enforce one schema.
+
+The merged base Supertonic runtime alone is not a release pin. A temporary
+public PR commit may be used for local development only when it is pinned by
+full SHA, publicly accessible, license-compatible, and validated as a single
+checkout. Branch names, dirty checkouts, and mixed Python/native revisions are
+forbidden.
+
+Before setting `ready_for_release` to true:
+
+1. Land every required capability and select one descendant ExecuTorch commit.
+2. Populate artifact sources, revisions, checksums, sizes, and exact licenses.
+3. Run real generation, stream cancellation, and post-cancel generation.
+4. Verify multiple Supertonic utterances reuse one warm process using the
+ documented protocol-v1 ready frame.
+5. Pass a clean-machine macOS arm64 end-to-end run.
diff --git a/muse_glimmer/macos/native/macos-arm64/README.md b/muse_glimmer/macos/native/macos-arm64/README.md
new file mode 100644
index 0000000000..56ae71d61f
--- /dev/null
+++ b/muse_glimmer/macos/native/macos-arm64/README.md
@@ -0,0 +1,9 @@
+# macOS Apple Silicon native targets
+
+Native binaries are built from the single ExecuTorch checkout pinned by
+`config/dependencies/compatibility.lock.json`. They are installed under the
+ignored `.local/artifacts/bin/` directory and are never committed.
+
+The supported milestone requires a MuseGlimmer worker that advertises
+`supports_cancel` and a Supertonic runner with `--server_jsonl`. Startup fails
+if either capability is unavailable.
diff --git a/muse_glimmer/macos/native/macos-arm64/targets.json b/muse_glimmer/macos/native/macos-arm64/targets.json
new file mode 100644
index 0000000000..a444ae55d2
--- /dev/null
+++ b/muse_glimmer/macos/native/macos-arm64/targets.json
@@ -0,0 +1,12 @@
+{
+ "schema_version": 1,
+ "targets": {
+ "parakeet_helper": "parakeet_helper",
+ "muse_glimmer_worker": "muse_glimmer_worker",
+ "supertonic_runner": "supertonic_runner"
+ },
+ "required_features": {
+ "muse_glimmer_worker": ["supports_cancel"],
+ "supertonic_runner": ["server_jsonl"]
+ }
+}
diff --git a/muse_glimmer/macos/packages/livekit-plugins-executorch/LICENSE b/muse_glimmer/macos/packages/livekit-plugins-executorch/LICENSE
new file mode 100644
index 0000000000..5651f75604
--- /dev/null
+++ b/muse_glimmer/macos/packages/livekit-plugins-executorch/LICENSE
@@ -0,0 +1,30 @@
+BSD License
+
+For "ExecuTorch" software
+
+Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name Meta nor the names of its contributors may be used to
+ endorse or promote products derived from this software without specific
+ prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/muse_glimmer/macos/packages/livekit-plugins-executorch/PROVENANCE.md b/muse_glimmer/macos/packages/livekit-plugins-executorch/PROVENANCE.md
new file mode 100644
index 0000000000..4d85997227
--- /dev/null
+++ b/muse_glimmer/macos/packages/livekit-plugins-executorch/PROVENANCE.md
@@ -0,0 +1,43 @@
+# Provenance
+
+## Ownership and source snapshots
+
+This package is original product source maintained in the canonical
+[`meta-pytorch/executorch-examples`](https://github.com/meta-pytorch/executorch-examples)
+repository under `muse_glimmer/macos/packages/livekit-plugins-executorch` and
+licensed under BSD-3-Clause. The source snapshot used for the macOS subtree
+migration is `914fb816fe9e0f6b7fc808fd843eb2e97df31dcf`.
+
+The package implements adapters against the public
+[`livekit/agents`](https://github.com/livekit/agents) plugin APIs at commit
+`bc5f3df3a2bd1b3b8c5d1df742be57b063374991`. This ExecuTorch plugin subtree did
+not exist in that upstream commit, and no LiveKit implementation source was
+copied into it.
+
+## Original source
+
+The original product files were reorganized under this package:
+
+- `livekit/plugins/executorch/__init__.py`
+- `livekit/plugins/executorch/_helper_process.py`
+- `livekit/plugins/executorch/log.py`
+- `livekit/plugins/executorch/py.typed`
+- `livekit/plugins/executorch/stt.py`
+- `livekit/plugins/executorch/supertonic_tts.py`
+- `livekit/plugins/executorch/version.py`
+- `tests/fake_helper.py`
+- `tests/fake_supertonic_runner.py`
+- `tests/test_helper_process.py`
+- `tests/test_stt.py`
+- `tests/test_supertonic_tts.py`
+
+The persistent Supertonic adapter and fake-runner tests are product-owned
+implementations of the native runner's strict `--server_jsonl` protocol.
+
+## Exclusions
+
+This source package does not include LiveKit or ExecuTorch implementation
+source, native runners, model weights, exported programs, tokenizers, voice
+styles, recordings, generated output, dependency source, or build and test
+caches. Those components retain their independent upstream licenses and
+notices.
diff --git a/muse_glimmer/macos/packages/livekit-plugins-executorch/README.md b/muse_glimmer/macos/packages/livekit-plugins-executorch/README.md
new file mode 100644
index 0000000000..6774062896
--- /dev/null
+++ b/muse_glimmer/macos/packages/livekit-plugins-executorch/README.md
@@ -0,0 +1,19 @@
+# LiveKit ExecuTorch plugin
+
+Local, source-only adapters for LiveKit Agents:
+
+- `executorch.STT` runs batch Parakeet ASR through one persistent framed helper.
+- `executorch.SupertonicTTS` owns one persistent `supertonic_runner --server_jsonl`
+ process and sends synthesis text only through stdin JSONL.
+
+Native binaries, model weights, tokenizers, voice styles, recordings, and generated
+outputs are deliberately outside this package. Supply explicit local artifact paths
+when constructing either provider.
+
+The adapters serialize requests because each native helper accepts one active request.
+Timeout, cancellation, protocol failure, and explicit close all terminate and reap the
+helper within configured bounds.
+
+Model weights and voice/style assets retain their upstream licenses. Supertonic 3 is
+distributed under the OpenRAIL-M license described by its model card; review it before
+redistributing model assets or generated output.
diff --git a/muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/__init__.py b/muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/__init__.py
new file mode 100644
index 0000000000..ba459016b5
--- /dev/null
+++ b/muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/__init__.py
@@ -0,0 +1,18 @@
+"""Local ExecuTorch providers for LiveKit Agents."""
+
+from livekit.agents import Plugin
+
+from .log import logger
+from .stt import STT
+from .supertonic_tts import SupertonicTTS
+from .version import __version__
+
+__all__ = ["STT", "SupertonicTTS", "__version__"]
+
+
+class ExecuTorchPlugin(Plugin):
+ def __init__(self) -> None:
+ super().__init__(__name__, __version__, __package__, logger)
+
+
+Plugin.register_plugin(ExecuTorchPlugin())
diff --git a/muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/_helper_process.py b/muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/_helper_process.py
new file mode 100644
index 0000000000..26e11b8de9
--- /dev/null
+++ b/muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/_helper_process.py
@@ -0,0 +1,265 @@
+from __future__ import annotations
+
+import asyncio
+import contextlib
+import json
+from collections import deque
+from collections.abc import Mapping, Sequence
+from typing import Any
+
+from .log import logger
+
+_DEFAULT_MAX_HEADER_BYTES = 64 * 1024
+_DEFAULT_MAX_PAYLOAD_BYTES = 64 * 1024 * 1024
+
+
+class HelperProcessError(RuntimeError):
+ """Raised when a native helper exits or violates the framed protocol."""
+
+
+class HelperProtocolError(HelperProcessError):
+ """Raised when a helper sends invalid framing or message data."""
+
+
+class HelperProcess:
+ """Async lifecycle and JSON-plus-binary framing for one native helper."""
+
+ def __init__(
+ self,
+ executable: str,
+ argv: Sequence[str] = (),
+ *,
+ name: str,
+ ready_timeout: float = 120.0,
+ shutdown_timeout: float = 2.0,
+ terminate_timeout: float = 2.0,
+ max_header_bytes: int = _DEFAULT_MAX_HEADER_BYTES,
+ max_payload_bytes: int = _DEFAULT_MAX_PAYLOAD_BYTES,
+ ) -> None:
+ if not executable:
+ raise ValueError("helper executable must be non-empty")
+ if max_header_bytes <= 0 or max_payload_bytes <= 0:
+ raise ValueError("helper framing limits must be positive")
+
+ self._executable = executable
+ self._argv = tuple(argv)
+ self._name = name
+ self._ready_timeout = ready_timeout
+ self._shutdown_timeout = shutdown_timeout
+ self._terminate_timeout = terminate_timeout
+ self._max_header_bytes = max_header_bytes
+ self._max_payload_bytes = max_payload_bytes
+ self._process: asyncio.subprocess.Process | None = None
+ self._write_lock = asyncio.Lock()
+ self._lifecycle_lock = asyncio.Lock()
+ self._stderr_task: asyncio.Task[None] | None = None
+ self._pending_read: asyncio.Task[tuple[dict[str, Any], bytes | None]] | None = None
+ self._ready_message: dict[str, Any] | None = None
+ self._stderr_tail: deque[str] = deque(maxlen=20)
+
+ @property
+ def running(self) -> bool:
+ return self._process is not None and self._process.returncode is None
+
+ @property
+ def stderr_tail(self) -> tuple[str, ...]:
+ return tuple(self._stderr_tail)
+
+ async def start(self) -> dict[str, Any]:
+ async with self._lifecycle_lock:
+ if self.running:
+ if self._ready_message is None:
+ raise HelperProcessError(f"{self._name} helper has no cached ready message")
+ return dict(self._ready_message)
+
+ await self._close_locked(graceful=False)
+ self._stderr_tail.clear()
+ try:
+ process = await asyncio.create_subprocess_exec(
+ self._executable,
+ *self._argv,
+ stdin=asyncio.subprocess.PIPE,
+ stdout=asyncio.subprocess.PIPE,
+ stderr=asyncio.subprocess.PIPE,
+ limit=self._max_header_bytes + 1,
+ )
+ except OSError as exc:
+ raise HelperProcessError(
+ f"failed to start {self._name} helper {self._executable!r}: {exc}"
+ ) from exc
+
+ self._process = process
+ self._stderr_task = asyncio.create_task(
+ self._drain_stderr(process), name=f"{self._name}-stderr"
+ )
+ try:
+ message, payload = await asyncio.wait_for(
+ self.read_message(), timeout=self._ready_timeout
+ )
+ except (TimeoutError, HelperProcessError) as exc:
+ await self._close_locked(graceful=False)
+ context = self._format_stderr_context()
+ if isinstance(exc, asyncio.TimeoutError):
+ raise HelperProcessError(
+ f"{self._name} helper did not become ready within "
+ f"{self._ready_timeout:.1f}s{context}"
+ ) from None
+ raise HelperProcessError(f"{exc}{context}") from exc
+
+ if payload is not None or message.get("type") != "ready" or message.get("version") != 1:
+ await self._close_locked(graceful=False)
+ raise HelperProtocolError(
+ f"{self._name} helper sent invalid ready message: {message!r}"
+ f"{self._format_stderr_context()}"
+ )
+ self._ready_message = dict(message)
+ return dict(message)
+
+ async def write_message(
+ self, message: Mapping[str, Any], payload: bytes | bytearray | memoryview | None = None
+ ) -> None:
+ process = self._require_running()
+ if process.stdin is None:
+ raise HelperProcessError(f"{self._name} helper stdin is unavailable")
+
+ payload_bytes = bytes(payload) if payload is not None else b""
+ if len(payload_bytes) > self._max_payload_bytes:
+ raise HelperProtocolError(
+ f"{self._name} helper payload exceeds {self._max_payload_bytes} bytes"
+ )
+ try:
+ header = json.dumps(dict(message), separators=(",", ":"), allow_nan=False).encode()
+ except (TypeError, ValueError) as exc:
+ raise HelperProtocolError(f"helper message is not valid JSON: {exc}") from exc
+ if b"\n" in header or len(header) > self._max_header_bytes:
+ raise HelperProtocolError("helper message header exceeds framing limits")
+
+ async with self._write_lock:
+ try:
+ process.stdin.write(header + b"\n")
+ if payload_bytes:
+ process.stdin.write(payload_bytes)
+ await process.stdin.drain()
+ except (BrokenPipeError, ConnectionResetError) as exc:
+ raise self._process_exit_error("failed to write helper request") from exc
+
+ async def read_message(self) -> tuple[dict[str, Any], bytes | None]:
+ if self._pending_read is None:
+ self._pending_read = asyncio.create_task(
+ self._read_message_impl(), name=f"{self._name}-read"
+ )
+ task = self._pending_read
+ try:
+ return await asyncio.shield(task)
+ finally:
+ if task.done() and self._pending_read is task:
+ self._pending_read = None
+
+ async def restart(self) -> dict[str, Any]:
+ async with self._lifecycle_lock:
+ await self._close_locked(graceful=False)
+ return await self.start()
+
+ async def aclose(self, *, graceful: bool = True) -> None:
+ async with self._lifecycle_lock:
+ await self._close_locked(graceful=graceful)
+
+ async def _read_message_impl(self) -> tuple[dict[str, Any], bytes | None]:
+ process = self._require_running()
+ if process.stdout is None:
+ raise HelperProcessError(f"{self._name} helper stdout is unavailable")
+ try:
+ line = await process.stdout.readline()
+ except ValueError as exc:
+ raise HelperProtocolError(
+ f"{self._name} helper header exceeds {self._max_header_bytes} bytes"
+ ) from exc
+ if not line:
+ raise self._process_exit_error("unexpected EOF from helper")
+ if not line.endswith(b"\n") or len(line) - 1 > self._max_header_bytes:
+ raise HelperProtocolError(
+ f"{self._name} helper header exceeds {self._max_header_bytes} bytes"
+ )
+ try:
+ parsed = json.loads(line)
+ except (UnicodeDecodeError, json.JSONDecodeError) as exc:
+ raise HelperProtocolError(f"{self._name} helper sent malformed JSON header") from exc
+ if not isinstance(parsed, dict):
+ raise HelperProtocolError(f"{self._name} helper header must be a JSON object")
+
+ payload_size = parsed.get("payload_byte_count", 0)
+ if isinstance(payload_size, bool) or not isinstance(payload_size, int) or payload_size < 0:
+ raise HelperProtocolError("helper payload_byte_count must be a non-negative integer")
+ if payload_size > self._max_payload_bytes:
+ raise HelperProtocolError(
+ f"{self._name} helper payload exceeds {self._max_payload_bytes} bytes"
+ )
+ if payload_size == 0:
+ return parsed, None
+ try:
+ payload = await process.stdout.readexactly(payload_size)
+ except asyncio.IncompleteReadError as exc:
+ raise HelperProcessError(
+ f"unexpected EOF reading {self._name} helper payload: "
+ f"expected {payload_size}, received {len(exc.partial)}"
+ ) from exc
+ return parsed, payload
+
+ async def _drain_stderr(self, process: asyncio.subprocess.Process) -> None:
+ if process.stderr is None:
+ return
+ while line := await process.stderr.readline():
+ text = line.decode(errors="replace").rstrip()
+ self._stderr_tail.append(text)
+ logger.debug("%s helper: %s", self._name, text)
+
+ async def _close_locked(self, *, graceful: bool) -> None:
+ process = self._process
+ if process is None:
+ return
+
+ if graceful and process.returncode is None:
+ with contextlib.suppress(HelperProcessError, HelperProtocolError):
+ await self.write_message({"type": "shutdown", "version": 1})
+ if process.returncode is None and graceful:
+ with contextlib.suppress(TimeoutError):
+ await asyncio.wait_for(process.wait(), timeout=self._shutdown_timeout)
+ if process.returncode is None:
+ process.terminate()
+ try:
+ await asyncio.wait_for(process.wait(), timeout=self._terminate_timeout)
+ except TimeoutError:
+ process.kill()
+ await process.wait()
+
+ if process.stdin is not None:
+ process.stdin.close()
+ with contextlib.suppress(BrokenPipeError, ConnectionResetError):
+ await process.stdin.wait_closed()
+
+ if self._pending_read is not None:
+ self._pending_read.cancel()
+ with contextlib.suppress(asyncio.CancelledError, HelperProcessError):
+ await self._pending_read
+ self._pending_read = None
+ if self._stderr_task is not None:
+ with contextlib.suppress(asyncio.CancelledError):
+ await self._stderr_task
+ self._stderr_task = None
+ self._process = None
+ self._ready_message = None
+
+ def _require_running(self) -> asyncio.subprocess.Process:
+ if not self.running or self._process is None:
+ raise self._process_exit_error("helper is not running")
+ return self._process
+
+ def _process_exit_error(self, message: str) -> HelperProcessError:
+ returncode = self._process.returncode if self._process is not None else None
+ suffix = f" (exit code {returncode})" if returncode is not None else ""
+ return HelperProcessError(f"{self._name} {message}{suffix}{self._format_stderr_context()}")
+
+ def _format_stderr_context(self) -> str:
+ if not self._stderr_tail:
+ return ""
+ return "; recent stderr: " + " | ".join(self._stderr_tail)
diff --git a/muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/log.py b/muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/log.py
new file mode 100644
index 0000000000..6b81deaf67
--- /dev/null
+++ b/muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/log.py
@@ -0,0 +1,3 @@
+import logging
+
+logger = logging.getLogger("livekit.plugins.executorch")
diff --git a/muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/py.typed b/muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/py.typed
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/stt.py b/muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/stt.py
new file mode 100644
index 0000000000..1c1083a80b
--- /dev/null
+++ b/muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/stt.py
@@ -0,0 +1,241 @@
+from __future__ import annotations
+
+import asyncio
+import contextlib
+import sys
+from array import array
+from collections.abc import Sequence
+from pathlib import Path
+from typing import Any
+
+from livekit.agents import (
+ APIConnectionError,
+ APIConnectOptions,
+ APIError,
+ APITimeoutError,
+ LanguageCode,
+ stt,
+ utils,
+)
+from livekit.agents.types import NOT_GIVEN, NotGivenOr
+from livekit.agents.utils import is_given
+
+from livekit import rtc
+
+from ._helper_process import HelperProcess, HelperProcessError, HelperProtocolError
+from .log import logger
+
+_SAMPLE_RATE = 16000
+
+
+class STT(stt.STT):
+ """Batch Parakeet STT backed by a persistent ExecuTorch helper."""
+
+ def __init__(
+ self,
+ *,
+ helper_path: str | Path,
+ model_path: str | Path,
+ tokenizer_path: str | Path,
+ delegate_data_path: str | Path | None = None,
+ language: str = "en",
+ ready_timeout: float = 120.0,
+ _helper: HelperProcess | None = None,
+ ) -> None:
+ super().__init__(
+ capabilities=stt.STTCapabilities(
+ streaming=False,
+ interim_results=False,
+ offline_recognize=True,
+ )
+ )
+ self._model_path = str(model_path)
+ self._language = LanguageCode(language)
+ argv = [f"--model_path={model_path}", f"--tokenizer_path={tokenizer_path}"]
+ if delegate_data_path is not None:
+ argv.append(f"--data_path={delegate_data_path}")
+ self._helper = _helper or HelperProcess(
+ str(helper_path), argv, name="parakeet", ready_timeout=ready_timeout
+ )
+ self._recognize_lock = asyncio.Lock()
+ self._prewarm_task: asyncio.Task[dict[str, Any]] | None = None
+
+ @property
+ def model(self) -> str:
+ return self._model_path
+
+ @property
+ def provider(self) -> str:
+ return "ExecuTorch"
+
+ def prewarm(self) -> None:
+ if self._prewarm_task is None:
+ try:
+ loop = asyncio.get_running_loop()
+ except RuntimeError:
+ return
+ self._prewarm_task = loop.create_task(self._helper.start())
+
+ async def start(self) -> None:
+ """Start the Parakeet helper and wait for its ready message."""
+ await self._ensure_ready()
+
+ async def _ensure_ready(self) -> None:
+ if self._prewarm_task is not None:
+ task, self._prewarm_task = self._prewarm_task, None
+ await task
+ else:
+ await self._helper.start()
+
+ async def _recognize_impl(
+ self,
+ buffer: utils.AudioBuffer,
+ *,
+ language: NotGivenOr[str] = NOT_GIVEN,
+ conn_options: APIConnectOptions,
+ ) -> stt.SpeechEvent:
+ async with self._recognize_lock:
+ request_id = utils.shortuuid("parakeet_")
+ payload = _audio_buffer_to_f32le(buffer)
+ message = {
+ "type": "transcribe",
+ "version": 1,
+ "request_id": request_id,
+ "audio": {
+ "encoding": "f32le",
+ "sample_rate": _SAMPLE_RATE,
+ "channel_count": 1,
+ "payload_byte_count": len(payload),
+ },
+ "enable_runtime_profile": False,
+ }
+ try:
+ await self._ensure_ready()
+ response = await asyncio.wait_for(
+ self._request(message, payload, request_id), timeout=conn_options.timeout
+ )
+ except asyncio.CancelledError:
+ await asyncio.shield(self._helper.aclose(graceful=False))
+ raise
+ except TimeoutError:
+ await self._helper.aclose(graceful=False)
+ raise APITimeoutError("Parakeet transcription timed out") from None
+ except HelperProtocolError as exc:
+ await self._helper.aclose(graceful=False)
+ raise APIError(str(exc), retryable=False) from exc
+ except HelperProcessError as exc:
+ await self._helper.aclose(graceful=False)
+ raise APIConnectionError("Parakeet helper failed") from exc
+
+ transcript_language = LanguageCode(language) if is_given(language) else self._language
+ return stt.SpeechEvent(
+ type=stt.SpeechEventType.FINAL_TRANSCRIPT,
+ request_id=request_id,
+ alternatives=[
+ stt.SpeechData(
+ language=transcript_language,
+ text=response["text"],
+ metadata={
+ "provider": self.provider,
+ "model": self.model,
+ "runtime": "parakeet_helper",
+ },
+ )
+ ],
+ )
+
+ async def _request(
+ self, message: dict[str, Any], payload: bytes, request_id: str
+ ) -> dict[str, Any]:
+ await self._helper.write_message(message, payload)
+ while True:
+ response, response_payload = await self._helper.read_message()
+ if response_payload is not None:
+ raise HelperProtocolError("Parakeet response must not contain a binary payload")
+ if response.get("version") != 1:
+ raise HelperProtocolError("Parakeet response has unsupported protocol version")
+ if response.get("request_id") != request_id:
+ raise HelperProtocolError("Parakeet response request_id does not match")
+ response_type = response.get("type")
+ if response_type == "status":
+ logger.debug("Parakeet status: %s", response.get("message", response.get("phase")))
+ continue
+ if response_type == "result":
+ _required_string(response, "text")
+ return response
+ if response_type == "error":
+ details = response.get("details")
+ error_message = str(response.get("message", "Parakeet transcription failed"))
+ if details:
+ error_message = f"{error_message}: {details}"
+ raise APIError(error_message, body=response, retryable=False)
+ raise HelperProtocolError(f"unexpected Parakeet response type: {response_type!r}")
+
+ async def aclose(self) -> None:
+ if self._prewarm_task is not None:
+ if not self._prewarm_task.done():
+ self._prewarm_task.cancel()
+ with contextlib.suppress(asyncio.CancelledError, HelperProcessError):
+ await self._prewarm_task
+ self._prewarm_task = None
+ await self._helper.aclose()
+
+
+def _required_string(message: dict[str, Any], key: str) -> str:
+ value = message.get(key)
+ if not isinstance(value, str):
+ raise HelperProtocolError(f"Parakeet response field {key!r} must be a string")
+ return value
+
+
+def _audio_buffer_to_f32le(buffer: utils.AudioBuffer) -> bytes:
+ frame = rtc.combine_audio_frames(buffer)
+ if frame.samples_per_channel == 0:
+ return b""
+
+ mono_samples = _downmix_s16(frame)
+ mono_frame = rtc.AudioFrame(
+ data=_s16le_bytes(mono_samples),
+ sample_rate=frame.sample_rate,
+ num_channels=1,
+ samples_per_channel=len(mono_samples),
+ )
+ if mono_frame.sample_rate != _SAMPLE_RATE:
+ resampler = rtc.AudioResampler(
+ input_rate=mono_frame.sample_rate,
+ output_rate=_SAMPLE_RATE,
+ num_channels=1,
+ quality=rtc.AudioResamplerQuality.HIGH,
+ )
+ frames = [*resampler.push(mono_frame), *resampler.flush()]
+ mono_frame = rtc.combine_audio_frames(frames)
+
+ samples = array("h")
+ samples.frombytes(mono_frame.data.tobytes())
+ if sys.byteorder != "little":
+ samples.byteswap()
+ floats = array("f", (sample / 32768.0 for sample in samples))
+ if sys.byteorder != "little":
+ floats.byteswap()
+ return floats.tobytes()
+
+
+def _downmix_s16(frame: rtc.AudioFrame) -> Sequence[int]:
+ samples = array("h")
+ samples.frombytes(frame.data.tobytes())
+ if sys.byteorder != "little":
+ samples.byteswap()
+ if frame.num_channels == 1:
+ return samples
+ channels = frame.num_channels
+ return [
+ sum(samples[index : index + channels]) // channels
+ for index in range(0, len(samples), channels)
+ ]
+
+
+def _s16le_bytes(samples: Sequence[int]) -> bytes:
+ output = array("h", samples)
+ if sys.byteorder != "little":
+ output.byteswap()
+ return output.tobytes()
diff --git a/muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/supertonic_tts.py b/muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/supertonic_tts.py
new file mode 100644
index 0000000000..9a9873eec8
--- /dev/null
+++ b/muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/supertonic_tts.py
@@ -0,0 +1,487 @@
+from __future__ import annotations
+
+import asyncio
+import contextlib
+import json
+import math
+import os
+import tempfile
+import wave
+from collections import deque
+from dataclasses import dataclass
+from pathlib import Path
+from typing import Any
+
+from livekit.agents import (
+ APIConnectionError,
+ APIConnectOptions,
+ APIError,
+ APITimeoutError,
+ tts,
+ utils,
+)
+from livekit.agents.types import DEFAULT_API_CONNECT_OPTIONS
+
+_SAMPLE_RATE = 44100
+_NUM_CHANNELS = 1
+_SAMPLE_WIDTH_BYTES = 2
+_MAX_JSON_LINE_BYTES = 64 * 1024
+_MAX_STDERR_LINES = 20
+
+
+class _ProtocolError(RuntimeError):
+ pass
+
+
+@dataclass(frozen=True)
+class _SupertonicOptions:
+ runner_path: str
+ pte_path: str
+ asset_dir: str
+ voice_style_path: str
+ language: str
+ speed: float
+ seed: int
+
+
+class SupertonicTTS(tts.TTS):
+ """Batch TTS backed by one persistent Supertonic JSONL server process."""
+
+ def __init__(
+ self,
+ *,
+ runner_path: str | Path,
+ pte_path: str | Path,
+ asset_dir: str | Path,
+ voice_style_path: str | Path,
+ language: str = "en",
+ speed: float = 1.05,
+ seed: int = 42,
+ ready_timeout: float = 120.0,
+ shutdown_timeout: float = 2.0,
+ terminate_timeout: float = 2.0,
+ ) -> None:
+ runner = _required_file(runner_path, "runner_path", executable=True)
+ pte = _required_file(pte_path, "pte_path")
+ assets = _required_directory(asset_dir, "asset_dir")
+ voice_style = _required_file(voice_style_path, "voice_style_path")
+ if not language.strip():
+ raise ValueError("language must be non-empty")
+ if not math.isfinite(speed) or speed <= 0.0:
+ raise ValueError("speed must be finite and positive")
+ if isinstance(seed, bool) or not isinstance(seed, int) or seed < 0:
+ raise ValueError("seed must be a non-negative integer")
+ for name, value in (
+ ("ready_timeout", ready_timeout),
+ ("shutdown_timeout", shutdown_timeout),
+ ("terminate_timeout", terminate_timeout),
+ ):
+ if not math.isfinite(value) or value <= 0.0:
+ raise ValueError(f"{name} must be finite and positive")
+
+ super().__init__(
+ capabilities=tts.TTSCapabilities(streaming=False),
+ sample_rate=_SAMPLE_RATE,
+ num_channels=_NUM_CHANNELS,
+ )
+ self._opts = _SupertonicOptions(
+ runner_path=str(runner),
+ pte_path=str(pte),
+ asset_dir=str(assets),
+ voice_style_path=str(voice_style),
+ language=language.strip(),
+ speed=speed,
+ seed=seed,
+ )
+ self._ready_timeout = ready_timeout
+ self._shutdown_timeout = shutdown_timeout
+ self._terminate_timeout = terminate_timeout
+ self._synthesis_lock = asyncio.Lock()
+ self._lifecycle_lock = asyncio.Lock()
+ self._process: asyncio.subprocess.Process | None = None
+ self._stderr_task: asyncio.Task[None] | None = None
+ self._stderr_tail: deque[str] = deque(maxlen=_MAX_STDERR_LINES)
+ self._request_active = False
+ self._next_request_id = 1
+ self._closed = False
+
+ @property
+ def model(self) -> str:
+ return self._opts.pte_path
+
+ @property
+ def provider(self) -> str:
+ return "ExecuTorch Supertonic"
+
+ @property
+ def running(self) -> bool:
+ return self._process is not None and self._process.returncode is None
+
+ def synthesize(
+ self, text: str, *, conn_options: APIConnectOptions = DEFAULT_API_CONNECT_OPTIONS
+ ) -> ChunkedStream:
+ return ChunkedStream(tts=self, input_text=text, conn_options=conn_options)
+
+ async def aclose(self) -> None:
+ self._closed = True
+ async with self._lifecycle_lock:
+ process = self._process
+ if process is None:
+ return
+ if self._request_active:
+ await self._stop_locked(process, graceful=False)
+ return
+ try:
+ await self._write_json(process, {"type": "shutdown"})
+ response = await asyncio.wait_for(
+ self._read_json(process), timeout=self._shutdown_timeout
+ )
+ if response != {"type": "stopped"}:
+ raise _ProtocolError(f"Supertonic sent invalid shutdown response: {response!r}")
+ if not await _wait_for_exit(process, self._shutdown_timeout):
+ raise TimeoutError
+ except (TimeoutError, BrokenPipeError, ConnectionResetError, _ProtocolError):
+ await self._stop_locked(process, graceful=False)
+ else:
+ await self._clear_process_locked(process)
+
+ def _command(self) -> tuple[str, ...]:
+ return (
+ self._opts.runner_path,
+ "--server_jsonl=true",
+ f"--pte={self._opts.pte_path}",
+ f"--asset_dir={self._opts.asset_dir}",
+ f"--voice_style={self._opts.voice_style_path}",
+ f"--language={self._opts.language}",
+ f"--speed={self._opts.speed}",
+ f"--seed={self._opts.seed}",
+ )
+
+ async def _ensure_started(self) -> asyncio.subprocess.Process:
+ async with self._lifecycle_lock:
+ if self._closed:
+ raise APIConnectionError("Supertonic TTS is closed", retryable=False)
+ if self.running and self._process is not None:
+ return self._process
+ if self._process is not None:
+ await self._stop_locked(self._process, graceful=False)
+
+ self._stderr_tail.clear()
+ try:
+ process = await asyncio.create_subprocess_exec(
+ *self._command(),
+ stdin=asyncio.subprocess.PIPE,
+ stdout=asyncio.subprocess.PIPE,
+ stderr=asyncio.subprocess.PIPE,
+ limit=_MAX_JSON_LINE_BYTES + 1,
+ )
+ except OSError as exc:
+ raise APIConnectionError(f"failed to start Supertonic runner: {exc}") from exc
+ self._process = process
+ self._stderr_task = asyncio.create_task(
+ self._drain_stderr(process), name="supertonic-stderr"
+ )
+ try:
+ ready = await asyncio.wait_for(
+ self._read_json(process), timeout=self._ready_timeout
+ )
+ _validate_ready(ready)
+ except asyncio.CancelledError:
+ await asyncio.shield(self._stop_locked(process, graceful=False))
+ raise
+ except TimeoutError:
+ await self._stop_locked(process, graceful=False)
+ raise APITimeoutError(
+ self._with_stderr(
+ f"Supertonic did not become ready within {self._ready_timeout:.1f}s"
+ )
+ ) from None
+ except (OSError, _ProtocolError) as exc:
+ await self._stop_locked(process, graceful=False)
+ raise APIConnectionError(self._with_stderr(str(exc))) from exc
+ if self._closed:
+ await self._stop_locked(process, graceful=False)
+ raise APIConnectionError("Supertonic TTS is closed", retryable=False)
+ return process
+
+ async def _request(self, text: str, output_path: Path, timeout: float) -> None:
+ process = await self._ensure_started()
+ request_id = self._next_request_id
+ self._next_request_id += 1
+ request = {
+ "type": "synthesize",
+ "id": request_id,
+ "text": text,
+ "output": str(output_path),
+ }
+ async with self._lifecycle_lock:
+ if process is not self._process or process.returncode is not None or self._closed:
+ raise APIConnectionError("Supertonic runner is unavailable")
+ self._request_active = True
+ try:
+ await self._write_json(process, request)
+ except asyncio.CancelledError:
+ self._request_active = False
+ await asyncio.shield(self._stop_locked(process, graceful=False))
+ raise
+ except _ProtocolError as exc:
+ self._request_active = False
+ raise APIError(str(exc), retryable=False) from exc
+ except (BrokenPipeError, ConnectionResetError, OSError) as exc:
+ self._request_active = False
+ await self._stop_locked(process, graceful=False)
+ raise APIConnectionError(
+ self._with_stderr("Supertonic request write failed")
+ ) from exc
+
+ try:
+ response = await asyncio.wait_for(self._read_json(process), timeout=timeout)
+ _validate_response(response, request_id, output_path)
+ except TimeoutError:
+ await asyncio.shield(self._stop(process))
+ raise APITimeoutError(self._with_stderr("Supertonic synthesis timed out")) from None
+ except asyncio.CancelledError:
+ await asyncio.shield(self._stop(process))
+ raise
+ except _ProtocolError as exc:
+ await self._stop(process)
+ raise APIError(self._with_stderr(str(exc)), retryable=False) from exc
+ except (BrokenPipeError, ConnectionResetError, OSError) as exc:
+ await self._stop(process)
+ raise APIConnectionError(self._with_stderr("Supertonic runner failed")) from exc
+ finally:
+ async with self._lifecycle_lock:
+ self._request_active = False
+
+ if response["type"] == "error":
+ raise APIError(str(response["message"]), body=response, retryable=False)
+
+ async def _stop(self, process: asyncio.subprocess.Process) -> None:
+ async with self._lifecycle_lock:
+ await self._stop_locked(process, graceful=False)
+
+ async def _stop_locked(self, process: asyncio.subprocess.Process, *, graceful: bool) -> None:
+ if process.returncode is None and graceful:
+ with contextlib.suppress(BrokenPipeError, ConnectionResetError, OSError):
+ await self._write_json(process, {"type": "shutdown"})
+ await _wait_for_exit(process, self._shutdown_timeout)
+ if process.returncode is None:
+ with contextlib.suppress(ProcessLookupError):
+ process.terminate()
+ if not await _wait_for_exit(process, self._terminate_timeout):
+ with contextlib.suppress(ProcessLookupError):
+ process.kill()
+ if not await _wait_for_exit(process, self._terminate_timeout):
+ raise RuntimeError("Supertonic runner did not exit after SIGKILL")
+ await self._clear_process_locked(process)
+
+ async def _clear_process_locked(self, process: asyncio.subprocess.Process) -> None:
+ stderr_task, self._stderr_task = self._stderr_task, None
+ if stderr_task is not None and not stderr_task.done():
+ stderr_task.cancel()
+ if process.stdin is not None:
+ process.stdin.close()
+ if self._process is process:
+ self._process = None
+ await asyncio.sleep(0)
+
+ async def _write_json(
+ self, process: asyncio.subprocess.Process, message: dict[str, object]
+ ) -> None:
+ if process.stdin is None:
+ raise OSError("Supertonic stdin is unavailable")
+ encoded = json.dumps(message, separators=(",", ":"), allow_nan=False).encode("utf-8")
+ if len(encoded) > _MAX_JSON_LINE_BYTES:
+ raise _ProtocolError("Supertonic request exceeds the JSONL size limit")
+ process.stdin.write(encoded + b"\n")
+ await process.stdin.drain()
+
+ async def _read_json(self, process: asyncio.subprocess.Process) -> dict[str, Any]:
+ if process.stdout is None:
+ raise OSError("Supertonic stdout is unavailable")
+ try:
+ line = await process.stdout.readline()
+ except ValueError as exc:
+ raise _ProtocolError("Supertonic response exceeds the JSONL size limit") from exc
+ if not line:
+ returncode = await process.wait()
+ raise _ProtocolError(f"Supertonic runner exited unexpectedly with code {returncode}")
+ if not line.endswith(b"\n") or len(line) - 1 > _MAX_JSON_LINE_BYTES:
+ raise _ProtocolError("Supertonic response exceeds the JSONL size limit")
+ try:
+ response = json.loads(
+ line,
+ parse_constant=lambda value: (_ for _ in ()).throw(
+ ValueError(f"non-finite number {value}")
+ ),
+ )
+ except (UnicodeDecodeError, json.JSONDecodeError, ValueError) as exc:
+ raise _ProtocolError("Supertonic sent invalid JSON") from exc
+ if not isinstance(response, dict):
+ raise _ProtocolError("Supertonic response must be a JSON object")
+ return response
+
+ async def _drain_stderr(self, process: asyncio.subprocess.Process) -> None:
+ if process.stderr is None:
+ return
+ while line := await process.stderr.readline():
+ self._stderr_tail.append(line.decode(errors="replace").rstrip())
+
+ def _with_stderr(self, message: str) -> str:
+ if not self._stderr_tail:
+ return message
+ return f"{message}; recent stderr: {' | '.join(self._stderr_tail)}"
+
+
+class ChunkedStream(tts.ChunkedStream):
+ def __init__(
+ self,
+ *,
+ tts: SupertonicTTS,
+ input_text: str,
+ conn_options: APIConnectOptions,
+ ) -> None:
+ super().__init__(tts=tts, input_text=input_text, conn_options=conn_options)
+ self._tts: SupertonicTTS = tts
+
+ async def _run(self, output_emitter: tts.AudioEmitter) -> None:
+ if not self._input_text:
+ raise APIError("Supertonic synthesis text must be non-empty", retryable=False)
+ async with self._tts._synthesis_lock:
+ if self._tts._closed:
+ raise APIConnectionError("Supertonic TTS is closed", retryable=False)
+ with tempfile.TemporaryDirectory(prefix="livekit-supertonic-") as temporary:
+ output_path = Path(temporary) / "speech.wav"
+ await self._tts._request(self._input_text, output_path, self._conn_options.timeout)
+ try:
+ payload = await asyncio.to_thread(_read_pcm_wav, output_path)
+ except (OSError, EOFError, wave.Error, ValueError) as exc:
+ raise APIError(
+ f"Supertonic produced invalid audio: {exc}", retryable=False
+ ) from exc
+
+ output_emitter.initialize(
+ request_id=utils.shortuuid("supertonic_"),
+ sample_rate=_SAMPLE_RATE,
+ num_channels=_NUM_CHANNELS,
+ mime_type="audio/pcm",
+ frame_size_ms=50,
+ )
+ output_emitter.push(payload)
+ output_emitter.flush()
+
+
+async def _wait_for_exit(process: asyncio.subprocess.Process, timeout: float) -> bool:
+ if process.returncode is not None:
+ return True
+ wait_task = asyncio.create_task(process.wait())
+ try:
+ await asyncio.wait_for(asyncio.shield(wait_task), timeout=timeout)
+ return True
+ except TimeoutError:
+ return process.returncode is not None
+ finally:
+ if not wait_task.done():
+ wait_task.cancel()
+
+
+def _validate_ready(response: dict[str, Any]) -> None:
+ expected = {
+ "type",
+ "protocol_version",
+ "sample_rate",
+ "load_seconds",
+ "warmup_seconds",
+ }
+ if set(response) != expected or response.get("type") != "ready":
+ raise _ProtocolError(f"Supertonic sent invalid ready response: {response!r}")
+ if response.get("protocol_version") != 1:
+ raise _ProtocolError("Supertonic uses an unsupported protocol version")
+ if response.get("sample_rate") != _SAMPLE_RATE:
+ raise _ProtocolError(f"Supertonic must use {_SAMPLE_RATE} Hz")
+ for field in ("load_seconds", "warmup_seconds"):
+ value = response.get(field)
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
+ raise _ProtocolError(f"Supertonic ready field {field!r} must be numeric")
+ if not math.isfinite(value) or value < 0.0:
+ raise _ProtocolError(
+ f"Supertonic ready field {field!r} must be finite and non-negative"
+ )
+
+
+def _validate_response(response: dict[str, Any], request_id: int, output_path: Path) -> None:
+ response_type = response.get("type")
+ if response_type == "error":
+ if set(response) != {"type", "id", "message"}:
+ raise _ProtocolError("Supertonic error response has unexpected fields")
+ if response.get("id") != request_id or not isinstance(response.get("message"), str):
+ raise _ProtocolError("Supertonic error response is invalid")
+ return
+ expected = {
+ "type",
+ "id",
+ "output",
+ "samples",
+ "audio_seconds",
+ "synthesis_seconds",
+ "rtf",
+ }
+ if response_type != "result" or set(response) != expected:
+ raise _ProtocolError(f"Supertonic sent invalid synthesis response: {response!r}")
+ if response.get("id") != request_id:
+ raise _ProtocolError("Supertonic response id does not match the request")
+ if response.get("output") != str(output_path):
+ raise _ProtocolError("Supertonic response output does not match the request")
+ samples = response.get("samples")
+ if isinstance(samples, bool) or not isinstance(samples, int) or samples <= 0:
+ raise _ProtocolError("Supertonic response samples must be a positive integer")
+ for field in ("audio_seconds", "synthesis_seconds", "rtf"):
+ value = response.get(field)
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
+ raise _ProtocolError(f"Supertonic response field {field!r} must be numeric")
+ if not math.isfinite(value) or value < 0.0:
+ raise _ProtocolError(
+ f"Supertonic response field {field!r} must be finite and non-negative"
+ )
+
+
+def _required_file(value: str | Path, name: str, *, executable: bool = False) -> Path:
+ path = Path(value).expanduser().resolve()
+ if not path.is_file():
+ raise ValueError(f"{name} must point to a file: {path}")
+ if executable and not os.access(path, os.X_OK):
+ raise ValueError(f"{name} must point to an executable file: {path}")
+ return path
+
+
+def _required_directory(value: str | Path, name: str) -> Path:
+ path = Path(value).expanduser().resolve()
+ if not path.is_dir():
+ raise ValueError(f"{name} must point to a directory: {path}")
+ return path
+
+
+def _read_pcm_wav(path: Path) -> bytes:
+ if not path.is_file():
+ raise ValueError("output WAV is missing")
+ with wave.open(str(path), "rb") as output:
+ channels = output.getnchannels()
+ sample_rate = output.getframerate()
+ sample_width = output.getsampwidth()
+ compression = output.getcomptype()
+ frame_count = output.getnframes()
+ payload = output.readframes(frame_count)
+ if compression != "NONE":
+ raise ValueError("output WAV must be uncompressed PCM")
+ if channels != _NUM_CHANNELS:
+ raise ValueError("output WAV must be mono")
+ if sample_rate != _SAMPLE_RATE:
+ raise ValueError(f"output WAV must use {_SAMPLE_RATE} Hz")
+ if sample_width != _SAMPLE_WIDTH_BYTES:
+ raise ValueError("output WAV must use signed PCM16 samples")
+ expected_bytes = frame_count * channels * sample_width
+ if frame_count <= 0 or not payload:
+ raise ValueError("output WAV contains no audio")
+ if len(payload) != expected_bytes:
+ raise ValueError("output WAV is truncated")
+ return payload
diff --git a/muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/version.py b/muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/version.py
new file mode 100644
index 0000000000..3dc1f76bc6
--- /dev/null
+++ b/muse_glimmer/macos/packages/livekit-plugins-executorch/livekit/plugins/executorch/version.py
@@ -0,0 +1 @@
+__version__ = "0.1.0"
diff --git a/muse_glimmer/macos/packages/livekit-plugins-executorch/pyproject.toml b/muse_glimmer/macos/packages/livekit-plugins-executorch/pyproject.toml
new file mode 100644
index 0000000000..00f5fba11c
--- /dev/null
+++ b/muse_glimmer/macos/packages/livekit-plugins-executorch/pyproject.toml
@@ -0,0 +1,62 @@
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[project]
+name = "livekit-plugins-executorch"
+dynamic = ["version"]
+description = "Local ExecuTorch Parakeet and Supertonic adapters for LiveKit Agents"
+readme = "README.md"
+requires-python = ">=3.13,<3.14"
+license = "BSD-3-Clause"
+license-files = ["LICENSE", "PROVENANCE.md"]
+keywords = ["voice", "livekit", "executorch", "parakeet", "supertonic"]
+classifiers = [
+ "License :: OSI Approved :: BSD License",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.13",
+ "Topic :: Multimedia :: Sound/Audio",
+]
+dependencies = ["livekit-agents>=1.6.9,<2"]
+
+[dependency-groups]
+dev = [
+ "pytest>=8.4,<9",
+ "pytest-asyncio>=0.25,<2",
+ "ruff>=0.12,<1",
+]
+
+[tool.hatch.version]
+path = "livekit/plugins/executorch/version.py"
+
+[tool.hatch.build]
+include = [
+ "/LICENSE",
+ "/PROVENANCE.md",
+ "/README.md",
+ "/livekit",
+]
+
+[tool.hatch.build.targets.wheel]
+packages = ["livekit"]
+
+[tool.hatch.build.targets.sdist]
+include = [
+ "/LICENSE",
+ "/PROVENANCE.md",
+ "/README.md",
+ "/livekit",
+ "/tests",
+]
+
+[tool.pytest.ini_options]
+asyncio_mode = "auto"
+asyncio_default_fixture_loop_scope = "function"
+markers = ["unit: hermetic unit tests"]
+
+[tool.ruff]
+line-length = 100
+target-version = "py313"
+
+[tool.ruff.lint]
+select = ["E", "F", "I", "UP", "B", "SIM"]
diff --git a/muse_glimmer/macos/packages/livekit-plugins-executorch/tests/fake_helper.py b/muse_glimmer/macos/packages/livekit-plugins-executorch/tests/fake_helper.py
new file mode 100644
index 0000000000..f23d7b2187
--- /dev/null
+++ b/muse_glimmer/macos/packages/livekit-plugins-executorch/tests/fake_helper.py
@@ -0,0 +1,207 @@
+#!/usr/bin/env python3
+from __future__ import annotations
+
+import json
+import os
+import struct
+import sys
+import time
+
+
+def send(message: dict[str, object], payload: bytes | None = None) -> None:
+ sys.stdout.buffer.write(json.dumps(message, separators=(",", ":")).encode() + b"\n")
+ if payload is not None:
+ sys.stdout.buffer.write(payload)
+ sys.stdout.buffer.flush()
+
+
+def main() -> int:
+ mode = os.environ.get("FAKE_HELPER_MODE", "stt")
+ if mode == "timeout":
+ time.sleep(60)
+ return 0
+ if mode == "stderr_crash":
+ print("model load exploded", file=sys.stderr, flush=True)
+ return 17
+ if mode == "malformed_ready":
+ sys.stdout.buffer.write(b"not-json\n")
+ sys.stdout.buffer.flush()
+ return 0
+ if mode == "oversized":
+ send({"type": "ready", "version": 1})
+ send({"type": "audio_chunk", "version": 1, "payload_byte_count": 999999})
+ return 0
+
+ if mode.startswith("tts"):
+ send(
+ {
+ "type": "ready",
+ "version": 1,
+ "sample_rate": 24000,
+ "channel_count": 1,
+ "encoding": "f32le",
+ }
+ )
+ else:
+ send({"type": "ready", "version": 1})
+
+ active_request_id: str | None = None
+ for raw_line in sys.stdin.buffer:
+ request = json.loads(raw_line)
+ request_type = request.get("type")
+ if request_type == "shutdown":
+ if mode == "ignore_shutdown":
+ continue
+ return 0
+ if request_type == "transcribe":
+ audio = request["audio"]
+ payload = sys.stdin.buffer.read(audio["payload_byte_count"])
+ if mode == "eof":
+ return 3
+ if mode == "stt_slow":
+ time.sleep(60)
+ continue
+ if audio != {
+ "encoding": "f32le",
+ "sample_rate": 16000,
+ "channel_count": 1,
+ "payload_byte_count": len(payload),
+ }:
+ send(
+ {
+ "type": "error",
+ "version": 1,
+ "request_id": request["request_id"],
+ "message": "invalid audio descriptor",
+ }
+ )
+ continue
+ if mode == "stt_bad_result":
+ send(
+ {
+ "type": "result",
+ "version": 1,
+ "request_id": request["request_id"],
+ "text": 42,
+ }
+ )
+ continue
+ if mode == "stt_error":
+ send(
+ {
+ "type": "error",
+ "version": 1,
+ "request_id": request["request_id"],
+ "message": "bad audio",
+ "details": "fake failure",
+ }
+ )
+ continue
+ samples = struct.unpack(f"<{len(payload) // 4}f", payload)
+ send(
+ {
+ "type": "status",
+ "version": 1,
+ "request_id": request["request_id"],
+ "phase": "running_encoder",
+ "message": "Running encoder...",
+ }
+ )
+ send(
+ {
+ "type": "result",
+ "version": 1,
+ "request_id": request["request_id"],
+ "text": ",".join(f"{sample:.3f}" for sample in samples[:4]),
+ "audio_descriptor": audio,
+ }
+ )
+ continue
+ if request_type == "synthesize":
+ request_id = request["request_id"]
+ active_request_id = request_id
+ if (
+ request.get("voice") != "voice.pt"
+ or request.get("temperature") != 0.25
+ or request.get("max_new_tokens") != 321
+ ):
+ send(
+ {
+ "type": "error",
+ "version": 1,
+ "request_id": request_id,
+ "message": "invalid synthesis options",
+ }
+ )
+ continue
+ if mode == "tts_error":
+ send(
+ {
+ "type": "error",
+ "version": 1,
+ "request_id": request_id,
+ "message": "voice missing",
+ }
+ )
+ continue
+ if mode == "tts_cancel_timeout":
+ time.sleep(60)
+ continue
+ if mode == "tts_slow":
+ time.sleep(60)
+ continue
+ if mode == "tts_wait_cancel":
+ continue
+ if mode == "tts_finish_cancel_race":
+ send(
+ {
+ "type": "result",
+ "version": 1,
+ "request_id": request_id,
+ "cancelled": False,
+ "sample_count": 0,
+ }
+ )
+ active_request_id = None
+ continue
+ chunks = [(-1.5, -1.0, -0.5, 0.0), (0.5, 1.0, 1.5)]
+ for chunk in chunks:
+ if mode == "tts_progressive":
+ chunk = chunk * 300
+ payload = struct.pack(f"<{len(chunk)}f", *chunk)
+ send(
+ {
+ "type": "audio_chunk",
+ "version": 1,
+ "request_id": request_id,
+ "payload_byte_count": len(payload),
+ },
+ payload,
+ )
+ send(
+ {
+ "type": "result",
+ "version": 1,
+ "request_id": request_id,
+ "cancelled": "no" if mode == "tts_bad_result" else False,
+ "sample_count": 7,
+ "request": request,
+ }
+ )
+ continue
+ if request_type == "cancel" and request["request_id"] == active_request_id:
+ send(
+ {
+ "type": "result",
+ "version": 1,
+ "request_id": request["request_id"],
+ "cancelled": True,
+ "sample_count": 0,
+ }
+ )
+ active_request_id = None
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/muse_glimmer/macos/packages/livekit-plugins-executorch/tests/fake_supertonic_runner.py b/muse_glimmer/macos/packages/livekit-plugins-executorch/tests/fake_supertonic_runner.py
new file mode 100755
index 0000000000..35d5e17a3e
--- /dev/null
+++ b/muse_glimmer/macos/packages/livekit-plugins-executorch/tests/fake_supertonic_runner.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python3
+from __future__ import annotations
+
+import json
+import os
+import signal
+import struct
+import sys
+import time
+import wave
+from pathlib import Path
+
+
+def _args() -> list[str]:
+ return sys.argv[1:]
+
+
+def _send(message: dict[str, object]) -> None:
+ sys.stdout.write(json.dumps(message, separators=(",", ":")) + "\n")
+ sys.stdout.flush()
+
+
+def _write_wav(path: Path, *, channels: int = 1, rate: int = 44100, width: int = 2) -> None:
+ samples = 4410
+ if width == 2:
+ payload = struct.pack(f"<{samples * channels}h", *([1000] * samples * channels))
+ else:
+ payload = b"\x80" * samples * channels
+ with wave.open(str(path), "wb") as output:
+ output.setnchannels(channels)
+ output.setsampwidth(width)
+ output.setframerate(rate)
+ output.writeframes(payload)
+
+
+def main() -> int:
+ argv = _args()
+ capture_argv = os.getenv("FAKE_SUPERTONIC_ARGV_CAPTURE")
+ if capture_argv:
+ Path(capture_argv).write_text("\n".join(argv), encoding="utf-8")
+ if "--server_jsonl=true" not in argv:
+ print("server mode required", file=sys.stderr, flush=True)
+ return 2
+
+ mode = os.getenv("FAKE_SUPERTONIC_MODE", "success")
+ if mode == "ready_timeout":
+ time.sleep(60)
+ return 0
+ if mode == "bad_ready":
+ _send({"type": "ready", "protocol_version": 2})
+ return 3
+ if mode == "stderr_crash":
+ print("model load exploded", file=sys.stderr, flush=True)
+ return 17
+
+ _send(
+ {
+ "type": "ready",
+ "protocol_version": 1,
+ "sample_rate": 44100,
+ "load_seconds": 0.01,
+ "warmup_seconds": 0.02,
+ }
+ )
+ for line in sys.stdin:
+ request = json.loads(line)
+ capture_request = os.getenv("FAKE_SUPERTONIC_REQUEST_CAPTURE")
+ if capture_request and request.get("type") == "synthesize":
+ with Path(capture_request).open("a", encoding="utf-8") as output:
+ output.write(json.dumps(request, separators=(",", ":")) + "\n")
+ if request.get("type") == "shutdown":
+ if mode == "ignore_shutdown":
+ continue
+ _send({"type": "stopped"})
+ return 0
+ if request.get("type") != "synthesize":
+ _send({"type": "error", "id": request.get("id"), "message": "bad request"})
+ continue
+ if mode == "sleep":
+ time.sleep(60)
+ continue
+ if mode == "ignore_terminate":
+ signal.signal(signal.SIGTERM, signal.SIG_IGN)
+ time.sleep(60)
+ continue
+ if mode == "error":
+ print("voice style is invalid", file=sys.stderr, flush=True)
+ _send(
+ {
+ "type": "error",
+ "id": request["id"],
+ "message": "voice style is invalid",
+ }
+ )
+ continue
+ if mode == "wrong_id":
+ request["id"] += 1
+ output_path = Path(request["output"])
+ if mode == "malformed":
+ output_path.write_bytes(b"not a wav")
+ elif mode == "stereo":
+ _write_wav(output_path, channels=2)
+ elif mode == "wrong_rate":
+ _write_wav(output_path, rate=24000)
+ elif mode == "wrong_width":
+ _write_wav(output_path, width=1)
+ elif mode != "missing":
+ _write_wav(output_path)
+ _send(
+ {
+ "type": "result",
+ "id": request["id"],
+ "output": request["output"],
+ "samples": 4410,
+ "audio_seconds": 0.1,
+ "synthesis_seconds": 0.01,
+ "rtf": 0.1,
+ }
+ )
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/muse_glimmer/macos/packages/livekit-plugins-executorch/tests/test_helper_process.py b/muse_glimmer/macos/packages/livekit-plugins-executorch/tests/test_helper_process.py
new file mode 100644
index 0000000000..977041e6f0
--- /dev/null
+++ b/muse_glimmer/macos/packages/livekit-plugins-executorch/tests/test_helper_process.py
@@ -0,0 +1,125 @@
+from __future__ import annotations
+
+import asyncio
+import sys
+from pathlib import Path
+
+import pytest
+
+from livekit.plugins.executorch._helper_process import (
+ HelperProcess,
+ HelperProcessError,
+ HelperProtocolError,
+)
+
+pytestmark = pytest.mark.unit
+
+_FAKE_HELPER = Path(__file__).with_name("fake_helper.py")
+
+
+def helper(*, ready_timeout: float = 1.0, max_payload_bytes: int = 1024) -> HelperProcess:
+ return HelperProcess(
+ sys.executable,
+ [str(_FAKE_HELPER)],
+ name="fake",
+ ready_timeout=ready_timeout,
+ shutdown_timeout=0.05,
+ terminate_timeout=0.05,
+ max_payload_bytes=max_payload_bytes,
+ )
+
+
+async def test_ready_write_read_and_shutdown(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setenv("FAKE_HELPER_MODE", "stt")
+ process = helper()
+ ready = await process.start()
+ assert ready == {"type": "ready", "version": 1}
+
+ payload = b"\x00\x00\x00\x00"
+ await process.write_message(
+ {
+ "type": "transcribe",
+ "version": 1,
+ "request_id": "request-1",
+ "audio": {
+ "encoding": "f32le",
+ "sample_rate": 16000,
+ "channel_count": 1,
+ "payload_byte_count": len(payload),
+ },
+ },
+ payload,
+ )
+ status, status_payload = await process.read_message()
+ result, result_payload = await process.read_message()
+ assert status["type"] == "status"
+ assert status_payload is None
+ assert result["text"] == "0.000"
+ assert result_payload is None
+
+ await process.aclose()
+ assert not process.running
+
+
+async def test_startup_timeout_reports_recent_stderr(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setenv("FAKE_HELPER_MODE", "stderr_crash")
+ process = helper()
+ with pytest.raises(HelperProcessError, match="model load exploded"):
+ await process.start()
+ assert not process.running
+
+
+async def test_startup_timeout_terminates_helper(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setenv("FAKE_HELPER_MODE", "timeout")
+ process = helper(ready_timeout=0.02)
+ with pytest.raises(HelperProcessError, match="did not become ready"):
+ await process.start()
+ assert not process.running
+
+
+async def test_shutdown_escalates_for_unresponsive_helper(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setenv("FAKE_HELPER_MODE", "ignore_shutdown")
+ process = helper()
+ await process.start()
+ await asyncio.wait_for(process.aclose(), timeout=1.0)
+ assert not process.running
+
+
+async def test_malformed_header_is_rejected(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setenv("FAKE_HELPER_MODE", "malformed_ready")
+ process = helper()
+ with pytest.raises(HelperProcessError, match="malformed JSON"):
+ await process.start()
+
+
+async def test_oversized_payload_is_rejected(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setenv("FAKE_HELPER_MODE", "oversized")
+ process = helper(max_payload_bytes=32)
+ await process.start()
+ with pytest.raises(HelperProtocolError, match="payload exceeds"):
+ await process.read_message()
+ await process.aclose(graceful=False)
+
+
+async def test_unexpected_eof_includes_exit_code(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setenv("FAKE_HELPER_MODE", "eof")
+ process = helper()
+ await process.start()
+ payload = b"\x00\x00\x00\x00"
+ await process.write_message(
+ {
+ "type": "transcribe",
+ "version": 1,
+ "request_id": "request-1",
+ "audio": {
+ "encoding": "f32le",
+ "sample_rate": 16000,
+ "channel_count": 1,
+ "payload_byte_count": len(payload),
+ },
+ },
+ payload,
+ )
+ with pytest.raises(HelperProcessError, match="unexpected EOF"):
+ await asyncio.wait_for(process.read_message(), 1.0)
+ await process.aclose(graceful=False)
diff --git a/muse_glimmer/macos/packages/livekit-plugins-executorch/tests/test_stt.py b/muse_glimmer/macos/packages/livekit-plugins-executorch/tests/test_stt.py
new file mode 100644
index 0000000000..44c7a089c2
--- /dev/null
+++ b/muse_glimmer/macos/packages/livekit-plugins-executorch/tests/test_stt.py
@@ -0,0 +1,144 @@
+from __future__ import annotations
+
+import asyncio
+import struct
+import sys
+from pathlib import Path
+
+import pytest
+from livekit.agents import APIConnectOptions, APIError, stt
+
+from livekit import rtc
+from livekit.plugins.executorch import STT
+from livekit.plugins.executorch._helper_process import HelperProcess
+from livekit.plugins.executorch.stt import _audio_buffer_to_f32le
+
+pytestmark = pytest.mark.unit
+
+_FAKE_HELPER = Path(__file__).with_name("fake_helper.py")
+
+
+def provider(*, mode: str = "stt") -> STT:
+ helper = HelperProcess(
+ sys.executable,
+ [str(_FAKE_HELPER)],
+ name="fake-parakeet",
+ ready_timeout=1.0,
+ shutdown_timeout=0.05,
+ terminate_timeout=0.05,
+ )
+ return STT(
+ helper_path="unused",
+ model_path="parakeet.pte",
+ tokenizer_path="tokenizer.model",
+ _helper=helper,
+ )
+
+
+def frame(samples: tuple[int, ...], *, sample_rate: int, channels: int) -> rtc.AudioFrame:
+ return rtc.AudioFrame(
+ data=struct.pack(f"<{len(samples)}h", *samples),
+ sample_rate=sample_rate,
+ num_channels=channels,
+ samples_per_channel=len(samples) // channels,
+ )
+
+
+def test_audio_conversion_downmixes_and_scales_s16() -> None:
+ audio = frame((-32768, -32768, 16384, 16384, 32767, 32767), sample_rate=16000, channels=2)
+ converted = struct.unpack("<3f", _audio_buffer_to_f32le(audio))
+ assert converted == pytest.approx((-1.0, 0.5, 32767 / 32768))
+
+
+def test_audio_conversion_resamples_to_16khz() -> None:
+ audio = frame(tuple([1000] * 480), sample_rate=48000, channels=1)
+ converted = _audio_buffer_to_f32le(audio)
+ assert len(converted) // 4 == pytest.approx(160, abs=2)
+
+
+async def test_recognize_builds_final_transcript(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setenv("FAKE_HELPER_MODE", "stt")
+ parakeet = provider()
+ event = await parakeet.recognize(
+ frame((-32768, 0, 16384, 32767), sample_rate=16000, channels=1),
+ language="en-US",
+ conn_options=APIConnectOptions(max_retry=0, timeout=1.0),
+ )
+ assert event.type is stt.SpeechEventType.FINAL_TRANSCRIPT
+ assert event.request_id.startswith("parakeet_")
+ assert event.alternatives[0].text == "-1.000,0.000,0.500,1.000"
+ assert str(event.alternatives[0].language) == "en-US"
+ assert event.alternatives[0].metadata == {
+ "provider": "ExecuTorch",
+ "model": "parakeet.pte",
+ "runtime": "parakeet_helper",
+ }
+ await parakeet.aclose()
+
+
+async def test_malformed_result_is_non_retryable(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setenv("FAKE_HELPER_MODE", "stt_bad_result")
+ parakeet = provider()
+ with pytest.raises(APIError, match="field 'text' must be a string") as exc_info:
+ await parakeet.recognize(
+ frame((0,), sample_rate=16000, channels=1),
+ conn_options=APIConnectOptions(max_retry=0, timeout=1.0),
+ )
+ assert not exc_info.value.retryable
+ assert not parakeet._helper.running
+ await parakeet.aclose()
+
+
+async def test_helper_error_is_non_retryable(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setenv("FAKE_HELPER_MODE", "stt_error")
+ parakeet = provider()
+ with pytest.raises(APIError, match="bad audio: fake failure") as exc_info:
+ await parakeet.recognize(
+ frame((0,), sample_rate=16000, channels=1),
+ conn_options=APIConnectOptions(max_retry=0, timeout=1.0),
+ )
+ assert not exc_info.value.retryable
+ await parakeet.aclose()
+
+
+async def test_recognition_calls_are_serialized(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setenv("FAKE_HELPER_MODE", "stt")
+ parakeet = provider()
+ audio = frame((0,), sample_rate=16000, channels=1)
+ events = await asyncio.gather(
+ parakeet.recognize(audio, conn_options=APIConnectOptions(max_retry=0, timeout=1.0)),
+ parakeet.recognize(audio, conn_options=APIConnectOptions(max_retry=0, timeout=1.0)),
+ )
+ assert len({event.request_id for event in events}) == 2
+ assert all(event.alternatives[0].text == "0.000" for event in events)
+ await parakeet.aclose()
+
+
+async def test_cancellation_closes_uncancellable_helper(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ monkeypatch.setenv("FAKE_HELPER_MODE", "stt_slow")
+ parakeet = provider()
+ task = asyncio.create_task(
+ parakeet.recognize(
+ frame((0,), sample_rate=16000, channels=1),
+ conn_options=APIConnectOptions(max_retry=0, timeout=60.0),
+ )
+ )
+ await asyncio.sleep(0.05)
+ assert parakeet._helper._process is not None
+ old_pid = parakeet._helper._process.pid
+ task.cancel()
+ with pytest.raises(asyncio.CancelledError):
+ await task
+ assert not parakeet._helper.running
+
+ monkeypatch.setenv("FAKE_HELPER_MODE", "stt")
+ event = await parakeet.recognize(
+ frame((0,), sample_rate=16000, channels=1),
+ conn_options=APIConnectOptions(max_retry=0, timeout=1.0),
+ )
+ assert parakeet._helper._process is not None
+ assert parakeet._helper._process.pid != old_pid
+ assert event.alternatives[0].text == "0.000"
+ await parakeet.aclose()
diff --git a/muse_glimmer/macos/packages/livekit-plugins-executorch/tests/test_supertonic_tts.py b/muse_glimmer/macos/packages/livekit-plugins-executorch/tests/test_supertonic_tts.py
new file mode 100644
index 0000000000..35043a0395
--- /dev/null
+++ b/muse_glimmer/macos/packages/livekit-plugins-executorch/tests/test_supertonic_tts.py
@@ -0,0 +1,282 @@
+from __future__ import annotations
+
+import asyncio
+import json
+import sys
+from pathlib import Path
+
+import pytest
+from livekit.agents import APIConnectOptions, APIError, APITimeoutError
+
+from livekit.plugins.executorch import SupertonicTTS
+
+pytestmark = pytest.mark.unit
+
+_FAKE_RUNNER = Path(__file__).with_name("fake_supertonic_runner.py")
+
+
+@pytest.fixture(autouse=True)
+def executable_runner() -> None:
+ _FAKE_RUNNER.chmod(0o755)
+
+
+def provider(
+ tmp_path: Path,
+ *,
+ ready_timeout: float = 1.0,
+ shutdown_timeout: float = 0.05,
+ terminate_timeout: float = 0.05,
+) -> SupertonicTTS:
+ pte = tmp_path / "supertonic.pte"
+ voice = tmp_path / "F1.json"
+ assets = tmp_path / "assets"
+ pte.write_bytes(b"pte")
+ voice.write_text("{}", encoding="utf-8")
+ assets.mkdir(exist_ok=True)
+ return SupertonicTTS(
+ runner_path=_FAKE_RUNNER,
+ pte_path=pte,
+ asset_dir=assets,
+ voice_style_path=voice,
+ language="en",
+ speed=1.05,
+ seed=42,
+ ready_timeout=ready_timeout,
+ shutdown_timeout=shutdown_timeout,
+ terminate_timeout=terminate_timeout,
+ )
+
+
+async def collect(supertonic: SupertonicTTS, text: str, *, timeout: float = 1.0):
+ stream = supertonic.synthesize(
+ text,
+ conn_options=APIConnectOptions(max_retry=0, timeout=timeout),
+ )
+ return [event async for event in stream]
+
+
+async def test_reuses_one_server_and_sends_text_only_over_jsonl(
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
+) -> None:
+ argv_capture = tmp_path / "argv.txt"
+ request_capture = tmp_path / "requests.jsonl"
+ monkeypatch.setenv("FAKE_SUPERTONIC_ARGV_CAPTURE", str(argv_capture))
+ monkeypatch.setenv("FAKE_SUPERTONIC_REQUEST_CAPTURE", str(request_capture))
+ text = "hello; touch /tmp/should-not-run && $(false)"
+ supertonic = provider(tmp_path)
+
+ first = await collect(supertonic, text)
+ assert supertonic._process is not None
+ pid = supertonic._process.pid
+ second = await collect(supertonic, "second request")
+
+ assert first and second
+ assert supertonic._process is not None and supertonic._process.pid == pid
+ argv = argv_capture.read_text(encoding="utf-8").splitlines()
+ assert "--server_jsonl=true" in argv
+ assert all(not argument.startswith("--text") for argument in argv)
+ assert text not in "\n".join(argv)
+ requests = [json.loads(line) for line in request_capture.read_text().splitlines()]
+ assert [request["text"] for request in requests] == [text, "second request"]
+ assert [request["id"] for request in requests] == [1, 2]
+ await supertonic.aclose()
+ assert not supertonic.running
+
+
+async def test_oversized_request_does_not_poison_server(tmp_path: Path) -> None:
+ supertonic = provider(tmp_path)
+
+ with pytest.raises(APIError, match="JSONL size limit") as exc_info:
+ await collect(supertonic, "x" * (64 * 1024))
+
+ assert not exc_info.value.retryable
+ assert not supertonic._request_active
+ assert await collect(supertonic, "small request")
+ await supertonic.aclose()
+
+
+async def test_emits_44100_hz_pcm_without_wav_header(tmp_path: Path) -> None:
+ supertonic = provider(tmp_path)
+ events = await collect(supertonic, "hello")
+ payload = b"".join(event.frame.data.tobytes() for event in events)
+
+ assert payload
+ assert not payload.startswith(b"RIFF")
+ assert events[0].request_id.startswith("supertonic_")
+ assert events[0].frame.sample_rate == 44100
+ assert events[0].frame.num_channels == 1
+ assert events[-1].is_final
+ await supertonic.aclose()
+
+
+@pytest.mark.parametrize(
+ ("mode", "message"),
+ [
+ ("missing", "missing"),
+ ("malformed", "invalid audio"),
+ ("stereo", "mono"),
+ ("wrong_rate", "44100 Hz"),
+ ("wrong_width", "PCM16"),
+ ],
+)
+async def test_rejects_invalid_wav(
+ monkeypatch: pytest.MonkeyPatch,
+ tmp_path: Path,
+ mode: str,
+ message: str,
+) -> None:
+ monkeypatch.setenv("FAKE_SUPERTONIC_MODE", mode)
+ supertonic = provider(tmp_path)
+ with pytest.raises(APIError, match=message) as exc_info:
+ await collect(supertonic, "hello")
+ assert not exc_info.value.retryable
+ await supertonic.aclose()
+
+
+async def test_runner_error_includes_message(
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
+) -> None:
+ monkeypatch.setenv("FAKE_SUPERTONIC_MODE", "error")
+ supertonic = provider(tmp_path)
+ with pytest.raises(APIError, match="voice style is invalid") as exc_info:
+ await collect(supertonic, "hello")
+ assert not exc_info.value.retryable
+ await supertonic.aclose()
+
+
+async def test_ready_timeout_kills_and_reaps_process(
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
+) -> None:
+ monkeypatch.setenv("FAKE_SUPERTONIC_MODE", "ready_timeout")
+ supertonic = provider(tmp_path, ready_timeout=0.02)
+ with pytest.raises(APITimeoutError):
+ await collect(supertonic, "hello")
+ assert not supertonic.running
+ await supertonic.aclose()
+
+
+async def test_cancellation_during_startup_kills_and_reaps_process(
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
+) -> None:
+ monkeypatch.setenv("FAKE_SUPERTONIC_MODE", "ready_timeout")
+ supertonic = provider(tmp_path, ready_timeout=60.0)
+ stream = supertonic.synthesize(
+ "hello", conn_options=APIConnectOptions(max_retry=0, timeout=60.0)
+ )
+
+ await asyncio.sleep(0.05)
+ await stream.aclose()
+
+ assert not supertonic.running
+ assert supertonic._process is None
+ await supertonic.aclose()
+
+
+async def test_synthesis_timeout_kills_and_reaps_process(
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
+) -> None:
+ monkeypatch.setenv("FAKE_SUPERTONIC_MODE", "ignore_terminate")
+ supertonic = provider(tmp_path, terminate_timeout=0.01)
+ with pytest.raises(APITimeoutError):
+ await collect(supertonic, "hello", timeout=0.02)
+ assert not supertonic.running
+ await supertonic.aclose()
+
+
+async def test_stream_cancellation_terminates_process(
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
+) -> None:
+ monkeypatch.setenv("FAKE_SUPERTONIC_MODE", "sleep")
+ supertonic = provider(tmp_path)
+ stream = supertonic.synthesize(
+ "hello", conn_options=APIConnectOptions(max_retry=0, timeout=60.0)
+ )
+
+ await asyncio.sleep(0.05)
+ await stream.aclose()
+
+ assert not supertonic.running
+ await supertonic.aclose()
+
+
+async def test_aclose_terminates_active_request(
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
+) -> None:
+ monkeypatch.setenv("FAKE_SUPERTONIC_MODE", "sleep")
+ supertonic = provider(tmp_path)
+ stream = supertonic.synthesize(
+ "hello", conn_options=APIConnectOptions(max_retry=0, timeout=60.0)
+ )
+ task = asyncio.create_task(anext(stream))
+
+ await asyncio.sleep(0.05)
+ await supertonic.aclose()
+ with pytest.raises(APIError):
+ await task
+ await stream.aclose()
+ assert not supertonic.running
+
+
+async def test_synthesis_calls_are_serialized_and_share_process(
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
+) -> None:
+ request_capture = tmp_path / "requests.jsonl"
+ monkeypatch.setenv("FAKE_SUPERTONIC_REQUEST_CAPTURE", str(request_capture))
+ supertonic = provider(tmp_path)
+
+ results = await asyncio.gather(collect(supertonic, "one"), collect(supertonic, "two"))
+
+ assert all(result for result in results)
+ assert supertonic.running
+ assert len(request_capture.read_text().splitlines()) == 2
+ await supertonic.aclose()
+
+
+async def test_shutdown_escalates_for_unresponsive_server(
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
+) -> None:
+ monkeypatch.setenv("FAKE_SUPERTONIC_MODE", "ignore_shutdown")
+ supertonic = provider(tmp_path)
+ await collect(supertonic, "hello")
+ await asyncio.wait_for(supertonic.aclose(), timeout=1.0)
+ assert not supertonic.running
+
+
+async def test_protocol_mismatch_terminates_server(
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
+) -> None:
+ monkeypatch.setenv("FAKE_SUPERTONIC_MODE", "wrong_id")
+ supertonic = provider(tmp_path)
+ with pytest.raises(APIError, match="response id"):
+ await collect(supertonic, "hello")
+ assert not supertonic.running
+ await supertonic.aclose()
+
+
+def test_constructor_validates_paths_and_options(tmp_path: Path) -> None:
+ with pytest.raises(ValueError, match="runner_path"):
+ SupertonicTTS(
+ runner_path=tmp_path / "missing",
+ pte_path=tmp_path / "missing.pte",
+ asset_dir=tmp_path,
+ voice_style_path=tmp_path / "missing.json",
+ )
+
+ runner = tmp_path / "runner"
+ pte = tmp_path / "model.pte"
+ voice = tmp_path / "voice.json"
+ for path in (runner, pte, voice):
+ path.write_bytes(b"x")
+ runner.chmod(0o755)
+ with pytest.raises(ValueError, match="speed"):
+ SupertonicTTS(
+ runner_path=runner,
+ pte_path=pte,
+ asset_dir=tmp_path,
+ voice_style_path=voice,
+ speed=0.0,
+ )
+
+
+def test_runner_can_be_python_interpreter_for_fake_protocol(tmp_path: Path) -> None:
+ assert Path(sys.executable).is_file()
diff --git a/muse_glimmer/macos/pyproject.toml b/muse_glimmer/macos/pyproject.toml
new file mode 100644
index 0000000000..177de64c60
--- /dev/null
+++ b/muse_glimmer/macos/pyproject.toml
@@ -0,0 +1,41 @@
+[project]
+name = "muse-glimmer-voice-agent-workspace"
+version = "0.1.0"
+description = "Fully local Muse Glimmer voice agent for macOS Apple silicon"
+requires-python = ">=3.13,<3.14"
+license = "BSD-3-Clause"
+
+[tool.uv.workspace]
+members = [
+ "apps/token-service",
+ "apps/worker",
+ "packages/livekit-plugins-executorch",
+]
+
+[tool.uv]
+package = false
+
+[dependency-groups]
+dev = [
+ "jsonschema>=4.25,<5",
+ "pytest>=8.4,<9",
+ "ruff>=0.12,<1",
+]
+
+[tool.pytest.ini_options]
+testpaths = ["tests", "apps", "packages"]
+addopts = "--strict-markers --import-mode=importlib"
+asyncio_mode = "auto"
+asyncio_default_fixture_loop_scope = "function"
+markers = [
+ "unit: hermetic unit test",
+ "e2e: model-heavy macOS integration test",
+]
+
+[tool.ruff]
+line-length = 100
+target-version = "py313"
+exclude = [".local"]
+
+[tool.ruff.lint]
+select = ["E", "F", "I", "UP", "B", "SIM"]
diff --git a/muse_glimmer/macos/scripts/__init__.py b/muse_glimmer/macos/scripts/__init__.py
new file mode 100644
index 0000000000..c3e0ccb430
--- /dev/null
+++ b/muse_glimmer/macos/scripts/__init__.py
@@ -0,0 +1 @@
+"""Repository lifecycle and validation tools."""
diff --git a/muse_glimmer/macos/scripts/bootstrap.py b/muse_glimmer/macos/scripts/bootstrap.py
new file mode 100644
index 0000000000..6e91b04e96
--- /dev/null
+++ b/muse_glimmer/macos/scripts/bootstrap.py
@@ -0,0 +1,146 @@
+from __future__ import annotations
+
+import argparse
+import json
+import os
+import re
+import shutil
+import subprocess
+import sys
+from pathlib import Path
+
+from scripts.repository import (
+ BOOTSTRAP_INPUTS,
+ BOOTSTRAP_RECEIPT,
+ ROOT,
+ TOOLCHAIN_LOCK,
+ WEB_DIST,
+ atomic_write_json,
+ digest_json,
+ digest_paths,
+ ensure_local_directories,
+ landed_gate_commits,
+ python_environment_fingerprint,
+ read_json,
+ require_supported_platform,
+ sha256_tree,
+ validate_executorch_checkout,
+)
+
+
+def _version(command: list[str]) -> str:
+ result = subprocess.run(command, check=True, capture_output=True, text=True)
+ return (result.stdout or result.stderr).strip().splitlines()[0]
+
+
+_VERSION = re.compile(r"\d+(?:\.\d+){0,2}")
+
+
+def _version_tuple(value: str) -> tuple[int, int, int]:
+ match = _VERSION.search(value)
+ if match is None:
+ raise RuntimeError(f"could not parse tool version: {value!r}")
+ parts = tuple(int(part) for part in match.group().split("."))
+ return (parts + (0, 0, 0))[:3]
+
+
+def _require_version(name: str, actual: str, requirement: str) -> None:
+ version = _version_tuple(actual)
+ for constraint in requirement.split(","):
+ constraint = constraint.strip()
+ if constraint.startswith(">=") and version < _version_tuple(constraint[2:]):
+ raise RuntimeError(f"{name} {actual!r} does not satisfy {requirement}")
+ if constraint.startswith("<") and version >= _version_tuple(constraint[1:]):
+ raise RuntimeError(f"{name} {actual!r} does not satisfy {requirement}")
+
+
+def _require_tool(name: str) -> str:
+ executable = shutil.which(name)
+ if executable is None:
+ raise RuntimeError(f"required tool is missing: {name}")
+ return executable
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(
+ description="Prepare source dependencies for local development"
+ )
+ parser.add_argument(
+ "--skip-install", action="store_true", help="validate without installing packages"
+ )
+ args = parser.parse_args()
+
+ require_supported_platform()
+ ensure_local_directories()
+ compatibility = read_json(ROOT / "config/dependencies/compatibility.lock.json")
+ toolchain = read_json(ROOT / "config/dependencies/toolchain.lock.json")
+ tools = {
+ name: _require_tool(name)
+ for name in ("git", "uv", "node", "npm", "cmake", "livekit-server")
+ }
+ versions = {
+ "python": sys.version.split()[0],
+ **{name: _version([path, "--version"]) for name, path in tools.items()},
+ }
+ for name, requirement in toolchain["tools"].items():
+ _require_version(name, versions[name], requirement)
+
+ commit = compatibility["executorch"].get("commit")
+ if not commit:
+ raise RuntimeError(
+ "the compatibility lock is gated until one ExecuTorch commit contains "
+ "supports_cancel and supertonic_server_jsonl"
+ )
+
+ checkout = (
+ Path(os.environ.get("GLIMMER_EXECUTORCH_ROOT", ROOT / ".local/src/executorch"))
+ .expanduser()
+ .resolve()
+ )
+ validate_executorch_checkout(
+ checkout,
+ commit,
+ required_ancestors=landed_gate_commits(compatibility),
+ )
+
+ if not args.skip_install:
+ subprocess.run(
+ [tools["uv"], "sync", "--all-packages", "--all-groups", "--frozen"],
+ cwd=ROOT,
+ check=True,
+ )
+ subprocess.run([tools["npm"], "ci", "--prefix", "apps/web"], cwd=ROOT, check=True)
+ subprocess.run([tools["npm"], "run", "build", "--prefix", "apps/web"], cwd=ROOT, check=True)
+
+ if not WEB_DIST.is_dir():
+ raise RuntimeError("web application is not built; run bootstrap without --skip-install")
+ runtime_python = ROOT / ".venv/bin/python"
+ if not os.access(runtime_python, os.X_OK):
+ raise RuntimeError(
+ "Python workspace environment is missing; run bootstrap without --skip-install"
+ )
+ runtime_python_version = _version([str(runtime_python), "--version"]).removeprefix("Python ")
+ _require_version("python", runtime_python_version, toolchain["tools"]["python"])
+
+ receipt = {
+ "schema_version": 1,
+ "toolchain_lock": digest_json(TOOLCHAIN_LOCK),
+ "bootstrap_inputs": digest_paths(BOOTSTRAP_INPUTS),
+ "web_dist": sha256_tree(WEB_DIST),
+ "python_environment": python_environment_fingerprint(runtime_python),
+ "tools": {
+ "python": {"path": str(runtime_python), "version": runtime_python_version},
+ **{name: {"path": path, "version": versions[name]} for name, path in tools.items()},
+ },
+ }
+ atomic_write_json(BOOTSTRAP_RECEIPT, receipt)
+ print(json.dumps({"status": "ok", "tools": versions}, indent=2))
+ return 0
+
+
+if __name__ == "__main__":
+ try:
+ raise SystemExit(main())
+ except (OSError, RuntimeError, subprocess.CalledProcessError) as error:
+ print(f"bootstrap: {error}", file=sys.stderr)
+ raise SystemExit(1) from error
diff --git a/muse_glimmer/macos/scripts/dev_stack.py b/muse_glimmer/macos/scripts/dev_stack.py
new file mode 100644
index 0000000000..bf06806c8e
--- /dev/null
+++ b/muse_glimmer/macos/scripts/dev_stack.py
@@ -0,0 +1,583 @@
+from __future__ import annotations
+
+import argparse
+import contextlib
+import fcntl
+import hashlib
+import json
+import os
+import re
+import secrets
+import signal
+import socket
+import subprocess
+import sys
+import time
+import urllib.error
+import urllib.request
+from dataclasses import dataclass
+from pathlib import Path
+from typing import IO
+
+from scripts.repository import (
+ BOOTSTRAP_INPUTS,
+ BOOTSTRAP_RECEIPT,
+ CREDENTIAL_FILE,
+ LOG_DIR,
+ ROOT,
+ RUN_DIR,
+ TOOLCHAIN_LOCK,
+ WEB_DIST,
+ atomic_write_json,
+ digest_json,
+ digest_paths,
+ ensure_local_directories,
+ load_valid_receipt,
+ python_environment_fingerprint,
+ relative_local_path,
+ sha256_tree,
+)
+
+STATE_FILE = RUN_DIR / "stack.json"
+LOCK_FILE = RUN_DIR / "stack.lock"
+SERVICE_ORDER = ("llm", "livekit", "token", "web", "agent")
+HTTP_ENDPOINTS = {
+ "llm": "http://127.0.0.1:8000/health",
+ "livekit": "http://127.0.0.1:7880",
+ "token": "http://127.0.0.1:8787/healthz",
+ "web": "http://127.0.0.1:5173",
+}
+
+
+@dataclass(frozen=True)
+class Service:
+ name: str
+ command: list[str]
+ cwd: Path
+ environment: dict[str, str]
+
+
+def _command_digest(command: list[str]) -> str:
+ return hashlib.sha256(b"\0".join(part.encode() for part in command)).hexdigest()
+
+
+def _observed_command_digest(command: str) -> str:
+ return hashlib.sha256(command.encode()).hexdigest()
+
+
+def _process_start(pid: int) -> str | None:
+ result = subprocess.run(["ps", "-p", str(pid), "-o", "lstart="], capture_output=True, text=True)
+ value = result.stdout.strip()
+ return value or None
+
+
+def _process_command(pid: int) -> str | None:
+ result = subprocess.run(
+ ["ps", "-p", str(pid), "-o", "command="], capture_output=True, text=True
+ )
+ value = result.stdout.strip()
+ return value or None
+
+
+def _pid_alive(pid: int) -> bool:
+ try:
+ os.kill(pid, 0)
+ except (ProcessLookupError, PermissionError):
+ return False
+ return True
+
+
+def _read_state() -> dict[str, object]:
+ if not STATE_FILE.is_file():
+ return {"schema_version": 1, "services": {}}
+ with STATE_FILE.open(encoding="utf-8") as stream:
+ value = json.load(stream)
+ if not isinstance(value, dict) or not isinstance(value.get("services"), dict):
+ raise RuntimeError("managed stack state is invalid")
+ return value
+
+
+def _service_matches(record: dict[str, object]) -> bool:
+ pid = record.get("pid")
+ if not isinstance(pid, int) or not _pid_alive(pid):
+ return False
+ if _process_start(pid) != record.get("start_time"):
+ return False
+ command = _process_command(pid)
+ expected = record.get("command_marker")
+ digest = record.get("observed_command_digest")
+ return (
+ isinstance(expected, str)
+ and command is not None
+ and expected in command
+ and isinstance(digest, str)
+ and _observed_command_digest(command) == digest
+ )
+
+
+@contextlib.contextmanager
+def _lifecycle_lock() -> IO[str]:
+ ensure_local_directories()
+ stream = LOCK_FILE.open("a+", encoding="utf-8")
+ try:
+ fcntl.flock(stream.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
+ except BlockingIOError as error:
+ stream.close()
+ raise RuntimeError("another stack lifecycle operation is in progress") from error
+ try:
+ yield stream
+ finally:
+ fcntl.flock(stream.fileno(), fcntl.LOCK_UN)
+ stream.close()
+
+
+def _base_environment() -> dict[str, str]:
+ allowed = ("HOME", "LANG", "LC_ALL", "PATH", "TMPDIR", "SSL_CERT_FILE")
+ return {name: os.environ[name] for name in allowed if name in os.environ}
+
+
+def _artifact(receipt: dict[str, object], role: str) -> str:
+ artifacts = receipt.get("artifacts")
+ if not isinstance(artifacts, dict) or role not in artifacts:
+ raise RuntimeError(f"prepared artifact is missing from receipt: {role}")
+ item = artifacts[role]
+ if not isinstance(item, dict) or not isinstance(item.get("path"), str):
+ raise RuntimeError(f"prepared artifact receipt is invalid: {role}")
+ return str(relative_local_path(item["path"]))
+
+
+def _new_credentials() -> tuple[str, str]:
+ api_key = f"local_{secrets.token_hex(8)}"
+ api_secret = secrets.token_urlsafe(36)
+ temporary = CREDENTIAL_FILE.with_name(
+ f".{CREDENTIAL_FILE.name}.{os.getpid()}.{secrets.token_hex(4)}.tmp"
+ )
+ descriptor = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
+ try:
+ with os.fdopen(descriptor, "w", encoding="utf-8") as stream:
+ stream.write(f"{api_key}: {api_secret}\n")
+ stream.flush()
+ os.fsync(stream.fileno())
+ os.replace(temporary, CREDENTIAL_FILE)
+ except BaseException:
+ temporary.unlink(missing_ok=True)
+ raise
+ return api_key, api_secret
+
+
+def _validated_tools() -> dict[str, str]:
+ if not BOOTSTRAP_RECEIPT.is_file():
+ raise RuntimeError("source dependencies are not bootstrapped; run `make bootstrap`")
+ with BOOTSTRAP_RECEIPT.open(encoding="utf-8") as stream:
+ receipt = json.load(stream)
+ if not WEB_DIST.is_dir() or (
+ receipt.get("toolchain_lock") != digest_json(TOOLCHAIN_LOCK)
+ or receipt.get("bootstrap_inputs") != digest_paths(BOOTSTRAP_INPUTS)
+ or receipt.get("web_dist") != sha256_tree(WEB_DIST)
+ ):
+ raise RuntimeError("bootstrap receipt is stale; run `make bootstrap`")
+ tools = receipt.get("tools")
+ required = {"python", "node", "livekit-server"}
+ if not isinstance(tools, dict) or not required <= set(tools):
+ raise RuntimeError("bootstrap receipt is invalid; run `make bootstrap`")
+ paths: dict[str, str] = {}
+ for name in required:
+ item = tools[name]
+ if not isinstance(item, dict) or not isinstance(item.get("path"), str):
+ raise RuntimeError("bootstrap receipt is invalid; run `make bootstrap`")
+ path = item["path"]
+ if not os.access(path, os.X_OK):
+ raise RuntimeError(f"bootstrapped tool is unavailable: {name}")
+ paths[name] = path
+ if receipt.get("python_environment") != python_environment_fingerprint(Path(paths["python"])):
+ raise RuntimeError("Python environment changed; run `make bootstrap`")
+ return paths
+
+
+def _services(receipt: dict[str, object], api_key: str, api_secret: str) -> list[Service]:
+ base = _base_environment()
+ venv_bin = ROOT / ".venv/bin"
+ token_service = str(venv_bin / "muse-glimmer-token-service")
+ worker = str(venv_bin / "muse-glimmer-worker")
+ for executable in (token_service, worker):
+ if not os.access(executable, os.X_OK):
+ raise RuntimeError("Python workspace is not bootstrapped; run `make bootstrap`")
+ tools = _validated_tools()
+ python = tools["python"]
+ livekit_environment = {
+ **base,
+ "LIVEKIT_URL": "ws://127.0.0.1:7880",
+ "LIVEKIT_API_KEY": api_key,
+ "LIVEKIT_API_SECRET": api_secret,
+ }
+ worker_environment = {
+ **livekit_environment,
+ "GLIMMER_AGENT_NAME": "assistant",
+ "GLIMMER_LANGUAGE": "en",
+ "MUSE_GLIMMER_BASE_URL": "http://127.0.0.1:8000/v1",
+ "MUSE_GLIMMER_API_KEY": "local",
+ "MUSE_GLIMMER_REASONING_STRENGTH": "low",
+ "MUSE_GLIMMER_MAX_TOKENS": "128",
+ "PARAKEET_HELPER_PATH": _artifact(receipt, "parakeet_helper"),
+ "PARAKEET_MODEL_PATH": _artifact(receipt, "parakeet_model"),
+ "PARAKEET_TOKENIZER_PATH": _artifact(receipt, "parakeet_tokenizer"),
+ "SUPERTONIC_RUNNER_PATH": _artifact(receipt, "supertonic_runner"),
+ "SUPERTONIC_PTE_PATH": _artifact(receipt, "supertonic_model"),
+ "SUPERTONIC_ASSET_DIR": _artifact(receipt, "supertonic_assets"),
+ "SUPERTONIC_VOICE_STYLE_PATH": _artifact(receipt, "supertonic_voice_style"),
+ "SUPERTONIC_SPEED": "1.05",
+ "SUPERTONIC_SEED": "42",
+ }
+ return [
+ Service(
+ "llm",
+ [python, "apps/muse-glimmer-server/launch.py"],
+ ROOT,
+ base,
+ ),
+ Service(
+ "livekit",
+ [
+ tools["livekit-server"],
+ "--config",
+ str(ROOT / "config/livekit/macos-arm64.yaml"),
+ "--key-file",
+ str(CREDENTIAL_FILE),
+ ],
+ ROOT,
+ base,
+ ),
+ Service(
+ "token",
+ [token_service],
+ ROOT,
+ livekit_environment,
+ ),
+ Service(
+ "web",
+ [tools["node"], "server.mjs", "--host", "127.0.0.1", "--port", "5173"],
+ ROOT / "apps/web",
+ base,
+ ),
+ Service(
+ "agent",
+ [worker, "dev"],
+ ROOT,
+ worker_environment,
+ ),
+ ]
+
+
+def _port_available(port: int, *, udp: bool = False) -> bool:
+ sock_type = socket.SOCK_DGRAM if udp else socket.SOCK_STREAM
+ with socket.socket(socket.AF_INET, sock_type) as probe:
+ try:
+ probe.bind(("127.0.0.1", port))
+ except OSError:
+ return False
+ return True
+
+
+def _assert_ports_available() -> None:
+ for port in (8000, 7880, 8787, 5173):
+ if not _port_available(port):
+ raise RuntimeError(f"TCP port {port} is already in use")
+ if not _port_available(7882, udp=True):
+ raise RuntimeError("UDP port 7882 is already in use")
+
+
+def _process_group_alive(pgid: int) -> bool:
+ try:
+ os.killpg(pgid, 0)
+ return True
+ except ProcessLookupError:
+ return False
+ except PermissionError:
+ return True
+
+
+def _wait_for_process_group(pgid: int, timeout: float) -> bool:
+ deadline = time.monotonic() + timeout
+ while time.monotonic() < deadline:
+ if not _process_group_alive(pgid):
+ return True
+ time.sleep(0.05)
+ return not _process_group_alive(pgid)
+
+
+def _terminate_new_process_group(process: subprocess.Popen[bytes]) -> None:
+ pgid = process.pid
+ with contextlib.suppress(ProcessLookupError):
+ os.killpg(pgid, signal.SIGTERM)
+ if not _wait_for_process_group(pgid, 5):
+ with contextlib.suppress(ProcessLookupError):
+ os.killpg(pgid, signal.SIGKILL)
+ if not _wait_for_process_group(pgid, 5):
+ raise RuntimeError(f"process group {pgid} survived SIGKILL")
+ with contextlib.suppress(subprocess.TimeoutExpired):
+ process.wait(timeout=1)
+
+
+def _start_service(service: Service) -> tuple[subprocess.Popen[bytes], dict[str, object]]:
+ log_path = LOG_DIR / f"{service.name}.log"
+ log_stream = log_path.open("wb")
+ try:
+ process = subprocess.Popen(
+ service.command,
+ cwd=service.cwd,
+ env=service.environment,
+ stdin=subprocess.DEVNULL,
+ stdout=log_stream,
+ stderr=subprocess.STDOUT,
+ start_new_session=True,
+ )
+ finally:
+ log_stream.close()
+ time.sleep(0.2)
+ if process.poll() is not None:
+ _terminate_new_process_group(process)
+ raise RuntimeError(f"{service.name} exited during startup; see {log_path}")
+ start_time = _process_start(process.pid)
+ command = _process_command(process.pid)
+ if start_time is None or command is None:
+ _terminate_new_process_group(process)
+ raise RuntimeError(f"could not establish process identity for {service.name}")
+ marker = Path(service.command[0]).name
+ return process, {
+ "pid": process.pid,
+ "pgid": os.getpgid(process.pid),
+ "start_time": start_time,
+ "command_marker": marker,
+ "command_digest": _command_digest(service.command),
+ "observed_command_digest": _observed_command_digest(command),
+ "log": str(log_path.relative_to(ROOT)),
+ }
+
+
+def _http_ready(url: str) -> bool:
+ try:
+ with urllib.request.urlopen(url, timeout=2) as response:
+ return response.status < 500
+ except urllib.error.HTTPError as error:
+ return error.code < 500
+ except (OSError, urllib.error.URLError):
+ return False
+
+
+def _wait_for_http(name: str, record: dict[str, object], timeout: float = 120) -> None:
+ deadline = time.monotonic() + timeout
+ while time.monotonic() < deadline:
+ if _http_ready(HTTP_ENDPOINTS[name]):
+ return
+ if not _service_matches(record):
+ raise RuntimeError(f"{name} exited before readiness")
+ time.sleep(0.5)
+ raise RuntimeError(f"{name} timed out waiting for readiness")
+
+
+def _wait_for_agent(record: dict[str, object], timeout: float = 60) -> str:
+ log_path = ROOT / str(record["log"])
+ registration = re.compile(r'registered worker.*"agent_name"\s*:\s*"assistant"')
+ health_endpoint = re.compile(r"HTTP server listening on 127\.0\.0\.1:(\d+)")
+ deadline = time.monotonic() + timeout
+ while time.monotonic() < deadline:
+ text = log_path.read_text(encoding="utf-8", errors="replace") if log_path.exists() else ""
+ match = health_endpoint.search(text)
+ if registration.search(text) and match is not None:
+ url = f"http://127.0.0.1:{match.group(1)}/"
+ if _http_ready(url):
+ return url
+ if not _service_matches(record):
+ raise RuntimeError("agent exited before registration")
+ time.sleep(0.5)
+ raise RuntimeError("agent timed out waiting for assistant registration")
+
+
+def _record_group_owned(record: dict[str, object]) -> bool:
+ pgid = record.get("pgid")
+ if not isinstance(pgid, int) or not _process_group_alive(pgid):
+ return False
+ if _service_matches(record):
+ return True
+ # A surviving child keeps the original PGID after its leader exits. If a
+ # process now occupies the leader PID, the PGID may have been recycled.
+ return not _pid_alive(pgid)
+
+
+def _signal_service(record: dict[str, object], signum: int) -> None:
+ pgid = record.get("pgid")
+ if not isinstance(pgid, int) or not _record_group_owned(record):
+ return
+ with contextlib.suppress(ProcessLookupError):
+ os.killpg(pgid, signum)
+
+
+def _stop_records(records: dict[str, object]) -> None:
+ for name in reversed(SERVICE_ORDER):
+ record = records.get(name)
+ if isinstance(record, dict):
+ _signal_service(record, signal.SIGTERM)
+ deadline = time.monotonic() + 10
+ while time.monotonic() < deadline:
+ active = [
+ record
+ for record in records.values()
+ if isinstance(record, dict) and _record_group_owned(record)
+ ]
+ if not active:
+ return
+ time.sleep(0.25)
+ for name in reversed(SERVICE_ORDER):
+ record = records.get(name)
+ if isinstance(record, dict):
+ _signal_service(record, signal.SIGKILL)
+ deadline = time.monotonic() + 5
+ while True:
+ if not any(
+ isinstance(record, dict) and _record_group_owned(record) for record in records.values()
+ ):
+ return
+ if time.monotonic() >= deadline:
+ break
+ time.sleep(0.1)
+ raise RuntimeError("managed process group survived SIGKILL; state was retained")
+
+
+def _write_state(records: dict[str, object]) -> None:
+ atomic_write_json(
+ STATE_FILE,
+ {"schema_version": 1, "updated_at": time.time(), "services": records},
+ )
+
+
+def _run_probe(command: list[str], *, timeout: float = 180) -> None:
+ subprocess.run(command, cwd=ROOT, check=True, timeout=timeout)
+
+
+def _up_locked() -> None:
+ state = _read_state()
+ existing = state["services"]
+ if any(
+ isinstance(record, dict) and _record_group_owned(record) for record in existing.values()
+ ):
+ raise RuntimeError(
+ "a managed stack or orphaned process group is still running; use `make down`"
+ )
+ _assert_ports_available()
+ receipt = load_valid_receipt()
+ api_key, api_secret = _new_credentials()
+ records: dict[str, object] = {}
+ interrupted = False
+
+ def handle_signal(_signum: int, _frame: object) -> None:
+ nonlocal interrupted
+ interrupted = True
+ raise KeyboardInterrupt
+
+ previous = {
+ signum: signal.signal(signum, handle_signal) for signum in (signal.SIGINT, signal.SIGTERM)
+ }
+ try:
+ for service in _services(receipt, api_key, api_secret):
+ _, record = _start_service(service)
+ records[service.name] = record
+ _write_state(records)
+ if service.name == "llm":
+ _wait_for_http(service.name, record, 360)
+ _run_probe([sys.executable, "-m", "scripts.llm_readiness"])
+ elif service.name == "agent":
+ record["health_url"] = _wait_for_agent(record)
+ _write_state(records)
+ elif service.name in HTTP_ENDPOINTS:
+ _wait_for_http(service.name, record, 120)
+ _run_probe([sys.executable, "-m", "scripts.privacy_audit"])
+ except BaseException:
+ _stop_records(records)
+ STATE_FILE.unlink(missing_ok=True)
+ CREDENTIAL_FILE.unlink(missing_ok=True)
+ if interrupted:
+ print("startup interrupted; rolled back managed services", file=sys.stderr)
+ raise
+ finally:
+ for signum, handler in previous.items():
+ signal.signal(signum, handler)
+
+
+def _down_locked() -> None:
+ state = _read_state()
+ records = state["services"]
+ _stop_records(records)
+ STATE_FILE.unlink(missing_ok=True)
+ CREDENTIAL_FILE.unlink(missing_ok=True)
+
+
+def up() -> int:
+ with _lifecycle_lock():
+ _up_locked()
+ print("Muse Glimmer is ready at http://127.0.0.1:5173")
+ return 0
+
+
+def down() -> int:
+ with _lifecycle_lock():
+ _down_locked()
+ print("Muse Glimmer stack is stopped.")
+ return 0
+
+
+def status() -> int:
+ state = _read_state()
+ records = state["services"]
+ healthy = True
+ for name in SERVICE_ORDER:
+ record = records.get(name)
+ process_ok = isinstance(record, dict) and _service_matches(record)
+ if name in HTTP_ENDPOINTS:
+ endpoint_ok = _http_ready(HTTP_ENDPOINTS[name])
+ elif name == "agent" and isinstance(record, dict):
+ health_url = record.get("health_url")
+ endpoint_ok = isinstance(health_url, str) and _http_ready(health_url)
+ else:
+ endpoint_ok = False
+ service_ok = process_ok and endpoint_ok
+ healthy = healthy and service_ok
+ print(f"{name:<8} {'healthy' if service_ok else 'unavailable'}")
+ return 0 if healthy else 1
+
+
+def logs() -> int:
+ paths = [LOG_DIR / f"{name}.log" for name in SERVICE_ORDER]
+ existing = [path for path in paths if path.exists()]
+ if not existing:
+ raise RuntimeError("no managed logs exist")
+ return subprocess.run(["tail", "-n", "80", "-F", *map(str, existing)]).returncode
+
+
+def restart() -> int:
+ with _lifecycle_lock():
+ _down_locked()
+ _up_locked()
+ print("Muse Glimmer is ready at http://127.0.0.1:5173")
+ return 0
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(description="Manage the local Muse Glimmer voice stack")
+ parser.add_argument("operation", choices=("up", "down", "restart", "status", "logs"))
+ operation = parser.parse_args().operation
+ return {"up": up, "down": down, "restart": restart, "status": status, "logs": logs}[operation]()
+
+
+if __name__ == "__main__":
+ try:
+ raise SystemExit(main())
+ except KeyboardInterrupt:
+ raise SystemExit(130) from None
+ except (
+ OSError,
+ RuntimeError,
+ subprocess.CalledProcessError,
+ subprocess.TimeoutExpired,
+ ) as error:
+ print(f"dev-stack: {error}", file=sys.stderr)
+ raise SystemExit(1) from error
diff --git a/muse_glimmer/macos/scripts/llm_readiness.py b/muse_glimmer/macos/scripts/llm_readiness.py
new file mode 100644
index 0000000000..764c5bd5d1
--- /dev/null
+++ b/muse_glimmer/macos/scripts/llm_readiness.py
@@ -0,0 +1,81 @@
+from __future__ import annotations
+
+import argparse
+import http.client
+import json
+import time
+import urllib.error
+import urllib.request
+
+_BASE_URL = "http://127.0.0.1:8000"
+_MODEL_ID = "muse-glimmer-k-quant-17G-128K-text-dflash-metal"
+
+
+def _body(*, stream: bool, max_tokens: int) -> bytes:
+ return json.dumps(
+ {
+ "model": _MODEL_ID,
+ "messages": [{"role": "user", "content": "Reply with the word ready."}],
+ "stream": stream,
+ "max_tokens": max_tokens,
+ "temperature": 0,
+ "chat_template_kwargs": {"reasoning_strength": "low"},
+ }
+ ).encode()
+
+
+def _generation() -> None:
+ request = urllib.request.Request(
+ f"{_BASE_URL}/v1/chat/completions",
+ data=_body(stream=False, max_tokens=2),
+ headers={"Content-Type": "application/json", "Authorization": "Bearer local"},
+ method="POST",
+ )
+ with urllib.request.urlopen(request, timeout=90) as response:
+ payload = json.load(response)
+ if not payload.get("choices"):
+ raise RuntimeError("LLM readiness generation returned no choices")
+
+
+def _disconnect_stream() -> None:
+ connection = http.client.HTTPConnection("127.0.0.1", 8000, timeout=90)
+ connection.request(
+ "POST",
+ "/v1/chat/completions",
+ body=_body(stream=True, max_tokens=128),
+ headers={"Content-Type": "application/json", "Authorization": "Bearer local"},
+ )
+ response = connection.getresponse()
+ if response.status != 200:
+ raise RuntimeError(f"LLM cancellation probe returned HTTP {response.status}")
+ deadline = time.monotonic() + 90
+ observed_content = False
+ while time.monotonic() < deadline:
+ line = response.readline()
+ if not line:
+ break
+ if line.startswith(b"data:") and b"choices" in line:
+ observed_content = True
+ break
+ connection.close()
+ if not observed_content:
+ raise RuntimeError("LLM cancellation probe received no streamed output")
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(description="Verify generation, cancellation, and reuse")
+ parser.add_argument("--settle-seconds", type=float, default=1.0)
+ args = parser.parse_args()
+ _generation()
+ _disconnect_stream()
+ time.sleep(args.settle_seconds)
+ _generation()
+ print("MuseGlimmer generation, cancellation, and reuse probe passed.")
+ return 0
+
+
+if __name__ == "__main__":
+ try:
+ raise SystemExit(main())
+ except (OSError, RuntimeError, urllib.error.URLError) as error:
+ raise SystemExit(f"llm-readiness: {error}") from error
diff --git a/muse_glimmer/macos/scripts/prepare_artifacts.py b/muse_glimmer/macos/scripts/prepare_artifacts.py
new file mode 100644
index 0000000000..9701468947
--- /dev/null
+++ b/muse_glimmer/macos/scripts/prepare_artifacts.py
@@ -0,0 +1,100 @@
+from __future__ import annotations
+
+import json
+import os
+import subprocess
+import sys
+from datetime import UTC, datetime
+from pathlib import Path
+
+from scripts.repository import (
+ ARTIFACT_LOCK,
+ COMPATIBILITY_LOCK,
+ PREPARED_RECEIPT,
+ ROOT,
+ TOOLCHAIN_LOCK,
+ atomic_write_json,
+ digest_json,
+ ensure_local_directories,
+ landed_gate_commits,
+ read_json,
+ relative_local_path,
+ require_supported_platform,
+ sha256_file,
+ sha256_tree,
+ validate_executorch_checkout,
+)
+
+
+def main() -> int:
+ require_supported_platform()
+ ensure_local_directories()
+ compatibility = read_json(COMPATIBILITY_LOCK)
+ expected_commit = compatibility["executorch"].get("commit")
+ if not expected_commit:
+ raise RuntimeError(
+ "artifact preparation is gated: set one immutable ExecuTorch commit "
+ "containing supports_cancel and supertonic_server_jsonl"
+ )
+
+ checkout = (
+ Path(os.environ.get("GLIMMER_EXECUTORCH_ROOT", ROOT / ".local/src/executorch"))
+ .expanduser()
+ .resolve()
+ )
+ validate_executorch_checkout(
+ checkout,
+ expected_commit,
+ required_ancestors=landed_gate_commits(compatibility),
+ )
+
+ manifest = read_json(ARTIFACT_LOCK)
+ inventory: dict[str, dict[str, object]] = {}
+ missing: list[str] = []
+ for item in manifest["artifacts"]:
+ path = relative_local_path(item["destination"])
+ if not path.exists():
+ missing.append(f"{item['role']}: {item['destination']} ({item['prepare']})")
+ continue
+ if item["kind"] == "file" and not path.is_file():
+ raise RuntimeError(f"{item['role']} must be a regular file")
+ if item["kind"] == "directory" and not path.is_dir():
+ raise RuntimeError(f"{item['role']} must be a directory")
+ if item["executable"] and not os.access(path, os.X_OK):
+ raise RuntimeError(f"{item['role']} must be executable")
+ checksum = sha256_tree(path) if path.is_dir() else sha256_file(path)
+ expected_checksum = item.get("sha256")
+ if expected_checksum and checksum != expected_checksum:
+ raise RuntimeError(f"checksum mismatch for {item['role']}")
+ inventory[item["role"]] = {
+ "path": item["destination"],
+ "sha256": checksum,
+ "size_bytes": path.stat().st_size if path.is_file() else None,
+ }
+ if missing:
+ details = "\n ".join(missing)
+ raise RuntimeError(f"required artifacts are missing:\n {details}")
+
+ receipt = {
+ "schema_version": 1,
+ "prepared_at": datetime.now(UTC).isoformat(),
+ "executorch_commit": expected_commit,
+ "executorch_checkout": str(checkout),
+ "locks": {
+ "compatibility": digest_json(COMPATIBILITY_LOCK),
+ "artifacts": digest_json(ARTIFACT_LOCK),
+ "toolchain": digest_json(TOOLCHAIN_LOCK),
+ },
+ "artifacts": inventory,
+ }
+ atomic_write_json(PREPARED_RECEIPT, receipt)
+ print(json.dumps({"status": "prepared", "receipt": str(PREPARED_RECEIPT)}, indent=2))
+ return 0
+
+
+if __name__ == "__main__":
+ try:
+ raise SystemExit(main())
+ except (OSError, RuntimeError, subprocess.CalledProcessError) as error:
+ print(f"prepare-artifacts: {error}", file=sys.stderr)
+ raise SystemExit(1) from error
diff --git a/muse_glimmer/macos/scripts/privacy_audit.py b/muse_glimmer/macos/scripts/privacy_audit.py
new file mode 100644
index 0000000000..08795baef0
--- /dev/null
+++ b/muse_glimmer/macos/scripts/privacy_audit.py
@@ -0,0 +1,134 @@
+from __future__ import annotations
+
+import json
+import os
+import re
+import socket
+import stat
+import subprocess
+import sys
+import urllib.request
+
+from scripts.repository import CREDENTIAL_FILE, ROOT
+
+_TCP_PORTS = (8000, 7880, 8787, 5173)
+_UDP_PORTS = (7882,)
+_FORBIDDEN_BROWSER_PATTERNS = {
+ "LLM port": re.compile(r"127\.0\.0\.1:8000"),
+ "model variant": re.compile(r"muse-glimmer-k-quant|17G|128K|dflash", re.IGNORECASE),
+ "model runtime": re.compile(r"Parakeet|Supertonic|MUSE_GLIMMER_|PARAKEET_|SUPERTONIC_"),
+ "private path": re.compile(r"/" + r"Users/|\.local/artifacts|\.pte\b"),
+ "credential name": re.compile(r"LIVEKIT_API_SECRET"),
+ "cloud URL": re.compile(r"wss://|https://[^\s\"']*livekit", re.IGNORECASE),
+}
+
+
+def _socket_rows(kind: str, port: int) -> list[str]:
+ command = ["lsof", "-nP", f"-i{kind}:{port}"]
+ if kind == "TCP":
+ command.append("-sTCP:LISTEN")
+ result = subprocess.run(command, capture_output=True, text=True)
+ return result.stdout.splitlines()[1:]
+
+
+def _assert_loopback_sockets() -> None:
+ for kind, ports in (("TCP", _TCP_PORTS), ("UDP", _UDP_PORTS)):
+ for port in ports:
+ rows = _socket_rows(kind, port)
+ if not rows:
+ raise RuntimeError(f"no {kind} listener found on required port {port}")
+ for row in rows:
+ if "127.0.0.1" not in row and "[::1]" not in row:
+ raise RuntimeError(f"{kind} port {port} is exposed beyond loopback: {row}")
+
+
+def _assert_credentials() -> None:
+ if not CREDENTIAL_FILE.is_file():
+ raise RuntimeError("runtime LiveKit credentials are missing")
+ mode = stat.S_IMODE(CREDENTIAL_FILE.stat().st_mode)
+ if mode != 0o600:
+ raise RuntimeError("runtime LiveKit credentials must have mode 0600")
+
+
+def _assert_token_boundary() -> None:
+ request = urllib.request.Request("http://127.0.0.1:8787/api/token", method="POST")
+ with urllib.request.urlopen(request, timeout=5) as response:
+ payload = json.load(response)
+ if response.headers.get("Cache-Control") != "no-store":
+ raise RuntimeError("token response is cacheable")
+ if not isinstance(payload, dict):
+ raise RuntimeError("token response is not an object")
+ expected = {"serverUrl", "participantToken", "roomName", "participantIdentity"}
+ if set(payload) != expected:
+ raise RuntimeError("token response exposes unapproved fields")
+ if payload["serverUrl"] != "ws://127.0.0.1:7880":
+ raise RuntimeError("token response points beyond local LiveKit")
+
+
+def _assert_browser_bundle() -> None:
+ bundle = ROOT / "apps/web/dist"
+ if not bundle.is_dir():
+ raise RuntimeError("production web bundle is missing; run `npm run build`")
+ for path in bundle.rglob("*"):
+ if not path.is_file():
+ continue
+ text = path.read_text(encoding="utf-8", errors="ignore")
+ for label, pattern in _FORBIDDEN_BROWSER_PATTERNS.items():
+ if pattern.search(text):
+ raise RuntimeError(f"browser bundle contains forbidden {label}: {path.name}")
+
+
+def _assert_no_external_connections() -> None:
+ result = subprocess.run(
+ ["lsof", "-nP", "-iTCP", "-sTCP:ESTABLISHED"], capture_output=True, text=True
+ )
+ managed_pgids: set[int] = set()
+ state_path = ROOT / ".local/run/stack.json"
+ if state_path.is_file():
+ state = json.loads(state_path.read_text(encoding="utf-8"))
+ managed_pgids = {
+ record["pgid"]
+ for record in state.get("services", {}).values()
+ if isinstance(record, dict) and isinstance(record.get("pgid"), int)
+ }
+ for row in result.stdout.splitlines()[1:]:
+ columns = row.split()
+ if len(columns) < 9:
+ continue
+ try:
+ process_group = os.getpgid(int(columns[1]))
+ except (ProcessLookupError, ValueError):
+ continue
+ if process_group not in managed_pgids:
+ continue
+ endpoint = columns[-1]
+ remote = endpoint.rsplit("->", 1)[-1]
+ host = remote.rsplit(":", 1)[0].strip("[]")
+ try:
+ address = socket.gethostbyname(host)
+ except OSError:
+ raise RuntimeError(
+ f"managed process has an unresolved connection: {endpoint}"
+ ) from None
+ if not address.startswith("127."):
+ raise RuntimeError(f"managed process has a non-loopback connection: {endpoint}")
+
+
+def main() -> int:
+ if sys.platform != "darwin":
+ raise RuntimeError("privacy audit currently supports macOS only")
+ _assert_credentials()
+ _assert_loopback_sockets()
+ _assert_token_boundary()
+ _assert_browser_bundle()
+ _assert_no_external_connections()
+ print("Local privacy audit passed.")
+ return 0
+
+
+if __name__ == "__main__":
+ try:
+ raise SystemExit(main())
+ except (OSError, RuntimeError) as error:
+ print(f"privacy-audit: {error}", file=sys.stderr)
+ raise SystemExit(1) from error
diff --git a/muse_glimmer/macos/scripts/publication_check.py b/muse_glimmer/macos/scripts/publication_check.py
new file mode 100644
index 0000000000..a919d387ce
--- /dev/null
+++ b/muse_glimmer/macos/scripts/publication_check.py
@@ -0,0 +1,156 @@
+from __future__ import annotations
+
+import argparse
+import os
+import re
+import subprocess
+import sys
+from pathlib import Path
+
+from scripts.repository import ROOT
+
+_DENIED_SUFFIXES = {
+ ".a",
+ ".bin",
+ ".dylib",
+ ".gguf",
+ ".metallib",
+ ".onnx",
+ ".o",
+ ".pcm",
+ ".pem",
+ ".pt",
+ ".ptd",
+ ".pte",
+ ".safetensors",
+ ".so",
+ ".wav",
+}
+_DENIED_PARTS = {
+ ".dev-stack",
+ ".local",
+ ".venv",
+ "__pycache__",
+ "dist",
+ "node_modules",
+ "recordings",
+ "reports",
+ "museglimmer-reports",
+}
+_DENIED_NAMES = {".env", ".env.cloud.disabled", "livekit.keys"}
+_DENIED_CONTENT = {
+ "absolute user path": re.compile(b"/" + b"Users" + b"/"),
+ "internal URL": re.compile((b"internal" + b"fb\\.com|fburl\\.com"), re.IGNORECASE),
+ "AGPL avatar package": re.compile((b"@bible-strong/" + b"avatar"), re.IGNORECASE),
+ "private key": re.compile(b"BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY"),
+}
+_MAX_FILE_BYTES = 5 * 1024 * 1024
+
+
+def _git_files(*arguments: str) -> list[Path]:
+ result = subprocess.run(
+ ["git", "ls-files", *arguments, "-z", "--", "."],
+ cwd=ROOT,
+ check=True,
+ capture_output=True,
+ )
+ root = ROOT.resolve()
+ files: list[Path] = []
+ for value in result.stdout.split(b"\0"):
+ if not value:
+ continue
+ relative = Path(os.fsdecode(value))
+ if relative.is_absolute() or ".." in relative.parts:
+ raise RuntimeError(f"Git candidate escapes application root: {relative}")
+ files.append(root / relative)
+ return files
+
+
+def _candidate_files() -> list[Path]:
+ return _git_files("--cached", "--others", "--exclude-standard")
+
+
+def _check_path(path: Path) -> list[str]:
+ relative = path.relative_to(ROOT)
+ errors: list[str] = []
+ if path.is_symlink():
+ errors.append(f"symlink is not allowed: {relative}")
+ return errors
+ if any(part in _DENIED_PARTS for part in relative.parts):
+ errors.append(f"generated/private path is not allowed: {relative}")
+ if path.name in _DENIED_NAMES or (path.name.startswith(".env") and path.name != ".env.example"):
+ errors.append(f"environment or credential file is not allowed: {relative}")
+ if path.suffix.lower() in _DENIED_SUFFIXES:
+ errors.append(f"binary/model artifact is not allowed: {relative}")
+ if not path.is_file():
+ return errors
+ size = path.stat().st_size
+ if size > _MAX_FILE_BYTES:
+ errors.append(f"file exceeds {_MAX_FILE_BYTES} bytes: {relative}")
+ return errors
+ payload = path.read_bytes()
+ for label, pattern in _DENIED_CONTENT.items():
+ if pattern.search(payload):
+ errors.append(f"{label} found in {relative}")
+ assignment = re.compile(
+ rb"^(?:export\s+)?(?:LIVEKIT_API_SECRET|OPENAI_API_KEY|AWS_SECRET_ACCESS_KEY)\s*=\s*(.+)$"
+ )
+ for raw_line in payload.splitlines():
+ match = assignment.fullmatch(raw_line.rstrip())
+ if match is None:
+ continue
+ value = match.group(1).strip().strip(b"\"'")
+ if value and not value.startswith((b"test-", b"<", b"${")):
+ errors.append(f"credential assignment found in {relative}")
+ break
+ return errors
+
+
+def _nested_repositories() -> list[Path]:
+ nested = []
+ for current, directories, files in os.walk(ROOT):
+ path = Path(current)
+ if path == ROOT:
+ directories[:] = [name for name in directories if name not in {".git", ".local"}]
+ continue
+ if ".git" in directories:
+ nested.append(path / ".git")
+ directories.remove(".git")
+ if ".git" in files:
+ nested.append(path / ".git")
+ directories[:] = [name for name in directories if name != ".local"]
+ return nested
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(description="Reject files unsafe for public source control")
+ parser.add_argument("--tracked-only", action="store_true")
+ args = parser.parse_args()
+ files = _candidate_files()
+ if args.tracked_only:
+ allowed = set(_git_files("--cached"))
+ if not allowed:
+ print(
+ "publication-check: application subtree contains no tracked files", file=sys.stderr
+ )
+ return 1
+ files = [path for path in files if path in allowed]
+ errors = [error for path in files for error in _check_path(path)]
+ errors.extend(
+ f"nested repository is not allowed: {path.relative_to(ROOT)}"
+ for path in _nested_repositories()
+ )
+ if errors:
+ for error in errors:
+ print(f"publication-check: {error}", file=sys.stderr)
+ return 1
+ print(f"Publication check passed for {len(files)} files.")
+ return 0
+
+
+if __name__ == "__main__":
+ try:
+ raise SystemExit(main())
+ except (OSError, RuntimeError, subprocess.CalledProcessError) as error:
+ print(f"publication-check: {error}", file=sys.stderr)
+ raise SystemExit(1) from error
diff --git a/muse_glimmer/macos/scripts/repository.py b/muse_glimmer/macos/scripts/repository.py
new file mode 100644
index 0000000000..02ce2f1bf8
--- /dev/null
+++ b/muse_glimmer/macos/scripts/repository.py
@@ -0,0 +1,273 @@
+from __future__ import annotations
+
+import hashlib
+import importlib.metadata
+import json
+import os
+import platform
+import secrets
+import subprocess
+from collections.abc import Iterable
+from pathlib import Path
+from typing import Any
+
+ROOT = Path(__file__).resolve().parents[1]
+LOCAL = ROOT / ".local"
+RUN_DIR = LOCAL / "run"
+LOG_DIR = LOCAL / "logs"
+STATE_DIR = LOCAL / "state"
+ARTIFACT_DIR = LOCAL / "artifacts"
+SOURCE_DIR = LOCAL / "src"
+PREPARED_RECEIPT = STATE_DIR / "prepared.json"
+BOOTSTRAP_RECEIPT = STATE_DIR / "bootstrap.json"
+CREDENTIAL_FILE = RUN_DIR / "livekit.keys"
+COMPATIBILITY_LOCK = ROOT / "config" / "dependencies" / "compatibility.lock.json"
+ARTIFACT_LOCK = ROOT / "artifacts" / "macos-arm64.lock.json"
+TOOLCHAIN_LOCK = ROOT / "config" / "dependencies" / "toolchain.lock.json"
+BOOTSTRAP_INPUTS = (
+ ROOT / "pyproject.toml",
+ ROOT / "uv.lock",
+ ROOT / "apps/token-service/pyproject.toml",
+ ROOT / "apps/worker/pyproject.toml",
+ ROOT / "packages/livekit-plugins-executorch/pyproject.toml",
+ ROOT / "apps/web/package.json",
+ ROOT / "apps/web/package-lock.json",
+ ROOT / "apps/web/index.html",
+ ROOT / "apps/web/server.mjs",
+ ROOT / "apps/web/src",
+ ROOT / "apps/web/tsconfig.app.json",
+ ROOT / "apps/web/tsconfig.json",
+ ROOT / "apps/web/tsconfig.node.json",
+ ROOT / "apps/web/vite.config.ts",
+)
+WEB_DIST = ROOT / "apps/web/dist"
+
+
+def read_json(path: Path) -> dict[str, Any]:
+ with path.open(encoding="utf-8") as stream:
+ value = json.load(stream)
+ if not isinstance(value, dict):
+ raise ValueError(f"expected a JSON object: {path}")
+ return value
+
+
+def sha256_file(path: Path) -> str:
+ digest = hashlib.sha256()
+ with path.open("rb") as stream:
+ for chunk in iter(lambda: stream.read(1024 * 1024), b""):
+ digest.update(chunk)
+ return digest.hexdigest()
+
+
+def sha256_tree(path: Path) -> str:
+ digest = hashlib.sha256()
+ for item in sorted(candidate for candidate in path.rglob("*") if candidate.is_file()):
+ digest.update(item.relative_to(path).as_posix().encode())
+ digest.update(b"\0")
+ digest.update(sha256_file(item).encode())
+ digest.update(b"\0")
+ return digest.hexdigest()
+
+
+def digest_json(path: Path) -> str:
+ return sha256_file(path)
+
+
+def digest_paths(paths: tuple[Path, ...]) -> str:
+ digest = hashlib.sha256()
+ for path in paths:
+ if not path.exists():
+ raise RuntimeError(f"bootstrap input is missing: {path.relative_to(ROOT)}")
+ digest.update(path.relative_to(ROOT).as_posix().encode())
+ digest.update(b"\0")
+ digest.update((sha256_tree(path) if path.is_dir() else sha256_file(path)).encode())
+ digest.update(b"\0")
+ return digest.hexdigest()
+
+
+def installed_environment_fingerprint(
+ distributions: Iterable[Any] | None = None,
+) -> str:
+ digest = hashlib.sha256()
+ installed = distributions if distributions is not None else importlib.metadata.distributions()
+ ordered = sorted(
+ installed,
+ key=lambda distribution: (
+ str(distribution.metadata.get("Name", "")).lower(),
+ distribution.version,
+ ),
+ )
+ for distribution in ordered:
+ name = str(distribution.metadata.get("Name", "")).lower()
+ digest.update(name.encode())
+ digest.update(b"\0")
+ digest.update(distribution.version.encode())
+ digest.update(b"\0")
+ for relative in sorted(distribution.files or (), key=str):
+ relative_text = str(relative)
+ if relative_text.endswith(".pyc") or "__pycache__" in Path(relative_text).parts:
+ continue
+ digest.update(relative_text.encode())
+ digest.update(b"\0")
+ path = Path(distribution.locate_file(relative))
+ if path.is_file():
+ stat = path.stat()
+ digest.update(f"{stat.st_size}:{stat.st_mtime_ns}".encode())
+ if path.name in {"RECORD", "direct_url.json"}:
+ digest.update(sha256_file(path).encode())
+ else:
+ digest.update(b"missing")
+ digest.update(b"\0")
+ return digest.hexdigest()
+
+
+def python_environment_fingerprint(python: Path) -> str:
+ script = (
+ "from scripts.repository import installed_environment_fingerprint; "
+ "print(installed_environment_fingerprint())"
+ )
+ return subprocess.run(
+ [str(python), "-c", script],
+ cwd=ROOT,
+ check=True,
+ capture_output=True,
+ text=True,
+ ).stdout.strip()
+
+
+def landed_gate_commits(compatibility: dict[str, Any]) -> tuple[str, ...]:
+ executorch = compatibility.get("executorch")
+ if not isinstance(executorch, dict):
+ raise RuntimeError("compatibility lock has no ExecuTorch configuration")
+ gates = executorch.get("gates")
+ if not isinstance(gates, dict):
+ raise RuntimeError("compatibility lock has no upstream gates")
+ commits: list[str] = []
+ for name, gate in gates.items():
+ if not isinstance(gate, dict):
+ raise RuntimeError(f"compatibility gate must be an object: {name}")
+ if gate.get("status") != "landed":
+ continue
+ commit = gate.get("commit")
+ if not isinstance(commit, str) or len(commit) != 40:
+ raise RuntimeError(f"landed compatibility gate requires a commit: {name}")
+ commits.append(commit)
+ return tuple(sorted(commits))
+
+
+def validate_executorch_checkout(
+ checkout: Path, expected_commit: str, *, required_ancestors: Iterable[str] = ()
+) -> None:
+ if not checkout.is_dir() or not (checkout / ".git").exists():
+ raise RuntimeError(f"ExecuTorch checkout is missing: {checkout}")
+ actual = subprocess.run(
+ ["git", "-C", str(checkout), "rev-parse", "HEAD"],
+ check=True,
+ capture_output=True,
+ text=True,
+ ).stdout.strip()
+ if actual != expected_commit:
+ raise RuntimeError(f"ExecuTorch checkout is {actual}; expected {expected_commit}")
+ dirty = subprocess.run(
+ ["git", "-C", str(checkout), "status", "--porcelain"],
+ check=True,
+ capture_output=True,
+ text=True,
+ ).stdout
+ if dirty:
+ raise RuntimeError("ExecuTorch checkout must remain clean")
+ for ancestor in required_ancestors:
+ result = subprocess.run(
+ ["git", "-C", str(checkout), "merge-base", "--is-ancestor", ancestor, actual],
+ check=False,
+ capture_output=True,
+ text=True,
+ )
+ if result.returncode != 0:
+ raise RuntimeError(
+ f"pinned ExecuTorch commit does not contain landed capability {ancestor}"
+ )
+
+
+def require_supported_platform() -> None:
+ if platform.system() != "Darwin" or platform.machine() != "arm64":
+ raise RuntimeError("Muse Glimmer currently supports macOS on Apple silicon only")
+
+
+def ensure_local_directories() -> None:
+ for path in (RUN_DIR, LOG_DIR, STATE_DIR, ARTIFACT_DIR, SOURCE_DIR):
+ path.mkdir(parents=True, exist_ok=True)
+
+
+def relative_local_path(value: str) -> Path:
+ path = (ROOT / value).resolve()
+ try:
+ path.relative_to(LOCAL.resolve())
+ except ValueError as error:
+ raise ValueError(f"artifact destination must stay under .local: {value}") from error
+ return path
+
+
+def atomic_write_json(path: Path, value: object, *, mode: int = 0o600) -> None:
+ path.parent.mkdir(parents=True, exist_ok=True)
+ temporary = path.with_name(f".{path.name}.{os.getpid()}.{secrets.token_hex(4)}.tmp")
+ descriptor = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL, mode)
+ try:
+ with os.fdopen(descriptor, "w", encoding="utf-8") as stream:
+ json.dump(value, stream, indent=2, sort_keys=True)
+ stream.write("\n")
+ stream.flush()
+ os.fsync(stream.fileno())
+ os.replace(temporary, path)
+ except BaseException:
+ temporary.unlink(missing_ok=True)
+ raise
+
+
+def load_valid_receipt() -> dict[str, Any]:
+ if not PREPARED_RECEIPT.is_file():
+ raise RuntimeError("artifacts are not prepared; run `make prepare-artifacts`")
+ receipt = read_json(PREPARED_RECEIPT)
+ expected_locks = {
+ "compatibility": digest_json(COMPATIBILITY_LOCK),
+ "artifacts": digest_json(ARTIFACT_LOCK),
+ "toolchain": digest_json(TOOLCHAIN_LOCK),
+ }
+ if receipt.get("locks") != expected_locks:
+ raise RuntimeError("artifact receipt is stale; run `make prepare-artifacts`")
+ compatibility = read_json(COMPATIBILITY_LOCK)
+ commit = compatibility.get("executorch", {}).get("commit")
+ if not commit or receipt.get("executorch_commit") != commit:
+ raise RuntimeError("artifact receipt does not match the pinned ExecuTorch commit")
+ checkout_value = receipt.get("executorch_checkout")
+ if not isinstance(checkout_value, str):
+ raise RuntimeError("artifact receipt has no ExecuTorch checkout")
+ validate_executorch_checkout(
+ Path(checkout_value).expanduser().resolve(),
+ commit,
+ required_ancestors=landed_gate_commits(compatibility),
+ )
+ recorded = receipt.get("artifacts")
+ if not isinstance(recorded, dict):
+ raise RuntimeError("artifact receipt has no artifact inventory")
+ manifest = read_json(ARTIFACT_LOCK)
+ requirements = {item["role"]: item for item in manifest["artifacts"]}
+ if set(recorded) != set(requirements):
+ raise RuntimeError("artifact receipt roles do not match the manifest")
+ for role, item in recorded.items():
+ if not isinstance(item, dict) or not isinstance(item.get("path"), str):
+ raise RuntimeError(f"invalid artifact receipt entry: {role}")
+ path = relative_local_path(item["path"])
+ if not path.exists():
+ raise RuntimeError(f"prepared artifact is missing: {role}")
+ requirement = requirements[role]
+ if requirement["kind"] == "file" and not path.is_file():
+ raise RuntimeError(f"prepared artifact must remain a regular file: {role}")
+ if requirement["kind"] == "directory" and not path.is_dir():
+ raise RuntimeError(f"prepared artifact must remain a directory: {role}")
+ if requirement["executable"] and not os.access(path, os.X_OK):
+ raise RuntimeError(f"prepared artifact must remain executable: {role}")
+ actual = sha256_tree(path) if path.is_dir() else sha256_file(path)
+ if actual != item.get("sha256"):
+ raise RuntimeError(f"prepared artifact checksum changed: {role}")
+ return receipt
diff --git a/muse_glimmer/macos/scripts/validate_manifests.py b/muse_glimmer/macos/scripts/validate_manifests.py
new file mode 100644
index 0000000000..9687e1b0ab
--- /dev/null
+++ b/muse_glimmer/macos/scripts/validate_manifests.py
@@ -0,0 +1,190 @@
+from __future__ import annotations
+
+import json
+import os
+import re
+import sys
+from pathlib import Path
+
+try:
+ import jsonschema
+except ModuleNotFoundError: # Core validation must also work before bootstrap.
+ jsonschema = None # type: ignore[assignment]
+
+from scripts.repository import (
+ ARTIFACT_LOCK,
+ COMPATIBILITY_LOCK,
+ ROOT,
+ TOOLCHAIN_LOCK,
+ landed_gate_commits,
+ validate_executorch_checkout,
+)
+
+_SHA256 = re.compile(r"^[0-9a-f]{64}$")
+_GIT_COMMIT = re.compile(r"^[0-9a-f]{40}$")
+_REQUIRED_CAPABILITIES = {"supports_cancel", "supertonic_server_jsonl"}
+_REQUIRED_GATES = {"supertonic_runtime", "supports_cancel", "supertonic_server_jsonl"}
+_GATE_STATUSES = {"landed", "pending", "unsubmitted"}
+
+
+def _load(path: Path) -> dict[str, object]:
+ with path.open(encoding="utf-8") as stream:
+ value = json.load(stream)
+ if not isinstance(value, dict):
+ raise RuntimeError(f"manifest must be a JSON object: {path}")
+ return value
+
+
+def _validate_artifacts() -> None:
+ manifest = _load(ARTIFACT_LOCK)
+ if manifest.get("schema_version") != 1 or manifest.get("platform") != "macos-arm64":
+ raise RuntimeError("artifact manifest has an unsupported schema or platform")
+ artifacts = manifest.get("artifacts")
+ if not isinstance(artifacts, list) or not artifacts:
+ raise RuntimeError("artifact manifest must contain artifacts")
+ roles: set[str] = set()
+ required_keys = {
+ "role",
+ "kind",
+ "executable",
+ "distribution",
+ "license",
+ "destination",
+ "sensitive",
+ "sha256",
+ }
+ for item in artifacts:
+ if not isinstance(item, dict) or not required_keys <= set(item):
+ raise RuntimeError("artifact entries must contain all required keys")
+ role = item["role"]
+ if not isinstance(role, str) or not role or role in roles:
+ raise RuntimeError(f"artifact role is empty or duplicated: {role!r}")
+ roles.add(role)
+ if item["kind"] not in {"file", "directory"} or not isinstance(item["executable"], bool):
+ raise RuntimeError(f"invalid artifact shape for {role}")
+ if item["kind"] == "directory" and item["executable"]:
+ raise RuntimeError(f"artifact directories cannot be executable: {role}")
+ if item["distribution"] not in {"build", "download", "user-provided"}:
+ raise RuntimeError(f"invalid distribution for {role}")
+ destination = item["destination"]
+ if not isinstance(destination, str) or not destination.startswith(".local/artifacts/"):
+ raise RuntimeError(f"artifact destination escapes .local/artifacts: {role}")
+ checksum = item["sha256"]
+ if checksum is not None and (
+ not isinstance(checksum, str) or not _SHA256.fullmatch(checksum)
+ ):
+ raise RuntimeError(f"invalid artifact checksum: {role}")
+ required_roles = {
+ "parakeet_helper",
+ "parakeet_model",
+ "parakeet_tokenizer",
+ "muse_glimmer_worker",
+ "muse_glimmer_model",
+ "muse_glimmer_tokenizer",
+ "supertonic_runner",
+ "supertonic_model",
+ "supertonic_assets",
+ "supertonic_voice_style",
+ }
+ if roles != required_roles:
+ raise RuntimeError(f"artifact roles differ from the runtime contract: {sorted(roles)}")
+
+
+def _validate_compatibility(
+ compatibility: dict[str, object], *, release_checkout: Path | None = None
+) -> None:
+ if compatibility.get("schema_version") != 1 or compatibility.get("platform") != "macos-arm64":
+ raise RuntimeError("compatibility lock has an unsupported schema or platform")
+ executorch = compatibility.get("executorch")
+ if not isinstance(executorch, dict):
+ raise RuntimeError("compatibility lock has no ExecuTorch configuration")
+ capabilities = executorch.get("required_capabilities")
+ if not isinstance(capabilities, list) or not set(capabilities) >= _REQUIRED_CAPABILITIES:
+ raise RuntimeError("compatibility lock omits required runtime capabilities")
+ gates = executorch.get("gates")
+ if not isinstance(gates, dict) or not set(gates) >= _REQUIRED_GATES:
+ raise RuntimeError("compatibility lock omits required upstream gates")
+ for name in _REQUIRED_GATES:
+ gate = gates[name]
+ if not isinstance(gate, dict):
+ raise RuntimeError(f"compatibility gate must be an object: {name}")
+ status = gate.get("status")
+ commit = gate.get("commit")
+ pull_request = gate.get("pull_request")
+ if status not in _GATE_STATUSES:
+ raise RuntimeError(f"compatibility gate has invalid status: {name}")
+ if pull_request is not None and (
+ not isinstance(pull_request, str)
+ or not pull_request.startswith("https://github.com/pytorch/executorch/pull/")
+ ):
+ raise RuntimeError(f"compatibility gate has invalid pull request: {name}")
+ if status == "landed":
+ if not isinstance(commit, str) or not _GIT_COMMIT.fullmatch(commit):
+ raise RuntimeError(f"landed compatibility gate requires a commit: {name}")
+ elif commit is not None:
+ raise RuntimeError(f"unlanded compatibility gate cannot have a commit: {name}")
+
+ final_commit = executorch.get("commit")
+ if final_commit is not None and (
+ not isinstance(final_commit, str) or not _GIT_COMMIT.fullmatch(final_commit)
+ ):
+ raise RuntimeError("compatibility lock has an invalid final ExecuTorch commit")
+ if compatibility.get("ready_for_release"):
+ if final_commit is None:
+ raise RuntimeError("release-ready compatibility requires an immutable commit")
+ unlanded = sorted(name for name in _REQUIRED_GATES if gates[name]["status"] != "landed")
+ if unlanded:
+ raise RuntimeError(f"release-ready compatibility has unlanded gates: {unlanded}")
+ if release_checkout is None:
+ raise RuntimeError(
+ "release-ready compatibility requires checkout ancestry verification"
+ )
+ validate_executorch_checkout(
+ release_checkout,
+ final_commit,
+ required_ancestors=landed_gate_commits(compatibility),
+ )
+
+
+def main() -> int:
+ _validate_artifacts()
+ schema = _load(ROOT / "artifacts/manifest.schema.json")
+ if schema.get("$schema") != "https://json-schema.org/draft/2020-12/schema":
+ raise RuntimeError("artifact schema must use JSON Schema 2020-12")
+ if jsonschema is not None:
+ jsonschema.Draft202012Validator.check_schema(schema)
+ jsonschema.Draft202012Validator(schema).validate(_load(ARTIFACT_LOCK))
+
+ compatibility = _load(COMPATIBILITY_LOCK)
+ release_checkout = None
+ if compatibility.get("ready_for_release"):
+ release_checkout = (
+ Path(os.environ.get("GLIMMER_EXECUTORCH_ROOT", ROOT / ".local/src/executorch"))
+ .expanduser()
+ .resolve()
+ )
+ _validate_compatibility(compatibility, release_checkout=release_checkout)
+ if compatibility.get("ready_for_release"):
+ for artifact in _load(ARTIFACT_LOCK)["artifacts"]:
+ if not all(artifact.get(field) for field in ("source", "revision", "sha256")):
+ raise RuntimeError(
+ f"release-ready artifact provenance is incomplete: {artifact['role']}"
+ )
+ if artifact.get("size_bytes") is None or str(artifact["license"]).startswith("See "):
+ raise RuntimeError(
+ f"release-ready artifact metadata is incomplete: {artifact['role']}"
+ )
+
+ toolchain = _load(TOOLCHAIN_LOCK)
+ if toolchain.get("platform") != {"system": "Darwin", "machine": "arm64"}:
+ raise RuntimeError("toolchain platform must be macOS arm64")
+ print("Manifest validation passed.")
+ return 0
+
+
+if __name__ == "__main__":
+ try:
+ raise SystemExit(main())
+ except (KeyError, RuntimeError, TypeError, ValueError) as error:
+ print(f"validate-manifests: {error}", file=sys.stderr)
+ raise SystemExit(1) from error
diff --git a/muse_glimmer/macos/tests/conftest.py b/muse_glimmer/macos/tests/conftest.py
new file mode 100644
index 0000000000..f96b4d8630
--- /dev/null
+++ b/muse_glimmer/macos/tests/conftest.py
@@ -0,0 +1,8 @@
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[1]
+if str(ROOT) not in sys.path:
+ sys.path.insert(0, str(ROOT))
diff --git a/muse_glimmer/macos/tests/test_bootstrap.py b/muse_glimmer/macos/tests/test_bootstrap.py
new file mode 100644
index 0000000000..1118f3403b
--- /dev/null
+++ b/muse_glimmer/macos/tests/test_bootstrap.py
@@ -0,0 +1,17 @@
+from __future__ import annotations
+
+import pytest
+
+from scripts.bootstrap import _require_version, _version_tuple
+
+
+def test_version_parser_accepts_common_tool_output() -> None:
+ assert _version_tuple("Python 3.13.2") == (3, 13, 2)
+ assert _version_tuple("v22.12.0") == (22, 12, 0)
+ assert _version_tuple("livekit-server version 1.13.5") == (1, 13, 5)
+
+
+def test_version_range_is_enforced() -> None:
+ _require_version("node", "v22.12.0", ">=22.12.0,<23")
+ with pytest.raises(RuntimeError, match="does not satisfy"):
+ _require_version("node", "v24.0.0", ">=22.12.0,<23")
diff --git a/muse_glimmer/macos/tests/test_dev_stack.py b/muse_glimmer/macos/tests/test_dev_stack.py
new file mode 100644
index 0000000000..3428ee10e5
--- /dev/null
+++ b/muse_glimmer/macos/tests/test_dev_stack.py
@@ -0,0 +1,327 @@
+from __future__ import annotations
+
+import json
+import os
+from pathlib import Path
+
+import pytest
+
+from scripts import dev_stack
+
+
+@pytest.fixture
+def receipt() -> dict[str, object]:
+ roles = {
+ "parakeet_helper": ".local/artifacts/bin/parakeet_helper",
+ "parakeet_model": ".local/artifacts/parakeet/model.pte",
+ "parakeet_tokenizer": ".local/artifacts/parakeet/tokenizer.model",
+ "supertonic_runner": ".local/artifacts/bin/supertonic_runner",
+ "supertonic_model": ".local/artifacts/supertonic/model.pte",
+ "supertonic_assets": ".local/artifacts/supertonic/assets",
+ "supertonic_voice_style": ".local/artifacts/supertonic/voice-style.json",
+ }
+ return {"artifacts": {role: {"path": path} for role, path in roles.items()}}
+
+
+def test_validated_tools_rejects_missing_bootstrap_receipt(
+ tmp_path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ monkeypatch.setattr(dev_stack, "BOOTSTRAP_RECEIPT", tmp_path / "missing.json")
+ with pytest.raises(RuntimeError, match="not bootstrapped"):
+ dev_stack._validated_tools()
+
+
+def test_validated_tools_rejects_stale_bootstrap_inputs(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ receipt = tmp_path / "bootstrap.json"
+ receipt.write_text(
+ json.dumps(
+ {
+ "toolchain_lock": "lock",
+ "bootstrap_inputs": "stale",
+ "web_dist": "web",
+ "tools": {},
+ }
+ )
+ )
+ web_dist = tmp_path / "dist"
+ web_dist.mkdir()
+ monkeypatch.setattr(dev_stack, "BOOTSTRAP_RECEIPT", receipt)
+ monkeypatch.setattr(dev_stack, "WEB_DIST", web_dist)
+ monkeypatch.setattr(dev_stack, "digest_json", lambda _path: "lock")
+ monkeypatch.setattr(dev_stack, "digest_paths", lambda _paths: "current")
+ monkeypatch.setattr(dev_stack, "sha256_tree", lambda _path: "web")
+
+ with pytest.raises(RuntimeError, match="stale"):
+ dev_stack._validated_tools()
+
+
+def test_validated_tools_rejects_changed_python_environment(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ python = tmp_path / "python"
+ node = tmp_path / "node"
+ livekit = tmp_path / "livekit-server"
+ for tool in (python, node, livekit):
+ tool.write_text("tool")
+ tool.chmod(0o755)
+ receipt = tmp_path / "bootstrap.json"
+ receipt.write_text(
+ json.dumps(
+ {
+ "toolchain_lock": "lock",
+ "bootstrap_inputs": "inputs",
+ "web_dist": "web",
+ "python_environment": "old",
+ "tools": {
+ "python": {"path": str(python)},
+ "node": {"path": str(node)},
+ "livekit-server": {"path": str(livekit)},
+ },
+ }
+ )
+ )
+ web_dist = tmp_path / "dist"
+ web_dist.mkdir()
+ monkeypatch.setattr(dev_stack, "BOOTSTRAP_RECEIPT", receipt)
+ monkeypatch.setattr(dev_stack, "WEB_DIST", web_dist)
+ monkeypatch.setattr(dev_stack, "digest_json", lambda _path: "lock")
+ monkeypatch.setattr(dev_stack, "digest_paths", lambda _paths: "inputs")
+ monkeypatch.setattr(dev_stack, "sha256_tree", lambda _path: "web")
+ monkeypatch.setattr(dev_stack, "python_environment_fingerprint", lambda _path: "new")
+
+ with pytest.raises(RuntimeError, match="Python environment changed"):
+ dev_stack._validated_tools()
+
+
+def test_credentials_are_private_before_secret_is_written(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ credential_file = tmp_path / "livekit.keys"
+ observed_modes: list[int] = []
+ real_fdopen = os.fdopen
+
+ def checked_fdopen(descriptor: int, *args, **kwargs):
+ observed_modes.append(os.fstat(descriptor).st_mode & 0o777)
+ return real_fdopen(descriptor, *args, **kwargs)
+
+ monkeypatch.setattr(dev_stack, "CREDENTIAL_FILE", credential_file)
+ monkeypatch.setattr(dev_stack.os, "fdopen", checked_fdopen)
+
+ api_key, api_secret = dev_stack._new_credentials()
+
+ assert credential_file.read_text() == f"{api_key}: {api_secret}\n"
+ assert observed_modes == [0o600]
+ assert credential_file.stat().st_mode & 0o777 == 0o600
+
+
+def test_service_order_and_local_environment(
+ receipt: dict[str, object], monkeypatch: pytest.MonkeyPatch
+) -> None:
+ monkeypatch.setattr(
+ dev_stack,
+ "_validated_tools",
+ lambda: {
+ "python": "/test/.venv/bin/python",
+ "node": "/test/bin/node",
+ "livekit-server": "/test/bin/livekit-server",
+ },
+ )
+ monkeypatch.setattr(dev_stack.os, "access", lambda _path, _mode: True)
+ services = dev_stack._services(receipt, "test-key", "test-secret")
+
+ assert [service.name for service in services] == list(dev_stack.SERVICE_ORDER)
+ worker = services[-1]
+ assert worker.environment["LIVEKIT_URL"] == "ws://127.0.0.1:7880"
+ assert worker.environment["MUSE_GLIMMER_BASE_URL"] == "http://127.0.0.1:8000/v1"
+ assert worker.environment["MUSE_GLIMMER_REASONING_STRENGTH"] == "low"
+ assert "OPENAI_API_KEY" not in worker.environment
+
+
+def test_stop_records_uses_reverse_order_and_escalates(monkeypatch: pytest.MonkeyPatch) -> None:
+ signalled: list[tuple[str, int]] = []
+ records = {name: {"name": name} for name in dev_stack.SERVICE_ORDER}
+ active = {name for name in dev_stack.SERVICE_ORDER}
+
+ def matches(record: dict[str, object]) -> bool:
+ return str(record["name"]) in active
+
+ def signal_record(record: dict[str, object], signum: int) -> None:
+ name = str(record["name"])
+ signalled.append((name, signum))
+ if signum == dev_stack.signal.SIGKILL:
+ active.discard(name)
+
+ now = 0.0
+
+ def monotonic() -> float:
+ nonlocal now
+ now += 20.0
+ return now
+
+ monkeypatch.setattr(dev_stack, "_record_group_owned", matches)
+ monkeypatch.setattr(dev_stack, "_signal_service", signal_record)
+ monkeypatch.setattr(dev_stack.time, "monotonic", monotonic)
+ monkeypatch.setattr(dev_stack.time, "sleep", lambda _seconds: None)
+
+ dev_stack._stop_records(records)
+
+ expected_reverse = list(reversed(dev_stack.SERVICE_ORDER))
+ assert [
+ name for name, signum in signalled if signum == dev_stack.signal.SIGTERM
+ ] == expected_reverse
+ assert [
+ name for name, signum in signalled if signum == dev_stack.signal.SIGKILL
+ ] == expected_reverse
+
+
+def test_up_rejects_dead_leader_with_live_child_group(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ record = {"pid": 42, "pgid": 42}
+ monkeypatch.setattr(
+ dev_stack,
+ "_read_state",
+ lambda: {"schema_version": 1, "services": {"agent": record}},
+ )
+ monkeypatch.setattr(dev_stack, "_record_group_owned", lambda _record: True)
+
+ with pytest.raises(RuntimeError, match="orphaned process group"):
+ dev_stack._up_locked()
+
+
+def test_dead_leader_with_live_child_group_is_still_owned(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ record = {"pid": 42, "pgid": 42}
+ alive = True
+ signals: list[int] = []
+
+ monkeypatch.setattr(dev_stack, "_service_matches", lambda _record: False)
+ monkeypatch.setattr(dev_stack, "_pid_alive", lambda _pid: False)
+ monkeypatch.setattr(dev_stack, "_process_group_alive", lambda _pgid: alive)
+
+ def killpg(_pgid: int, signum: int) -> None:
+ nonlocal alive
+ signals.append(signum)
+ alive = False
+
+ monkeypatch.setattr(dev_stack.os, "killpg", killpg)
+
+ assert dev_stack._record_group_owned(record)
+ dev_stack._stop_records({"agent": record})
+ assert signals == [dev_stack.signal.SIGTERM]
+
+
+def test_stop_waits_for_delayed_exit_after_sigkill(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ record = {"name": "agent"}
+ owned = iter((True, False))
+ signals: list[int] = []
+ times = iter((0.0, 11.0, 20.0, 20.0))
+
+ def monotonic() -> float:
+ return next(times)
+
+ monkeypatch.setattr(dev_stack, "_record_group_owned", lambda _record: next(owned))
+ monkeypatch.setattr(
+ dev_stack,
+ "_signal_service",
+ lambda _record, signum: signals.append(signum),
+ )
+ monkeypatch.setattr(dev_stack.time, "monotonic", monotonic)
+ monkeypatch.setattr(dev_stack.time, "sleep", lambda _seconds: None)
+
+ dev_stack._stop_records({"agent": record})
+
+ assert signals == [dev_stack.signal.SIGTERM, dev_stack.signal.SIGKILL]
+
+
+def test_agent_readiness_returns_health_endpoint(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ log = tmp_path / "agent.log"
+ log.write_text(
+ 'HTTP server listening on 127.0.0.1:54321\nregistered worker {"agent_name": "assistant"}\n'
+ )
+ monkeypatch.setattr(dev_stack, "ROOT", tmp_path)
+ monkeypatch.setattr(dev_stack, "_http_ready", lambda url: url.endswith(":54321/"))
+ monkeypatch.setattr(dev_stack, "_service_matches", lambda _record: True)
+
+ assert dev_stack._wait_for_agent({"log": "agent.log"}) == "http://127.0.0.1:54321/"
+
+
+def test_process_identity_requires_start_time_and_command(monkeypatch: pytest.MonkeyPatch) -> None:
+ command = ".venv/bin/muse-glimmer-worker dev"
+ record = {
+ "pid": 42,
+ "start_time": "Mon Aug 24 00:00:00 2026",
+ "command_marker": "muse-glimmer-worker",
+ "observed_command_digest": dev_stack._observed_command_digest(command),
+ }
+ monkeypatch.setattr(dev_stack, "_pid_alive", lambda _pid: True)
+ monkeypatch.setattr(dev_stack, "_process_start", lambda _pid: record["start_time"])
+ monkeypatch.setattr(dev_stack, "_process_command", lambda _pid: command)
+ assert dev_stack._service_matches(record)
+
+ monkeypatch.setattr(dev_stack, "_process_start", lambda _pid: "different process start")
+ assert not dev_stack._service_matches(record)
+
+
+def test_new_process_group_termination_escalates(monkeypatch: pytest.MonkeyPatch) -> None:
+ signals: list[tuple[int, int]] = []
+
+ class Process:
+ pid = 42
+
+ def wait(self, timeout: float) -> int:
+ return 0
+
+ waits = iter((False, True))
+ monkeypatch.setattr(dev_stack, "_wait_for_process_group", lambda _pgid, _timeout: next(waits))
+ monkeypatch.setattr(dev_stack.os, "killpg", lambda pgid, signum: signals.append((pgid, signum)))
+
+ dev_stack._terminate_new_process_group(Process()) # type: ignore[arg-type]
+
+ assert signals == [
+ (42, dev_stack.signal.SIGTERM),
+ (42, dev_stack.signal.SIGKILL),
+ ]
+
+
+def test_restart_holds_one_lifecycle_lock(monkeypatch: pytest.MonkeyPatch) -> None:
+ events: list[str] = []
+
+ class Lock:
+ def __enter__(self):
+ events.append("locked")
+
+ def __exit__(self, *_args):
+ events.append("unlocked")
+
+ monkeypatch.setattr(dev_stack, "_lifecycle_lock", Lock)
+ monkeypatch.setattr(dev_stack, "_down_locked", lambda: events.append("down"))
+ monkeypatch.setattr(dev_stack, "_up_locked", lambda: events.append("up"))
+
+ assert dev_stack.restart() == 0
+ assert events == ["locked", "down", "up", "unlocked"]
+
+
+def test_missing_artifact_role_fails(
+ receipt: dict[str, object], monkeypatch: pytest.MonkeyPatch
+) -> None:
+ monkeypatch.setattr(
+ dev_stack,
+ "_validated_tools",
+ lambda: {
+ "python": "/test/.venv/bin/python",
+ "node": "/test/bin/node",
+ "livekit-server": "/test/bin/livekit-server",
+ },
+ )
+ monkeypatch.setattr(dev_stack.os, "access", lambda _path, _mode: True)
+ del receipt["artifacts"]["supertonic_voice_style"] # type: ignore[index]
+ with pytest.raises(RuntimeError, match="supertonic_voice_style"):
+ dev_stack._services(receipt, "test-key", "test-secret")
diff --git a/muse_glimmer/macos/tests/test_e2e_stack.py b/muse_glimmer/macos/tests/test_e2e_stack.py
new file mode 100644
index 0000000000..bdd395953b
--- /dev/null
+++ b/muse_glimmer/macos/tests/test_e2e_stack.py
@@ -0,0 +1,41 @@
+from __future__ import annotations
+
+import subprocess
+import sys
+
+import pytest
+
+from scripts import dev_stack
+from scripts.repository import (
+ CREDENTIAL_FILE,
+ PREPARED_RECEIPT,
+ ROOT,
+ load_valid_receipt,
+)
+
+pytestmark = pytest.mark.e2e
+
+
+def test_running_prepared_stack_generation_cancellation_and_privacy() -> None:
+ if not PREPARED_RECEIPT.is_file():
+ pytest.skip("local artifacts are not prepared")
+ if dev_stack.status() != 0:
+ pytest.skip("prepared Muse Glimmer stack is not running; run `make dev` first")
+
+ api_key, api_secret = CREDENTIAL_FILE.read_text(encoding="utf-8").strip().split(": ", 1)
+ worker_environment = dev_stack._services(load_valid_receipt(), api_key, api_secret)[
+ -1
+ ].environment
+ subprocess.run(
+ [sys.executable, "-m", "scripts.llm_readiness"], cwd=ROOT, check=True, timeout=240
+ )
+ subprocess.run(
+ [str(ROOT / ".venv/bin/muse-glimmer-diagnostics"), "doctor"],
+ cwd=ROOT,
+ env=worker_environment,
+ check=True,
+ timeout=360,
+ )
+ subprocess.run(
+ [sys.executable, "-m", "scripts.privacy_audit"], cwd=ROOT, check=True, timeout=60
+ )
diff --git a/muse_glimmer/macos/tests/test_makefile.py b/muse_glimmer/macos/tests/test_makefile.py
new file mode 100644
index 0000000000..f618b97101
--- /dev/null
+++ b/muse_glimmer/macos/tests/test_makefile.py
@@ -0,0 +1,18 @@
+from __future__ import annotations
+
+import subprocess
+
+from scripts.repository import ROOT
+
+
+def test_dev_and_dev_up_execute_up_once() -> None:
+ dev = subprocess.run(
+ ["make", "-n", "dev"], cwd=ROOT, check=True, capture_output=True, text=True
+ )
+ dev_up = subprocess.run(
+ ["make", "-n", "dev", "up"], cwd=ROOT, check=True, capture_output=True, text=True
+ )
+
+ command = ".venv/bin/python -m scripts.dev_stack up"
+ assert dev.stdout.count(command) == 1
+ assert dev_up.stdout.count(command) == 1
diff --git a/muse_glimmer/macos/tests/test_privacy_audit.py b/muse_glimmer/macos/tests/test_privacy_audit.py
new file mode 100644
index 0000000000..2a5ff04983
--- /dev/null
+++ b/muse_glimmer/macos/tests/test_privacy_audit.py
@@ -0,0 +1,32 @@
+from __future__ import annotations
+
+import json
+from types import SimpleNamespace
+
+import pytest
+
+from scripts import privacy_audit
+
+
+def test_external_connection_audit_includes_managed_process_group(
+ tmp_path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ run_dir = tmp_path / ".local/run"
+ run_dir.mkdir(parents=True)
+ (run_dir / "stack.json").write_text(
+ json.dumps({"services": {"web": {"pid": 100, "pgid": 100}}})
+ )
+ output = (
+ "COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME\n"
+ "node 321 user 10u IPv4 1 0t0 TCP 127.0.0.1:5000->203.0.113.10:443\n"
+ )
+ monkeypatch.setattr(privacy_audit, "ROOT", tmp_path)
+ monkeypatch.setattr(
+ privacy_audit.subprocess,
+ "run",
+ lambda *args, **kwargs: SimpleNamespace(stdout=output),
+ )
+ monkeypatch.setattr(privacy_audit.os, "getpgid", lambda pid: 100 if pid == 321 else pid)
+
+ with pytest.raises(RuntimeError, match="non-loopback connection"):
+ privacy_audit._assert_no_external_connections()
diff --git a/muse_glimmer/macos/tests/test_publication_check.py b/muse_glimmer/macos/tests/test_publication_check.py
new file mode 100644
index 0000000000..beb3ddca5b
--- /dev/null
+++ b/muse_glimmer/macos/tests/test_publication_check.py
@@ -0,0 +1,91 @@
+from __future__ import annotations
+
+import subprocess
+import sys
+from pathlib import Path
+from types import SimpleNamespace
+
+from scripts import publication_check
+
+
+def test_rejects_model_and_environment_files(tmp_path: Path, monkeypatch) -> None:
+ monkeypatch.setattr(publication_check, "ROOT", tmp_path)
+ model = tmp_path / "model.pte"
+ model.write_bytes(b"artifact")
+ environment = tmp_path / ".env"
+ environment.write_text("LIVEKIT_API_SECRET=real-secret\n")
+
+ assert publication_check._check_path(model)
+ assert publication_check._check_path(environment)
+
+
+def test_allows_explicit_test_fixture(tmp_path: Path, monkeypatch) -> None:
+ monkeypatch.setattr(publication_check, "ROOT", tmp_path)
+ source = tmp_path / "test_config.py"
+ source.write_text('value = "LIVEKIT_API_SECRET=test-secret"\n')
+
+ assert publication_check._check_path(source) == []
+
+
+def test_detects_nested_git_metadata_file(tmp_path: Path, monkeypatch) -> None:
+ monkeypatch.setattr(publication_check, "ROOT", tmp_path)
+ nested = tmp_path / "dependency"
+ nested.mkdir()
+ (nested / ".git").write_text("gitdir: ../.git/modules/dependency\n")
+
+ assert publication_check._nested_repositories() == [nested / ".git"]
+
+
+def test_tracked_only_rejects_repository_without_tracked_files(tmp_path: Path, monkeypatch) -> None:
+ monkeypatch.setattr(publication_check, "ROOT", tmp_path)
+ monkeypatch.setattr(sys, "argv", ["publication-check", "--tracked-only"])
+ monkeypatch.setattr(
+ publication_check.subprocess,
+ "run",
+ lambda *args, **kwargs: SimpleNamespace(stdout=b""),
+ )
+
+ assert publication_check.main() == 1
+
+
+def test_rejects_literal_private_path(tmp_path: Path, monkeypatch) -> None:
+ monkeypatch.setattr(publication_check, "ROOT", tmp_path)
+ source = tmp_path / "config.py"
+ source.write_bytes(b"root = " + b'"/' + b"Users/example/models" + b'"')
+
+ assert any("absolute user path" in error for error in publication_check._check_path(source))
+
+
+def test_candidate_files_are_scoped_to_nested_application(tmp_path: Path, monkeypatch) -> None:
+ application = tmp_path / "muse_glimmer" / "macos"
+ application.mkdir(parents=True)
+ subprocess.run(["git", "init", "-q"], cwd=tmp_path, check=True)
+ inside = application / "inside.py"
+ inside.write_text("safe = True\n")
+ outside = tmp_path / "sibling.env"
+ outside.write_text("LIVEKIT_API_SECRET=real-secret\n")
+ subprocess.run(
+ ["git", "add", "muse_glimmer/macos/inside.py", "sibling.env"], cwd=tmp_path, check=True
+ )
+ untracked = application / "untracked.py"
+ untracked.write_text("safe = True\n")
+ monkeypatch.setattr(publication_check, "ROOT", application)
+
+ assert set(publication_check._candidate_files()) == {inside.resolve(), untracked.resolve()}
+ assert publication_check._git_files("--cached") == [inside.resolve()]
+
+
+def test_git_candidate_cannot_escape_application_root(tmp_path: Path, monkeypatch) -> None:
+ monkeypatch.setattr(publication_check, "ROOT", tmp_path)
+ monkeypatch.setattr(
+ publication_check.subprocess,
+ "run",
+ lambda *args, **kwargs: SimpleNamespace(stdout=b"../secret\0"),
+ )
+
+ try:
+ publication_check._candidate_files()
+ except RuntimeError as error:
+ assert "escapes application root" in str(error)
+ else:
+ raise AssertionError("escaping Git candidate was accepted")
diff --git a/muse_glimmer/macos/tests/test_repository.py b/muse_glimmer/macos/tests/test_repository.py
new file mode 100644
index 0000000000..0d7b6e9b5d
--- /dev/null
+++ b/muse_glimmer/macos/tests/test_repository.py
@@ -0,0 +1,116 @@
+from __future__ import annotations
+
+import json
+from pathlib import Path
+from types import SimpleNamespace
+
+import pytest
+
+from scripts import repository
+
+
+def test_relative_local_path_rejects_escape(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ local = tmp_path / ".local"
+ local.mkdir()
+ monkeypatch.setattr(repository, "ROOT", tmp_path)
+ monkeypatch.setattr(repository, "LOCAL", local)
+
+ assert repository.relative_local_path(".local/artifacts/model") == local / "artifacts/model"
+ with pytest.raises(ValueError, match="must stay under .local"):
+ repository.relative_local_path("outside")
+
+
+def test_atomic_write_json_uses_private_permissions(tmp_path: Path) -> None:
+ destination = tmp_path / "state.json"
+ repository.atomic_write_json(destination, {"status": "ok"})
+
+ assert json.loads(destination.read_text()) == {"status": "ok"}
+ assert destination.stat().st_mode & 0o777 == 0o600
+
+
+def test_installed_environment_fingerprint_detects_same_version_content_change(
+ tmp_path: Path,
+) -> None:
+ package = tmp_path / "package.py"
+ package.write_text("value = 1\n")
+
+ class Distribution:
+ metadata = {"Name": "example-package"}
+ version = "1.0.0"
+ files = (Path("package.py"),)
+
+ def locate_file(self, relative: Path) -> Path:
+ return tmp_path / relative
+
+ first = repository.installed_environment_fingerprint([Distribution()])
+ package.write_text("value = 2\n")
+ second = repository.installed_environment_fingerprint([Distribution()])
+
+ assert first != second
+
+
+def test_executorch_checkout_rejects_drift(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
+ checkout = tmp_path / "executorch"
+ (checkout / ".git").mkdir(parents=True)
+ responses = iter(
+ (
+ SimpleNamespace(stdout="expected\n"),
+ SimpleNamespace(stdout="modified.py\n"),
+ )
+ )
+ monkeypatch.setattr(repository.subprocess, "run", lambda *args, **kwargs: next(responses))
+
+ with pytest.raises(RuntimeError, match="must remain clean"):
+ repository.validate_executorch_checkout(checkout, "expected")
+
+
+def test_executorch_checkout_rejects_changed_commit(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ checkout = tmp_path / "executorch"
+ (checkout / ".git").mkdir(parents=True)
+ monkeypatch.setattr(
+ repository.subprocess,
+ "run",
+ lambda *args, **kwargs: SimpleNamespace(stdout="different\n"),
+ )
+
+ with pytest.raises(RuntimeError, match="expected expected"):
+ repository.validate_executorch_checkout(checkout, "expected")
+
+
+def test_landed_gate_commits_returns_only_landed_capabilities() -> None:
+ compatibility = {
+ "executorch": {
+ "gates": {
+ "runtime": {"status": "landed", "commit": "a" * 40},
+ "cancellation": {"status": "pending", "commit": None},
+ }
+ }
+ }
+
+ assert repository.landed_gate_commits(compatibility) == ("a" * 40,)
+
+
+def test_executorch_checkout_rejects_missing_landed_capability(
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
+) -> None:
+ checkout = tmp_path / "executorch"
+ (checkout / ".git").mkdir(parents=True)
+ responses = iter(
+ (
+ SimpleNamespace(stdout="expected\n"),
+ SimpleNamespace(stdout=""),
+ SimpleNamespace(returncode=1),
+ )
+ )
+ monkeypatch.setattr(repository.subprocess, "run", lambda *args, **kwargs: next(responses))
+
+ with pytest.raises(RuntimeError, match="does not contain landed capability"):
+ repository.validate_executorch_checkout(
+ checkout,
+ "expected",
+ required_ancestors=("a" * 40,),
+ )
diff --git a/muse_glimmer/macos/tests/test_validate_manifests.py b/muse_glimmer/macos/tests/test_validate_manifests.py
new file mode 100644
index 0000000000..c9a06e143f
--- /dev/null
+++ b/muse_glimmer/macos/tests/test_validate_manifests.py
@@ -0,0 +1,124 @@
+from __future__ import annotations
+
+from copy import deepcopy
+
+import pytest
+
+from scripts import validate_manifests
+
+
+@pytest.fixture
+def compatibility() -> dict[str, object]:
+ return {
+ "schema_version": 1,
+ "status": "development-gated",
+ "platform": "macos-arm64",
+ "executorch": {
+ "repository": "https://github.com/pytorch/executorch.git",
+ "commit": None,
+ "required_capabilities": [
+ "parakeet_persistent_helper",
+ "muse_glimmer_dflash_mlx",
+ "supports_cancel",
+ "supertonic_server_jsonl",
+ ],
+ "gates": {
+ "supertonic_runtime": {
+ "status": "landed",
+ "pull_request": "https://github.com/pytorch/executorch/pull/22063",
+ "commit": "81969a92dd2e5515fa23ccdf9d87346cf3ba2ba2",
+ },
+ "supports_cancel": {
+ "status": "pending",
+ "pull_request": "https://github.com/pytorch/executorch/pull/22070",
+ "commit": None,
+ },
+ "supertonic_server_jsonl": {
+ "status": "unsubmitted",
+ "pull_request": None,
+ "commit": None,
+ },
+ },
+ },
+ "ready_for_release": False,
+ }
+
+
+def _gates(compatibility: dict[str, object]) -> dict[str, dict[str, object]]:
+ executorch = compatibility["executorch"]
+ assert isinstance(executorch, dict)
+ gates = executorch["gates"]
+ assert isinstance(gates, dict)
+ return gates # type: ignore[return-value]
+
+
+def test_accepts_development_gated_capabilities(compatibility: dict[str, object]) -> None:
+ validate_manifests._validate_compatibility(compatibility)
+
+
+def test_landed_gate_requires_commit(compatibility: dict[str, object]) -> None:
+ value = deepcopy(compatibility)
+ _gates(value)["supertonic_runtime"]["commit"] = None
+
+ with pytest.raises(RuntimeError, match="landed compatibility gate requires a commit"):
+ validate_manifests._validate_compatibility(value)
+
+
+def test_unlanded_gate_rejects_commit(compatibility: dict[str, object]) -> None:
+ value = deepcopy(compatibility)
+ _gates(value)["supports_cancel"]["commit"] = "a" * 40
+
+ with pytest.raises(RuntimeError, match="unlanded compatibility gate cannot have a commit"):
+ validate_manifests._validate_compatibility(value)
+
+
+def test_release_ready_rejects_unlanded_gate(compatibility: dict[str, object]) -> None:
+ value = deepcopy(compatibility)
+ value["ready_for_release"] = True
+ executorch = value["executorch"]
+ assert isinstance(executorch, dict)
+ executorch["commit"] = "b" * 40
+
+ with pytest.raises(RuntimeError, match="unlanded gates"):
+ validate_manifests._validate_compatibility(value)
+
+
+def test_release_ready_requires_checkout_ancestry_verification(
+ compatibility: dict[str, object],
+) -> None:
+ value = deepcopy(compatibility)
+ value["ready_for_release"] = True
+ executorch = value["executorch"]
+ assert isinstance(executorch, dict)
+ executorch["commit"] = "b" * 40
+ for gate in _gates(value).values():
+ gate["status"] = "landed"
+ gate["commit"] = "a" * 40
+
+ with pytest.raises(RuntimeError, match="requires checkout ancestry verification"):
+ validate_manifests._validate_compatibility(value)
+
+
+def test_release_ready_verifies_all_landed_gate_commits(
+ compatibility: dict[str, object], tmp_path, monkeypatch
+) -> None:
+ value = deepcopy(compatibility)
+ value["ready_for_release"] = True
+ executorch = value["executorch"]
+ assert isinstance(executorch, dict)
+ executorch["commit"] = "b" * 40
+ for index, gate in enumerate(_gates(value).values(), start=1):
+ gate["status"] = "landed"
+ gate["commit"] = str(index) * 40
+ calls = []
+ monkeypatch.setattr(
+ validate_manifests,
+ "validate_executorch_checkout",
+ lambda checkout, commit, *, required_ancestors: calls.append(
+ (checkout, commit, required_ancestors)
+ ),
+ )
+
+ validate_manifests._validate_compatibility(value, release_checkout=tmp_path)
+
+ assert calls == [(tmp_path, "b" * 40, ("1" * 40, "2" * 40, "3" * 40))]
diff --git a/muse_glimmer/macos/uv.lock b/muse_glimmer/macos/uv.lock
new file mode 100644
index 0000000000..0650e47d59
--- /dev/null
+++ b/muse_glimmer/macos/uv.lock
@@ -0,0 +1,1594 @@
+version = 1
+revision = 3
+requires-python = "==3.13.*"
+
+[manifest]
+members = [
+ "livekit-plugins-executorch",
+ "muse-glimmer-token-service",
+ "muse-glimmer-voice-agent-workspace",
+ "muse-glimmer-worker",
+]
+
+[[package]]
+name = "aiofiles"
+version = "25.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/41/c3/534eac40372d8ee36ef40df62ec129bee4fdb5ad9706e58a29be53b2c970/aiofiles-25.1.0.tar.gz", hash = "sha256:a8d728f0a29de45dc521f18f07297428d56992a742f0cd2701ba86e44d23d5b2", size = 46354, upload-time = "2025-10-09T20:51:04.358Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/bc/8a/340a1555ae33d7354dbca4faa54948d76d89a27ceef032c8c3bc661d003e/aiofiles-25.1.0-py3-none-any.whl", hash = "sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695", size = 14668, upload-time = "2025-10-09T20:51:03.174Z" },
+]
+
+[[package]]
+name = "aiohappyeyeballs"
+version = "2.7.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ce/f4/eec0465c2f67b2664688d0240b3212d5196fd89e741df67ddb81f8d35658/aiohappyeyeballs-2.7.1.tar.gz", hash = "sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d", size = 24757, upload-time = "2026-07-01T17:11:55.501Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/71/43/1947f06babed6b3f1d7f38b0c767f52df66bfb2bc10b468c4a7de9eceff2/aiohappyeyeballs-2.7.1-py3-none-any.whl", hash = "sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472", size = 15038, upload-time = "2026-07-01T17:11:54.055Z" },
+]
+
+[[package]]
+name = "aiohttp"
+version = "3.14.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "aiohappyeyeballs" },
+ { name = "aiosignal" },
+ { name = "attrs" },
+ { name = "frozenlist" },
+ { name = "multidict" },
+ { name = "propcache" },
+ { name = "yarl" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/58/d9/22ce5786ac0c1653ae8b6c23bded02c1686d11f0dbb45b31ce128e0df985/aiohttp-3.14.3.tar.gz", hash = "sha256:9491196535a88924a60afd5b5f434b5b203b6cc616250878dbdb223a8f7844bc", size = 7971213, upload-time = "2026-07-23T01:57:27.037Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/57/be/5afd201cc0ab139029aadb75392efe85a293403d9dd3a3226161c21ce00c/aiohttp-3.14.3-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:2e9878ae68e4a5f1c0abe4dd497dbc3d51946f5837b56759e2a02e78fa90ef86", size = 506269, upload-time = "2026-07-23T01:54:49.075Z" },
+ { url = "https://files.pythonhosted.org/packages/22/09/dec8189d62b45ade009f6792a2264b942a90cb88aeaf181239933cd72c3c/aiohttp-3.14.3-cp313-cp313-android_21_x86_64.whl", hash = "sha256:f3d2669fe7dec7fc359ecdb5984b29b50d85d5d00f8c1cb61de4f4a24ee42627", size = 515166, upload-time = "2026-07-23T01:54:51.894Z" },
+ { url = "https://files.pythonhosted.org/packages/28/24/2854869d29ed8a8b19d74f9ec6629515f7e04d02dd329d9d179201e58e47/aiohttp-3.14.3-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:cc7cb243a68167172f48c1fd43cee91ec4b1d40cefd190edd43369d1a6bc9c82", size = 486263, upload-time = "2026-07-23T01:54:54.223Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/dd/57187c8be2a35aea65eaee3bd2c3dcbbcf0204f5106c89637e3610380cd1/aiohttp-3.14.3-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:78253b573e6ffab5028924fc98bc281aae05445969982a10864bc360dea2016c", size = 492299, upload-time = "2026-07-23T01:54:56.236Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/11/06ae6ed8f0d414edf4068861e233d8fe23ee699bfd4b3ceb8663db948a62/aiohttp-3.14.3-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7041d52c3a7fa20c9e8c182b534704abb19502c8bdcbde7ab23bfda6f642394f", size = 502235, upload-time = "2026-07-23T01:54:58.377Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/a3/559639c34a345d2cf7c52dff6838119f2eaf29eb508227b5b83f573af813/aiohttp-3.14.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac74facc01463f138b0da5580329cfcc82818dea5656e83ddcd11268fc12ff80", size = 750883, upload-time = "2026-07-23T01:55:00.65Z" },
+ { url = "https://files.pythonhosted.org/packages/91/cd/41e131f13afd1e7b0172a9d9eda085ef90eb8439f41f0d279db81ed3ae60/aiohttp-3.14.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d6218d92e450824e9b4881f44e8c09f1853b490f9a64130801024a4793b1b3b0", size = 508473, upload-time = "2026-07-23T01:55:02.945Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/6b/e7f13410d391c6e55b4c007a8de024355389d7d459e3d64c42b2d33617e5/aiohttp-3.14.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:11fb37ef075669eee52ab1928fbf6e1741fada40409fa309ebde9607a962aebf", size = 509190, upload-time = "2026-07-23T01:55:05.173Z" },
+ { url = "https://files.pythonhosted.org/packages/97/21/6464573e53d69672cc1eada3e5c5cb2d2efa82701e8305a0f2047a576967/aiohttp-3.14.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55bdcc472aafe2de4a253045cc128007a64f1e0264fb675791e132ea5edaa3bd", size = 1761478, upload-time = "2026-07-23T01:55:07.383Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/81/d217043a4c17fbce360905e3b2bdd20139ebc9a2de836d035d179c4da006/aiohttp-3.14.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c39846c3aad97a8530c89d7a3869a8f8e9e3762c6ac0504481e5c80948f7e807", size = 1735092, upload-time = "2026-07-23T01:55:09.803Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/66/e13a02d0eeb1a9a502402a977abb4e4abff9fe4051c26f80558c57a7c975/aiohttp-3.14.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5895ef58c4620afe02fa16044f023dc4dafec08158f9d08874a46a7dbc0341b8", size = 1800546, upload-time = "2026-07-23T01:55:12.012Z" },
+ { url = "https://files.pythonhosted.org/packages/26/5e/57d42fca1d18cb5acc1cad945d017fabc5d6ae71d8a08ad66be8dc3ee544/aiohttp-3.14.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa9467a8113aa69d3d7c55a70ef0b7c636010a40993f3df9d9d0d73b3eb7ef24", size = 1895250, upload-time = "2026-07-23T01:55:14.357Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/1c/7da8d08e74d56f00070822f9638ff3f1c563f8ad87d1efa996c87bfc8644/aiohttp-3.14.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7d2deec16eeedf55f2c7cf75b521ea3856a5177e123844f8fd0f114ce252cb5", size = 1789289, upload-time = "2026-07-23T01:55:16.668Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/0f/cf16bcf56896981c1a0319f5d5db9337994b5165730c48a8fa07e9b34be6/aiohttp-3.14.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:dd54d0e8717de95939766febac482ac0474d8ac3b048115f9f2b1d23a16e7db4", size = 1586706, upload-time = "2026-07-23T01:55:18.913Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/6f/76eac12a7f2480e1e304f842efdb07db33256b0d9165b866b6ef0806c202/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:df82f3787c940c94986b34222d59c9e38843fba85139f36e85255a82ad5355a9", size = 1724652, upload-time = "2026-07-23T01:55:21.296Z" },
+ { url = "https://files.pythonhosted.org/packages/39/b6/19c8c592baeeb94b75f966547d40c02ac7590902306ec5863d5c027cf506/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:42a67efc36300d052fb4508a53e8b6901b9284b599ae63945c377569c5fcc1e1", size = 1756239, upload-time = "2026-07-23T01:55:23.705Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/c9/4e9383150296f97f873b680c4de8fb2cd88608fb9f48c79edcb111611abc/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7a75aa63cbf9b21cfaf60dc2657e19df2c2867d91707d653fee171ffeedd1371", size = 1769161, upload-time = "2026-07-23T01:55:26.082Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/1e/147bdc6cc5de5f3ab011be8bf5d6e786633249f22c20bae06f85e45f5387/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e92eb8acc45eb6a9f4935071a77edf5b85cc6f8dfad5cd99e97653c26593cdde", size = 1578759, upload-time = "2026-07-23T01:55:28.846Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/31/78388a9d6040ece2e11df62ea229a822cf5e52d238374b220ae9975b2623/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b014a6ed7cf912e787149fdc529166d3ceabac23f26efeea3158c9aba2354e7e", size = 1792025, upload-time = "2026-07-23T01:55:31.457Z" },
+ { url = "https://files.pythonhosted.org/packages/03/51/a3d29fdf2c25d796746af8ad6fe56a45d6256c38b0a8a2ed752e1160b3a2/aiohttp-3.14.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3d4f72af88ac2474bb5bca640030320e3d38a0163a1d7533500e87be458eef71", size = 1768477, upload-time = "2026-07-23T01:55:33.87Z" },
+ { url = "https://files.pythonhosted.org/packages/29/a6/442e18b5afeade534d877a2dc3c3e392aff8d49787890b0cf84790410267/aiohttp-3.14.3-cp313-cp313-win32.whl", hash = "sha256:5f08ec777f35ee70720233b8b9811d3bb5d728137f30ac91b7457709c3261ac0", size = 451069, upload-time = "2026-07-23T01:55:36.121Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/69/3d876ac02659f271cf7f6769f14a8e3de5b6e888ed8b5a7e998086a4cec8/aiohttp-3.14.3-cp313-cp313-win_amd64.whl", hash = "sha256:dff9461ec275f22135650d5ba4b4931a11f3958df7dfbb8db630000d4dee0883", size = 476518, upload-time = "2026-07-23T01:55:38.303Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/0e/50d6e6471cd31edce8b282bdec59375a3a69124d8a989a0b1313355cae52/aiohttp-3.14.3-cp313-cp313-win_arm64.whl", hash = "sha256:ddcac3c6b382e81f1dd0499199d4136b877beb4cb5ef770bbbfba56c4b8f55d2", size = 447676, upload-time = "2026-07-23T01:55:40.451Z" },
+]
+
+[[package]]
+name = "aiosignal"
+version = "1.4.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "frozenlist" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" },
+]
+
+[[package]]
+name = "annotated-doc"
+version = "0.0.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5a/8e/38aa427ed5402449e226975b649c5dc73ccadfefeb95e6aecb8f8ea4b6b6/annotated_doc-0.0.5.tar.gz", hash = "sha256:c7e58ce09192557605d8bbd92836d7e1d520ac9580096042c0bfd197efacf1bb", size = 10758, upload-time = "2026-07-28T13:50:58.129Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3e/30/e900b21425a860e195f32e37657aa1f7c7f2b1bfb26f03ca209b90933c06/annotated_doc-0.0.5-py3-none-any.whl", hash = "sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101", size = 5302, upload-time = "2026-07-28T13:50:57.239Z" },
+]
+
+[[package]]
+name = "annotated-types"
+version = "0.8.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" },
+]
+
+[[package]]
+name = "anyio"
+version = "4.14.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "idna" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" },
+]
+
+[[package]]
+name = "attrs"
+version = "26.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" },
+]
+
+[[package]]
+name = "av"
+version = "18.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/8d/f4/f22114d30d3435e38c6af2b4870f37b864403dca6ae7af747a289ce0a18e/av-18.1.0.tar.gz", hash = "sha256:47bfc286e1bc9de7ab4681fc2b575cd2460a66919d31ffe1bd5aa54fae531a28", size = 4451061, upload-time = "2026-08-12T22:28:18.761Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/05/d4/d7cdc8bff143c17a6d35924375ae28dd692cacde38700a7d419fde54f44a/av-18.1.0-cp311-abi3-macosx_11_0_x86_64.whl", hash = "sha256:ae75d8bb6467895ed1f8572ededf7ffa49eac07f6e483222f5d7d62a41d12f04", size = 22546147, upload-time = "2026-08-12T22:27:11.851Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/c9/37a619297492256b77d5ed906e7d8166c10a26ed251dccf1ae03ab19bff6/av-18.1.0-cp311-abi3-macosx_14_0_arm64.whl", hash = "sha256:b30a4e8d934558e19602b68998a4d9ac9f250fa0dacef216f7e8e40153b13316", size = 18217603, upload-time = "2026-08-12T22:27:14.713Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/84/2464ffb64c08c5ce8b522c8e74594714414e3b0575267652c5c51c0574b9/av-18.1.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6fc837cc51adf80331ac850779cd53b5d4c4460b0ebe9057a02a921c6736f19d", size = 33640142, upload-time = "2026-08-12T22:27:17.835Z" },
+ { url = "https://files.pythonhosted.org/packages/27/3a/204dbfc3e08eb4cdc6e6ff57be02150bc44523ebdb50182d10025792ebd9/av-18.1.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:8a032e8d8ebc73dec079364b9b4a6837638a2d106e8472314e685ffbf163e700", size = 35786210, upload-time = "2026-08-12T22:27:20.984Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/99/b0d04ec553ff9a7e00455458dfa3a39c8a8f627b273056b4e5fe57d590de/av-18.1.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:3c8b1f8b46f99d52e2d8b0ed5d0cdadf172d24794d46e2077b16e44ed08e26ff", size = 39379798, upload-time = "2026-08-12T22:27:24.432Z" },
+ { url = "https://files.pythonhosted.org/packages/56/b1/e00d4feae59160149df6126585e726fdc6300798fd40c5dd324879e81f68/av-18.1.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ab5ac081bc9eaf54109120d4e56284674fecfbe520d9aa1707c7fa911ec5f4d2", size = 34690321, upload-time = "2026-08-12T22:27:27.769Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/94/836fa987e3084d11a21489f11357fb24843ef3aa8faf74ddddfc603d5062/av-18.1.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:191224788d87af06c31784a395bb73f14b72f33d7f4871ace0157de2abdc6276", size = 36859932, upload-time = "2026-08-12T22:27:31.403Z" },
+ { url = "https://files.pythonhosted.org/packages/33/b4/76ba21e46704f632004276b85289a1582e95f5eff760436d6149875a1881/av-18.1.0-cp311-abi3-win_amd64.whl", hash = "sha256:ea1480b7a8d5405cb5f382b344731bf125fd2c1c6fae3964f6c48595628387ff", size = 27595679, upload-time = "2026-08-12T22:27:35.177Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/ad/a3135884c5753b09773176b97201ae602f67ad14206c395ff838d66bf9b0/av-18.1.0-cp311-abi3-win_arm64.whl", hash = "sha256:5509ec12aaa19fd6601de13cfa6f4cdad450da07982118510592875d970454d6", size = 20257584, upload-time = "2026-08-12T22:27:38.472Z" },
+]
+
+[[package]]
+name = "certifi"
+version = "2026.7.22"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" },
+]
+
+[[package]]
+name = "cffi"
+version = "2.1.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pycparser", marker = "implementation_name != 'PyPy'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9e/ef/008a1939e372c06329a3fce4279c02f328488f3526744906eeec3da7ad5f/cffi-2.1.1.tar.gz", hash = "sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be", size = 530807, upload-time = "2026-08-03T21:21:18.939Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9d/f4/035513d4117049066b4779dc3b7c0c0fdad175fa13731c9f4003f1cd1478/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:b5bdfd1c873d4e093aabc0ca84c4ca6dbc4f752afb5c86f146d9742580c9da2e", size = 194248, upload-time = "2026-08-03T21:19:59.399Z" },
+ { url = "https://files.pythonhosted.org/packages/76/af/2aeb4dbb5fc41a04161ae9ff1518de7cec08e164f44a8ce6a4cf7fd2cd1d/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c", size = 196908, upload-time = "2026-08-03T21:20:00.746Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/46/2e5fdde8555706dd98139a910ca11be02809f3f605ce956f655d0214e100/cffi-2.1.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:9d2055050ea716bd38b7f7f1579c275386646b4894c155a3e2f3cd62ed41b7c6", size = 184805, upload-time = "2026-08-03T21:20:02.02Z" },
+ { url = "https://files.pythonhosted.org/packages/55/41/4c7042f317b9217502988f0873af87e16ad606dc20f84e546e3e6ce9764c/cffi-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19ee6127ee34de7d83ce3d371ebc5ed91addbdcc39f9ab15ce4eb35a4e534971", size = 184764, upload-time = "2026-08-03T21:20:03.141Z" },
+ { url = "https://files.pythonhosted.org/packages/43/1f/1c3d90d91811c8f86ced9ed637956c54bfe5b79ca98fe976d7f8c8979f6b/cffi-2.1.1-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c", size = 214722, upload-time = "2026-08-03T21:20:04.377Z" },
+ { url = "https://files.pythonhosted.org/packages/37/6f/3b5ce4c3b2192d250f04908f2bfd91ef34552ec8f7716a5d4abdb8d67bb2/cffi-2.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125", size = 222369, upload-time = "2026-08-03T21:20:05.544Z" },
+ { url = "https://files.pythonhosted.org/packages/02/10/4b3c75dde3d9663c9e02ba05c2668b954f671d4bbe346413ca8c696b295a/cffi-2.1.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264", size = 210175, upload-time = "2026-08-03T21:20:06.75Z" },
+ { url = "https://files.pythonhosted.org/packages/df/62/14f74b9543e605d17701dc797b815958b8bb70b7624ce1b832ddad48ed6c/cffi-2.1.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3", size = 208670, upload-time = "2026-08-03T21:20:08.04Z" },
+ { url = "https://files.pythonhosted.org/packages/95/95/86342356ff5953b3fb06f7ef7c5bee212d45e770abc7218d451b9148313c/cffi-2.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2", size = 221824, upload-time = "2026-08-03T21:20:09.274Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/ff/7b3429ff53aafe931ed8a5fc69f481bbef7ba6de87ddcbb63d08f483f613/cffi-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b", size = 225148, upload-time = "2026-08-03T21:20:10.7Z" },
+ { url = "https://files.pythonhosted.org/packages/34/34/a95870b9221e09cf4f2ce3178b1a210abdfe63a1bd357da940418d7b8d15/cffi-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7", size = 223564, upload-time = "2026-08-03T21:20:12.165Z" },
+ { url = "https://files.pythonhosted.org/packages/70/ea/839b50531021a647fb5e929f72cf97bc1ff702b5472166164b5b6e76b851/cffi-2.1.1-cp313-cp313-win32.whl", hash = "sha256:334644fbac4eff73d985a17a91226df55d0f394160c4cfb880e084c8f7161cac", size = 175263, upload-time = "2026-08-03T21:20:13.559Z" },
+ { url = "https://files.pythonhosted.org/packages/60/a6/8b149b2c3f2e11aaa1618ef64500b45f50f22c57a977a4dff1aff1f91042/cffi-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:1aa5645c30469b09530c4ebca77ebf8f17618293c58f8549cb1a543a50236e7d", size = 185688, upload-time = "2026-08-03T21:20:14.69Z" },
+ { url = "https://files.pythonhosted.org/packages/01/9a/11f687cb39d6a3504060d5242f04f48c735afb4d3d533958a20594890cb2/cffi-2.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973", size = 180078, upload-time = "2026-08-03T21:20:15.917Z" },
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.5.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e5/3f/143b048436775b0f76ac3eec145c019e8173ccc2885c8f20319b996d5e83/charset_normalizer-3.5.1.tar.gz", hash = "sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3", size = 171764, upload-time = "2026-08-15T08:20:44.807Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/bc/61/2cb6ad133dbbb449fa2d37ccae973232f4827e799af258d15e589a3d1e9e/charset_normalizer-3.5.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:4f298bdadb8f0b9e5672877f647d1be9373ef5320c9e2f049795e26cad28b6a9", size = 211584, upload-time = "2026-08-15T08:17:33.597Z" },
+ { url = "https://files.pythonhosted.org/packages/18/57/a305c968be1ca13f3dd1b32f445877e97addf55d80b65c7cb35fac82b777/charset_normalizer-3.5.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:88ca277405c2d3b71c4e1c2ee0e7966e807bcba86a69d11e19ba199d18ae4491", size = 223359, upload-time = "2026-08-15T08:17:35.022Z" },
+ { url = "https://files.pythonhosted.org/packages/09/0a/d3646670292ce8d8f8cc11ac067d44885e697a5591f57a9221128da5e7b3/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9362dd90aa7dab48c0054a21187791ccf05473f7dba5d92b8033ae62164675e7", size = 194464, upload-time = "2026-08-15T08:17:36.452Z" },
+ { url = "https://files.pythonhosted.org/packages/de/93/d51ec556e01042fed6f993ea859311bc7917b466684182fbbceb6ca24762/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:977cdbd483a9cff38179bea4fd754289a6f2195c7abd414aba85410b3e66cc5e", size = 197676, upload-time = "2026-08-15T08:17:37.819Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/a0/562247944386f7d4ef94467e84876600cc1e0f1b93239aaa9213d2bc3cbd/charset_normalizer-3.5.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e90251c0c7bdd54a100a0dce3c07b7e637278c93af29dbf78ebb89a58c4bac7d", size = 340473, upload-time = "2026-08-15T08:17:39.303Z" },
+ { url = "https://files.pythonhosted.org/packages/31/e7/1d994be1b93d41e9502b8b0460eaa88a1dd8df335df415db87d6c3e91ab2/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94d78ecec2605a8d0398b0f365d5f12a63248438516f5dac536a5eff7337df4a", size = 240156, upload-time = "2026-08-15T08:17:40.66Z" },
+ { url = "https://files.pythonhosted.org/packages/09/53/27923ce5cc6cbccb832037b27dca98882d9c53e9b69e866bbbef4aae7fc8/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d59b75732e9b6f27388e10c14b0259cc5f2e48c78627d185e6a177b58ad3cffe", size = 228246, upload-time = "2026-08-15T08:17:42.003Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/48/5a97e84d63af1d55c07439cb80e56d99a8efb4295700eb4e18c0d1615d2c/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0d929fc574b4d6fd9e7c0f5c2ede8716a41911923aa7fa5fce38e0818aa4a1ac", size = 263660, upload-time = "2026-08-15T08:17:43.627Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/c2/071575791dcc88316c0a9a65ce38897a82e4cfe4a325f0f7fe1b1ac47bcf/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:394fea06235c8543390050ed5f529187074b029fb027213f6c46ac11ab5d950e", size = 260354, upload-time = "2026-08-15T08:17:45.094Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/af/63240b0c0248c075c2535a1f1bd992821d8251b9f173abc13329661d09e4/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62b55f6722735a6c472f88361cde6640608773d9443cebdbb51abf436a1fcdd3", size = 250638, upload-time = "2026-08-15T08:17:46.496Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/66/70dfad64f15be09c15ccfee81330a7e515895dbe296dd23114e9a231268a/charset_normalizer-3.5.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa48b1b63d639f9483e0633e092f5851e2348c352f1f9bb6c8182f87884ef876", size = 244583, upload-time = "2026-08-15T08:17:47.963Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/24/ef36367d38b9ddd4bccbf72888c342e8de1f5ae506fa0b2dcf970e2732a1/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c71fb0d56c920c269cd3e2e3fe7c610e3f1fdb21a6ce60efa6430ff63676cea6", size = 242038, upload-time = "2026-08-15T08:17:49.481Z" },
+ { url = "https://files.pythonhosted.org/packages/db/ab/55e683ba0fff2e43adafc10daa3001eac90fdaa419a97227d5a7067eedde/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:485a0d363cafefcd2538a73c7c838daa2035f09b2c9f9b5e3133f80c6aeb84c2", size = 233677, upload-time = "2026-08-15T08:17:50.845Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/67/0f40eaf8d1b6e7cf15e82382a2965efaca787fc1c2794b7021d37aaf5036/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c0ea61a470e070686aa30892fed79e297d2c8d0ab46b8bcdf027d38c51da591", size = 264491, upload-time = "2026-08-15T08:17:52.61Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/64/12b4c2a11ee8df4fcc518c78b0d93e3a92bd3d5253d1617ce74ff0e8c7ef/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:90b7481fb62fbe172c558bc6fd1c4c98d82004a54a7551f20e11ac9bf0b8708c", size = 245196, upload-time = "2026-08-15T08:17:54.023Z" },
+ { url = "https://files.pythonhosted.org/packages/37/2e/651d910af6d0fba325eee1cda37ec5443462ed25360e666c144166eb6091/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:35fe081843b35aad20ffeccec3eeffbe637b15d14f3fb22cc1b59cd8ec17e93c", size = 261660, upload-time = "2026-08-15T08:17:55.491Z" },
+ { url = "https://files.pythonhosted.org/packages/90/c6/b09e05e6db7f64338e0dc067c79577b1138da86c1e38369096851d96be88/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fd0350afdc3aabd5576f60ea109228bd5538139713c7b094c5cd27c73a98bc6f", size = 252618, upload-time = "2026-08-15T08:17:57.025Z" },
+ { url = "https://files.pythonhosted.org/packages/76/4e/362d4f9fdcdf5556fb2aa3ce7d4a58ebce03ed1ff03aa1d9aca8d02f13f3/charset_normalizer-3.5.1-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:9d9a0dc7cbe9bec24c3f767c9122c41fe5a1bc43f47cd099d00d393e09769de4", size = 140362, upload-time = "2026-08-15T08:17:58.425Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/d4/703be739b26acce318bd29eb3b25b7209e1b1f527f9eae3d1f1f01fdde2b/charset_normalizer-3.5.1-cp313-cp313-win32.whl", hash = "sha256:d63600d620ad0064c3a748b950ac5ea38a80190e5498532efefa4b7b3f1da1f3", size = 177755, upload-time = "2026-08-15T08:18:00.037Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/33/56d97ade41c8db611e727168c52ae46c9224c362ec28d4b65d7e9869e8da/charset_normalizer-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:aea996a6aba25260827c9ea511d1addfde2da9eb686ac961838509086188b7e6", size = 199295, upload-time = "2026-08-15T08:18:01.506Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/75/5b20dd1e6573a01a08158fe104104fa2c8abf941745596954185726cd46c/charset_normalizer-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:fd0a274c0e5f9a21565cd9d3dd749b61f96b7aa1e20a93aa1ba4029518f2e5c0", size = 179856, upload-time = "2026-08-15T08:18:02.929Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/97/fb4e82231aba271ffd775a1b4993b0defc4e3059f286ae41d9433409fe85/charset_normalizer-3.5.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2", size = 331467, upload-time = "2026-08-15T08:19:50.959Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/2f/fe3f187327aac18e2d54e9d2b08e15d27bf9b642d9e51c219f130fc34d1a/charset_normalizer-3.5.1-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99", size = 253057, upload-time = "2026-08-15T08:19:52.654Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/c7/9e48cee5c161fe24da823b61bf381921d77cb994a0a4de148e95018c1984/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2", size = 240930, upload-time = "2026-08-15T08:19:54.163Z" },
+ { url = "https://files.pythonhosted.org/packages/49/e0/716601f3cc69be7b198951150c75ead1ece33c3c8036ff6ffa46029659a0/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:343fb4f2821043bd87095f7b08a1a181febc8e36ac64212143bbfd0a0e1bc235", size = 230822, upload-time = "2026-08-15T08:19:55.807Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/05/71bfc5caa0abcc45aea1f6a4d50ac68e59605ddc7666fe8494f4cd229665/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ae4a097991662cd4fff0ddc74e0fe7874f82e00042fa0ea00855645ed0c79598", size = 260037, upload-time = "2026-08-15T08:19:57.312Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/92/de7e32ed05341e7a9c4c877c318418197b7f2d66a3b68d561bf2ac57ca3e/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b599739b93b2cbeded49645ae3c8d1405c29ddfbceac1545c87a3f9580a9e96", size = 255097, upload-time = "2026-08-15T08:19:59.056Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/7b/ade0a122600319dfa0b1000ab0f9731c94a817904cf3c5de408c73a4ede7/charset_normalizer-3.5.1-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b39b69b347e5e47a3b5b8cfc005c68c1ba347474e3960236c4944a8ecd174962", size = 250166, upload-time = "2026-08-15T08:20:00.612Z" },
+ { url = "https://files.pythonhosted.org/packages/75/9c/019fbb9f4834491a160951349b1a3714439376f66e5f7cf18b4f18f0c7aa/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a2028475ba855475b8b4d3cfeb4994269c967aea8b9892dfba907f4263a863a3", size = 241821, upload-time = "2026-08-15T08:20:02.321Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/b8/11d4840bfc99330cc7fbcc2681ee5a044553a6e77655508d8f9b2bff7b34/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:36047af20e17097c3bb9476c2b7655f2f7aa51322c0ba58c07695bedf755a950", size = 232529, upload-time = "2026-08-15T08:20:04.008Z" },
+ { url = "https://files.pythonhosted.org/packages/18/96/2b3a21492d9f65171ac75d872f5018260013d00bfa0ff70ec9f179148cbd/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4c4fb141a727957c93edfe5c32a26ceb6b5f6461d67146e2d39f51e16170bea8", size = 260348, upload-time = "2026-08-15T08:20:05.877Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/aa/a69a2028e8bd052476c245460ab19d7de595de084dd968f2d75cd50c3e25/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:2f293479cce755c75f1697e87c409b7ae4c555c7dfecb6e988ad13abba943031", size = 247234, upload-time = "2026-08-15T08:20:07.487Z" },
+ { url = "https://files.pythonhosted.org/packages/35/8a/3d130aeabcaf3d2466af76b7b141c08d9e89c9016ab4b7cdd0f7dc2d1c62/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:3588e376b3ea2eea84976f67273d679f229e24c66dce7b82ae45aef04ff6e072", size = 256917, upload-time = "2026-08-15T08:20:09.142Z" },
+ { url = "https://files.pythonhosted.org/packages/80/c2/a7379b840292d0c1ab9fbd17d1f3967aa81794dc95bc74be8999d7fedcf7/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e199fb99720074809a7720f1c0b4d919eea8b87e88713e0f8f602f7bef543d9d", size = 254846, upload-time = "2026-08-15T08:20:10.727Z" },
+ { url = "https://files.pythonhosted.org/packages/01/65/d43b714731bb2f40d4053dfa00ecfc1c5a301f8e3316c5db3a09af59fe94/charset_normalizer-3.5.1-cp37-abi3-win32.whl", hash = "sha256:dd732602a7009217f658d5863d12d79d373a4de0eebc111094bcdd3bb8e0a6cc", size = 174216, upload-time = "2026-08-15T08:20:12.334Z" },
+ { url = "https://files.pythonhosted.org/packages/35/4f/b911ed898b26a09789eba9c9200c999aff6c61b4bafaf4838e56d1a1e1a3/charset_normalizer-3.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:70055ff39b97c99e7ae40ea3e393fb62aa2e44dbd9b29f8d14f42fb0025c3959", size = 199764, upload-time = "2026-08-15T08:20:13.908Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/a7/920baf467bfd9bf689f3b318340f37aee4572a71f162bd8db51da55ba4fa/charset_normalizer-3.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:87e4f41d375c0b9be2fb5251aee4b8a689169e134535aed81bf085c3b647451e", size = 287318, upload-time = "2026-08-15T08:20:15.551Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/61/d01fc49b8dea277640b55a9e15960dbca9fdc8c9fde18e572d39c59f4019/charset_normalizer-3.5.1-py3-none-any.whl", hash = "sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6", size = 68658, upload-time = "2026-08-15T08:20:43.306Z" },
+]
+
+[[package]]
+name = "click"
+version = "8.4.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" },
+]
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
+]
+
+[[package]]
+name = "distro"
+version = "1.9.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" },
+]
+
+[[package]]
+name = "docstring-parser"
+version = "0.18.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e0/4d/f332313098c1de1b2d2ff91cf2674415cc7cddab2ca1b01ae29774bd5fdf/docstring_parser-0.18.0.tar.gz", hash = "sha256:292510982205c12b1248696f44959db3cdd1740237a968ea1e2e7a900eeb2015", size = 29341, upload-time = "2026-04-14T04:09:19.867Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a7/5f/ed01f9a3cdffbd5a008556fc7b2a08ddb1cc6ace7effa7340604b1d16699/docstring_parser-0.18.0-py3-none-any.whl", hash = "sha256:b3fcbed555c47d8479be0796ef7e19c2670d428d72e96da63f3a40122860374b", size = 22484, upload-time = "2026-04-14T04:09:18.638Z" },
+]
+
+[[package]]
+name = "eval-type-backport"
+version = "0.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1c/15/273a4baf8248d6d76220723c3caf039d283774b31a7c46ba686120145b76/eval_type_backport-0.4.0.tar.gz", hash = "sha256:8397d25e6524c2e67b9576bb0636be27dea2192017711220c534ec2de921e9b0", size = 10260, upload-time = "2026-06-02T13:22:06.059Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/50/a7/bb99bf5e6f78736ddb53480f2c3ff3702ffe2196a7c5e1661c03081d398e/eval_type_backport-0.4.0-py3-none-any.whl", hash = "sha256:ad5e2a8db71b6696a56eafb938b0f5a337d3217f256b8e158b469422b4772b20", size = 6432, upload-time = "2026-06-02T13:22:04.827Z" },
+]
+
+[[package]]
+name = "fastapi"
+version = "0.141.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "annotated-doc" },
+ { name = "pydantic" },
+ { name = "starlette" },
+ { name = "typing-extensions" },
+ { name = "typing-inspection" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/8a/02/91e3416a8fdd715abb903a952a6bec7cdd8d14eed55d415fc8595524c319/fastapi-0.141.1.tar.gz", hash = "sha256:e8822fc40db1e1858054d7a949a888695bc9bdce70139178e33bd2871a453ca1", size = 425799, upload-time = "2026-07-29T17:18:05.568Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/03/10388a42375ee7e4ac9b94eb2c5c569c8b5795e377e701c9ac3ad63de890/fastapi-0.141.1-py3-none-any.whl", hash = "sha256:bfb91aa2d334c61cb35ba9a116fc123b3d3df31640b801cf57a7a78ec3f603b3", size = 131954, upload-time = "2026-07-29T17:18:04.364Z" },
+]
+
+[[package]]
+name = "flatbuffers"
+version = "25.12.19"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e8/2d/d2a548598be01649e2d46231d151a6c56d10b964d94043a335ae56ea2d92/flatbuffers-25.12.19-py2.py3-none-any.whl", hash = "sha256:7634f50c427838bb021c2d66a3d1168e9d199b0607e6329399f04846d42e20b4", size = 26661, upload-time = "2025-12-19T23:16:13.622Z" },
+]
+
+[[package]]
+name = "frozenlist"
+version = "1.8.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" },
+ { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" },
+ { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" },
+ { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" },
+ { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" },
+ { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" },
+]
+
+[[package]]
+name = "googleapis-common-protos"
+version = "1.75.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "protobuf" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/72/73/74bcab964c9a7a61f2bb71e8179b0f13e6fa98f7ce00fd168aab291e4a2e/googleapis_common_protos-1.75.1.tar.gz", hash = "sha256:d3042c6c5a2d4e67113104d6b6818b59b6bd92a197f2a91508e801fe815cf071", size = 150967, upload-time = "2026-08-06T06:24:51.972Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9a/51/186c02b8549b69ccda44429cf6ff5081e4b61a602ddfe6a8020d1be31d1b/googleapis_common_protos-1.75.1-py3-none-any.whl", hash = "sha256:28a1934bcd33b9c9da66ac301a0a4227e3367f095a17d0375cb98f0a09d93b79", size = 300626, upload-time = "2026-08-06T06:23:46.696Z" },
+]
+
+[[package]]
+name = "grpcio"
+version = "1.83.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0c/98/304898ac4e04e2d5e4e4c2eadc178b1f2a16d5f4bc2f91306c87d64680b9/grpcio-1.83.0.tar.gz", hash = "sha256:7674587248fbbb2ac6e4eecf83a8a0f3d91a928f941de571acfd3a2f007fbc24", size = 13428824, upload-time = "2026-07-23T15:20:37.759Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/56/eb/135daaa713f32d33b8f99b4153b3f8dc3b2a124996ac15581bf9ebdad3c3/grpcio-1.83.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:6662f3b1e07cc7493d437351860dc867bddc6a93c83ecf33bbfdaf0c217ab2d0", size = 6304480, upload-time = "2026-07-23T15:19:53.962Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/a1/121806ce69f23138dabe06aa595b0e5f1ae051a37e4c1954eed7d692c800/grpcio-1.83.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:74fe6f9e8a35c7dbf32255ee154d15e3e5338a81ed39173d079d594d2e544cd1", size = 12154419, upload-time = "2026-07-23T15:19:56.3Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/e8/d0389e09cd6b4c4d3089b92967ae4e3ffd64795bd349bf2f85cd6656d3da/grpcio-1.83.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:10b3fa0475eb572c9a81a6fe37fa16a9c500c0c91cfc148cac15692b7e3c2867", size = 6873200, upload-time = "2026-07-23T15:19:58.701Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/51/f464c1d211fa50d5adbabe1b2e519948d99c13757052bfc9ea7afa28e284/grpcio-1.83.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:5f20a988480b0f28207f057f7f7ae1313393c3cef0adcfeae8248f9947eaf881", size = 7618811, upload-time = "2026-07-23T15:20:00.733Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/c0/539fe0832f2dd6500a28f5263071623fb34e8d4867aec632ccf81bd21156/grpcio-1.83.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7bd82671b39065ba18cd536e9cd45b27ff649053f81ddd2c6a966d595067080f", size = 7042310, upload-time = "2026-07-23T15:20:02.675Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/ca/ccf617d37ffa72567fa8e005ec7090c99da922799be2fb9847c8b21ca18c/grpcio-1.83.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc60215b5cb9fc8ca72942c498b551ac2305bd08f6ef8d4e3f0d21b64fbecd61", size = 7575412, upload-time = "2026-07-23T15:20:04.712Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/b9/fd8d5245f823a8e0fd35d90e20ea3aa4acd47f8d5318fa8df307df52dec6/grpcio-1.83.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f1c3e5689d4b90987b1d72022bcfe866a9a3dc66197484cf856d96b6150e7f45", size = 8604248, upload-time = "2026-07-23T15:20:06.77Z" },
+ { url = "https://files.pythonhosted.org/packages/14/1e/f37632fc11db72dfa4bba86c3a43e54358e53030df111ecae5e91a733ad6/grpcio-1.83.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a21cb4eeeba124443f399be2e8b624943cde864dcbe588cb42e5c483a52a906c", size = 7977458, upload-time = "2026-07-23T15:20:09.109Z" },
+ { url = "https://files.pythonhosted.org/packages/93/b6/d70b69ae5c0cfc341b9ba474980e4ed99cbf05c0e4a14e9eee8cb73db0a5/grpcio-1.83.0-cp313-cp313-win32.whl", hash = "sha256:8fe04f1050a59f875601eb55d42b4f66946fe89817f967e34db1462ccd07dadf", size = 4393993, upload-time = "2026-07-23T15:20:11.017Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/13/45d4cccb555cf4c476226979bf3d2fd0b0254216f7564c3a053e35117efc/grpcio-1.83.0-cp313-cp313-win_amd64.whl", hash = "sha256:6e01ecd9d8ef280abe1365138a4dc318f9a5287f4cb1b41d07816f796653f735", size = 5159650, upload-time = "2026-07-23T15:20:12.979Z" },
+]
+
+[[package]]
+name = "h11"
+version = "0.16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
+]
+
+[[package]]
+name = "httpcore"
+version = "1.0.9"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "certifi" },
+ { name = "h11" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
+]
+
+[[package]]
+name = "httptools"
+version = "0.8.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/43/e5/d471fcb0e14523fe1c3f4ba58ca52480e7bd70ad7109a3846bc75892f7fb/httptools-0.8.0.tar.gz", hash = "sha256:6b2a32f18d97e16e90827d7a819ffa8dbd8cc245fc4e1fa9d1095b54ef4bd999", size = 271342, upload-time = "2026-05-25T22:17:48.841Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5e/e5/8cfcabc5546e8022f168be28bcdaa128a240a0befdd03b59d558b4f18bd6/httptools-0.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:614ceea8ea606848bece2338ac03b3ce5324bcb4be8dc7d377ed708012fa4db8", size = 205148, upload-time = "2026-05-25T22:17:16.333Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/0e/0fb14848c19a686c8062ff9067c1a48793e3224b47bc5b201535b6036fce/httptools-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2d689918c15a013c65ef52d9fd495d766893ab831a2c8d89f2ac5940a5df847c", size = 111368, upload-time = "2026-05-25T22:17:17.586Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/1b/46f1cecf06b9bbde8e4b8c88034ac7908989e5ff7a3a388ef38392949c1f/httptools-0.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:eb3028cca2fc0a6d720e52ef61d8ebb62fcbfeb1de56874546d858d3f25a26b7", size = 486447, upload-time = "2026-05-25T22:17:18.564Z" },
+ { url = "https://files.pythonhosted.org/packages/77/00/258bfc0837221f81d9725c45f9b948a6a6b2994a147a4fb66e85100c668f/httptools-0.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88bdd940f2b5d487b4d032c6afa5489a7dc4694410d43de3c38c4fb3af0dc45d", size = 482448, upload-time = "2026-05-25T22:17:19.912Z" },
+ { url = "https://files.pythonhosted.org/packages/04/ab/d1cef3b5523f4d272a70f42a776c3169a2dddfe3a54de4b2ce4a36341528/httptools-0.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a43c9dd399758ccc0531acb0a3c4a6c299ee893ee9400e9c893b7bdcfae0681", size = 464460, upload-time = "2026-05-25T22:17:20.882Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/48/5d1d072442277bb2b3434e0e60690b8e8c23840ef7de8b6ea54040a536d3/httptools-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0770728beb05094c809b98e814edff5fef69d26ad7d21185f2f6d5884a0ba683", size = 471312, upload-time = "2026-05-25T22:17:22.085Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/66/b96623b27e51a68199ef4efdda0613cced9233fe3062ac74e50749c5ad37/httptools-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:7685df791fad561384bfb139e77fde27a1ffd93134e016f95a0db424ffbf77b1", size = 90117, upload-time = "2026-05-25T22:17:23.074Z" },
+]
+
+[[package]]
+name = "httpx"
+version = "0.28.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+ { name = "certifi" },
+ { name = "httpcore" },
+ { name = "idna" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
+]
+
+[[package]]
+name = "idna"
+version = "3.19"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5f/f7/abb373e5757eaec4b922b92f97ec8d6d7e057cf06778247604fbc4e7c3f3/idna-3.19.tar.gz", hash = "sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15", size = 215237, upload-time = "2026-08-18T05:14:24.27Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/57/b0/0e52c878c53f245edd3a11020f20979b3f490f245af532c7cae3027754b5/idna-3.19-py3-none-any.whl", hash = "sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4", size = 68550, upload-time = "2026-08-18T05:14:22.343Z" },
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
+]
+
+[[package]]
+name = "jiter"
+version = "0.16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1d/1f/10936e16d8860c70698a1aa939a46aa0224813b782bce4e000e637da0b2d/jiter-0.16.0.tar.gz", hash = "sha256:7b24c3492c5f4f84a37946ad9cf504910cf6a782d6a4e0689b6673c5894b4a1c", size = 176431, upload-time = "2026-06-29T13:05:13.657Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/91/c0/555fc60473d30d66894ba825e63615e3be7524fac23858356afa7a38906c/jiter-0.16.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:41977aa5654023948c2dae2a81cbf9c43343954bef1cd59a154dd15a4d84c195", size = 306203, upload-time = "2026-06-29T13:03:36.243Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/2b/c3eaf16f5d7c9bad66ea32f40a95bd169b29a91217fcc7f081375157e99c/jiter-0.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d28bb3c26762358dadf3e5bf0bccd29ae987d65e6988d2e6f49829c76b003c09", size = 306489, upload-time = "2026-06-29T13:03:37.846Z" },
+ { url = "https://files.pythonhosted.org/packages/96/3f/02fdfc6705cad96127d883af5c34e4867f554f29ec7705ec1a46156400a9/jiter-0.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0542a7189c26920778658fc8fcf2af8bae05bae9924577f71804acef37996536", size = 335453, upload-time = "2026-06-29T13:03:39.221Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/a6/e4bda5920d4b0d7c5dfb7174ce4a6b2e4d3e11c9162c452ef0eab4cdbdbd/jiter-0.16.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8fb8de1e23a0cb2a7f53c335049c7b72b6db41aa6227cdcc0972a1de5cb39450", size = 361625, upload-time = "2026-06-29T13:03:40.597Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/97/4e6b59b2c6e55cbb3e183595f81ad65dcfb21c915fee5e19e335df21bc55/jiter-0.16.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b72d0b2990ca754a9102779ac98d8597b7cb31678958562214a007f909eab78e", size = 456958, upload-time = "2026-06-29T13:03:42.074Z" },
+ { url = "https://files.pythonhosted.org/packages/15/e0/97e9557686d2f94f4b93786eccb7eed28e9228ad132ea8237f44727314a7/jiter-0.16.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5f91b1c27fc22a57993d5a5cb8a627cb8ed4b10502716fac1ffbfe1d19d84e8", size = 372017, upload-time = "2026-06-29T13:03:43.658Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/94/db768b6938e0df35c86beeba3dfbbb025c9ee5c19e1aa271f2396e50864d/jiter-0.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c682bea068a90b764577bdb78a60a4c1d1606daf9cd4c893832a37c7cc9d9026", size = 343320, upload-time = "2026-06-29T13:03:45.226Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/d6/5a59d938244a30735fe62d9433fd325f9021ea29d89780ea4596ea93bc89/jiter-0.16.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:8d031aabecc4f1b6276adfb42e3aabb77c89d468bf616600e8d3a11328929053", size = 350520, upload-time = "2026-06-29T13:03:46.671Z" },
+ { url = "https://files.pythonhosted.org/packages/67/f8/c4a857f49c9af125f6bbcac7e3eee7f7978ed89682833062e2dbf62576b1/jiter-0.16.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eab2cd170150e70153de16896a1774e3a1dca80154c56b54d7a812c479a7165e", size = 387550, upload-time = "2026-06-29T13:03:48.361Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/d6/5fbc2f7d6b67b754caa61a993a2e626e815dec47ffc2f9e35f01adfebec7/jiter-0.16.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:6edb63a46e65a82c26800a868e49b2cac30dd5a4218b88d74bc2c848c8ad60bb", size = 515424, upload-time = "2026-06-29T13:03:49.881Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/54/284f0164b64a5fed915fea6ba7e9ba9b3d8d37c67d59cf2e3bb99d45cdfe/jiter-0.16.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:659039cc50b5addcc35fcc87ae2c1833b7c0a8e5326ef631a75e4478447bcf84", size = 546981, upload-time = "2026-06-29T13:03:51.363Z" },
+ { url = "https://files.pythonhosted.org/packages/13/c5/2a467585a576594384e1d2c43e1224deaafc085f24e243529cf98beef8e1/jiter-0.16.0-cp313-cp313-win32.whl", hash = "sha256:c9c53be232c2e206ef9cdbad81a48bfa74c3d3f08bcf8124630a8a748aad993e", size = 202853, upload-time = "2026-06-29T13:03:53.015Z" },
+ { url = "https://files.pythonhosted.org/packages/88/6a/de61d04b9eec69c71719968d2f716532a3bc121170c44a39e14979c6be81/jiter-0.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:baad945ed47f163ad833314f8e3288c396118934f94e7bbb9e243ce4b341a4fd", size = 196160, upload-time = "2026-06-29T13:03:54.447Z" },
+ { url = "https://files.pythonhosted.org/packages/19/4b/b390ed59bafb3f31d008d1218578f10327714484b334439947f7e5b11e7f/jiter-0.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:3c1fd2dbe1b0af19e987f03fe66c5f5bd105a2229c1aff4ab14890b24f41d21a", size = 189862, upload-time = "2026-06-29T13:03:55.754Z" },
+]
+
+[[package]]
+name = "json-repair"
+version = "0.60.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5e/a6/d69888cb4ffde30e80db1e6c32caaadd2f984a80067d5ea72c2cb3f61c3f/json_repair-0.60.1.tar.gz", hash = "sha256:841661cdd2df507c9a4e189097f38ca6bc372e06d4b4e36d72e590f68176c290", size = 49451, upload-time = "2026-06-03T17:28:44.451Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/32/1f/2a2b5eea8ef5762a86ad3f8fddddaaba2c0d76dd44e644b9158900868bec/json_repair-0.60.1-py3-none-any.whl", hash = "sha256:ba6ff974f2a8bef2f7768144a7f03f870a816443f03da27a49cdd0ec31a78049", size = 48045, upload-time = "2026-06-03T17:28:43.038Z" },
+]
+
+[[package]]
+name = "jsonschema"
+version = "4.26.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "attrs" },
+ { name = "jsonschema-specifications" },
+ { name = "referencing" },
+ { name = "rpds-py" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" },
+]
+
+[[package]]
+name = "jsonschema-specifications"
+version = "2025.9.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "referencing" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" },
+]
+
+[[package]]
+name = "livekit"
+version = "1.1.14"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "aiofiles" },
+ { name = "numpy" },
+ { name = "protobuf" },
+ { name = "types-protobuf" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ab/5d/bfaf1cc73f960b40294f604d334f05e628b0a07de3c47e475d760996a8d0/livekit-1.1.14.tar.gz", hash = "sha256:47428e10ecf20d7db4ee9fde4009bf96578c003b1ae6e1c5e7e4837a55902393", size = 375000, upload-time = "2026-07-31T14:05:14.425Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7a/ff/a2659522b3cf860b9b4453e1ec12d4b4c7e9cfd2b672f2cf925016d73492/livekit-1.1.14-py3-none-macosx_10_15_x86_64.whl", hash = "sha256:5f671b1752c93b878cb241b84fd3f72a31f857c3927755d672cfb7656a84778c", size = 10196322, upload-time = "2026-07-31T14:05:04.167Z" },
+ { url = "https://files.pythonhosted.org/packages/82/a2/89f32d369cc78cb1a50b2a9e635c653f88d86ea4338ccdfa7b2d4ca0aecd/livekit-1.1.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:efa16b9036b0b592e5399fdb858c1f04ec8a32c385184c705f030952f72174e8", size = 9019745, upload-time = "2026-07-31T14:05:06.49Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/5b/dda7d660fa5d5b6e228dcfc6be3664a2442d1601481686052af2da642e5e/livekit-1.1.14-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:299146efefad5f67751cd15b8225bae759be0d7ad2f0b4ae1a22c15860d93cf9", size = 10042499, upload-time = "2026-07-31T14:05:08.563Z" },
+ { url = "https://files.pythonhosted.org/packages/21/e3/d9255eeaf205f090d63d762e5254097b62af394bfaa90106f71f1fb6740e/livekit-1.1.14-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:80962c4a22ddbf0e0ebd3563fc090fce42df66b39b90de68b161b7db01970f68", size = 11445915, upload-time = "2026-07-31T14:05:10.628Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/0a/514fb230e7c7f13ae7e53b9e39a6dd9ea1aa9ff5be9e588d55301d159a1e/livekit-1.1.14-py3-none-win_amd64.whl", hash = "sha256:b8f8d38f131956297923e520bc4375bc9ebfa255cab7f125cb7755bfca71df24", size = 10766643, upload-time = "2026-07-31T14:05:12.716Z" },
+]
+
+[[package]]
+name = "livekit-agents"
+version = "1.7.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "aiofiles" },
+ { name = "aiohttp" },
+ { name = "av" },
+ { name = "certifi" },
+ { name = "click" },
+ { name = "colorama" },
+ { name = "docstring-parser" },
+ { name = "eval-type-backport" },
+ { name = "json-repair" },
+ { name = "livekit" },
+ { name = "livekit-api" },
+ { name = "livekit-blingfire" },
+ { name = "livekit-local-inference" },
+ { name = "livekit-protocol" },
+ { name = "nest-asyncio" },
+ { name = "numpy" },
+ { name = "openai" },
+ { name = "opentelemetry-api" },
+ { name = "opentelemetry-exporter-otlp" },
+ { name = "opentelemetry-sdk" },
+ { name = "prometheus-client" },
+ { name = "protobuf" },
+ { name = "psutil" },
+ { name = "pydantic" },
+ { name = "pyjwt" },
+ { name = "pyyaml" },
+ { name = "sounddevice" },
+ { name = "typer" },
+ { name = "types-protobuf" },
+ { name = "typing-extensions" },
+ { name = "watchfiles" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/3c/25/242c1e0fcaef5486be838b8535401baa8ffea6cbe1be5c9a8ab0f367e1f7/livekit_agents-1.7.0.tar.gz", hash = "sha256:3cc8ec39ed0c63f09d94cb170d95284f4a2fd63ebe608cb5f40c5b6e08a7d52a", size = 2659034, upload-time = "2026-08-20T18:16:20.231Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a0/20/f83b5b89046514b80502b52f2a6ce70a8ce14d32902c499c385d2b1595d6/livekit_agents-1.7.0-py3-none-any.whl", hash = "sha256:416d73e7c9ae85d118b4feb9b9c5432d26c134abb537139bc44e0f9faa79ac11", size = 2773768, upload-time = "2026-08-20T18:16:17.744Z" },
+]
+
+[package.optional-dependencies]
+codecs = [
+ { name = "numpy" },
+]
+images = [
+ { name = "pillow" },
+]
+openai = [
+ { name = "livekit-plugins-openai" },
+]
+silero = [
+ { name = "livekit-plugins-silero" },
+]
+
+[[package]]
+name = "livekit-api"
+version = "1.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "aiohttp" },
+ { name = "livekit-protocol" },
+ { name = "protobuf" },
+ { name = "pyjwt" },
+ { name = "types-protobuf" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f3/19/36ff6712ec638a4b7dad4d8f03795952e401dc31db0b04cddec7892650da/livekit_api-1.2.0.tar.gz", hash = "sha256:a89817b3bca9584873786ff07209839308217537a42f95ecb2609aafaa109ddc", size = 20778, upload-time = "2026-07-11T23:20:54.781Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/bf/e7/8926f16d4bc1b2e0ae46d4a507321bb899396d263a757f1adaabcd3b3867/livekit_api-1.2.0-py3-none-any.whl", hash = "sha256:307f8e5cfb0358c3ca091814ab768af55896022151bcd7f951954ccefa036a24", size = 26499, upload-time = "2026-07-11T23:20:53.736Z" },
+]
+
+[[package]]
+name = "livekit-blingfire"
+version = "1.1.0"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/81/50/46e410b935154a6bcf2d9494ee8e298b1a9c91ae33beaa78346703cf7681/livekit_blingfire-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f5f6a40e498940f5b2e53d9753f5f7fb7f909e12a93a158844c9e3e99a5486b8", size = 154623, upload-time = "2025-12-16T00:48:17.641Z" },
+ { url = "https://files.pythonhosted.org/packages/de/b4/f51c25bf104e51703dc66558ff9831a9769a9effa397956268902784a3d0/livekit_blingfire-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:945a672a224c9a686925e9af94c2660bacdbe190ccf693d6f17cea9359426c15", size = 148846, upload-time = "2025-12-16T00:48:18.591Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/d2/ad95d195ed6dccb6527ed3c1e753f211c3e9509050af5cddf007608bb104/livekit_blingfire-1.1.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f3aac3207cdd88c62323e0b07c33a69aac79c544122a2ddfbecc6c721ca760c", size = 167886, upload-time = "2025-12-16T00:48:19.858Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/67/fc4af1bbbed319d8edc319051bce720b51fa544f5d2ebb3201240779f135/livekit_blingfire-1.1.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:839feefa2910f99d794d3f3d696f95193ee8188cc6688a8d712bade2cede7951", size = 175858, upload-time = "2025-12-16T00:48:21.144Z" },
+ { url = "https://files.pythonhosted.org/packages/76/6c/9e14763826476925767b511531318a83f95f3bf9e4dbc7dc611400af6e9e/livekit_blingfire-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:1409d4c297260b60a37bfe6ba21e4fb59dd53cd929632c0a78a28d41fe424302", size = 131048, upload-time = "2025-12-16T00:48:22.17Z" },
+]
+
+[[package]]
+name = "livekit-local-inference"
+version = "0.2.7"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a2/ff/01233367f526c67df021d5ee5ad0e7d229553ad7e90d5ae02d5afbdc7abb/livekit_local_inference-0.2.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5b23b2fca99fbf05d349b8c1c1e499d9154997214db15b6784a999783f63169a", size = 34839008, upload-time = "2026-08-18T09:46:50.374Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/45/9c70db9dc4581d9f2eecc04386bba3c8e74b6f438d2d6acb11aeaf66f96e/livekit_local_inference-0.2.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:866036cf42fce282404ecdad90bb2b814bc78aab245bc7be740e3cff528a36e8", size = 35096385, upload-time = "2026-08-18T09:46:53.359Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/7d/0a981a4c7504fc4323a277d04705799fa48fbc036d368e47d5780c737341/livekit_local_inference-0.2.7-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f304cca187033b257cc8baf67f8799f5f467fa5f5984cdc3948591eb2027761b", size = 34835081, upload-time = "2026-08-18T09:46:55.883Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/cc/858e2792eba28aa3baae1939488319f3bbef38c911cc0640f020bb0fe708/livekit_local_inference-0.2.7-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:454c451a4df153f5a9c8c7ba20e842dd5c77993103fd1c03194856d351be87dd", size = 34876656, upload-time = "2026-08-18T09:46:58.567Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/07/b85d8f18fd46f335a559f6d39f8acf07663510d1add19d1f31814ef7daf0/livekit_local_inference-0.2.7-cp313-cp313-win_amd64.whl", hash = "sha256:c16e86495346d8c349910ac8530de1e3532a6d1bac95ece0bc416c88f2a5c20f", size = 34877068, upload-time = "2026-08-18T09:47:01.317Z" },
+]
+
+[[package]]
+name = "livekit-plugins-executorch"
+source = { editable = "packages/livekit-plugins-executorch" }
+dependencies = [
+ { name = "livekit-agents" },
+]
+
+[package.dev-dependencies]
+dev = [
+ { name = "pytest" },
+ { name = "pytest-asyncio" },
+ { name = "ruff" },
+]
+
+[package.metadata]
+requires-dist = [{ name = "livekit-agents", specifier = ">=1.6.9,<2" }]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "pytest", specifier = ">=8.4,<9" },
+ { name = "pytest-asyncio", specifier = ">=0.25,<2" },
+ { name = "ruff", specifier = ">=0.12,<1" },
+]
+
+[[package]]
+name = "livekit-plugins-openai"
+version = "1.7.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "livekit-agents", extra = ["codecs", "images"] },
+ { name = "openai", extra = ["realtime"] },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/bf/d2/6a4f7f8b0cea084bbb750572b2722d8e9fccd651fe14b58de77ad1871371/livekit_plugins_openai-1.7.0.tar.gz", hash = "sha256:c388cd3e23c0ea1527d280c2c71b3d277de059395959eac4a84aecc2f5a56307", size = 54202, upload-time = "2026-08-20T18:17:48.833Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ad/99/7985693a4b0da0f431c51b10670f4a34fc0db6a4080f581ccf8657bb2899/livekit_plugins_openai-1.7.0-py3-none-any.whl", hash = "sha256:bde4c38eaacde216b3f9228d23237c1f74fe6235023c6497142c5fef4637e085", size = 59991, upload-time = "2026-08-20T18:17:47.627Z" },
+]
+
+[[package]]
+name = "livekit-plugins-silero"
+version = "1.7.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "livekit-agents" },
+ { name = "numpy" },
+ { name = "onnxruntime" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/87/34/388ba8cb1db86839228e88b799776d74db2286dbd2289ded744ec6b7d8dc/livekit_plugins_silero-1.7.0.tar.gz", hash = "sha256:f18aa1e980bc6892613a906ebfacd058160db3fb6db1dcd2a7b9d809dc9c0492", size = 1955425, upload-time = "2026-08-20T18:18:10.686Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2d/e1/5db37c5ca78bd011938f8d60ed6af984e61cd076a4535c56287cfb0d256b/livekit_plugins_silero-1.7.0-py3-none-any.whl", hash = "sha256:ce29626179d6a474b6b7fd6eb4dddb81e23a3a6a512e0013e8e3d07fda798e54", size = 3903143, upload-time = "2026-08-20T18:18:08.947Z" },
+]
+
+[[package]]
+name = "livekit-protocol"
+version = "1.1.24"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "protobuf" },
+ { name = "types-protobuf" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d7/dc/65c106d689916e78e47d79d4f890f6e83b680442c67ed4909b44425084ea/livekit_protocol-1.1.24.tar.gz", hash = "sha256:b0a5699d3a4c4e42c3d37416dc3ed3c817c527c317c4cf3351d0bbe52887b9d4", size = 122624, upload-time = "2026-08-20T22:07:24.06Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/88/1f/e06cbbaea37bbe12cab1d169767f3fff9b385b019e6bdcd02fb9c94df63d/livekit_protocol-1.1.24-py3-none-any.whl", hash = "sha256:794463c4ed209fc884194470595052de652477d92b10d81bb5b0f9dcf53050f9", size = 149448, upload-time = "2026-08-20T22:07:22.699Z" },
+]
+
+[[package]]
+name = "markdown-it-py"
+version = "4.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "mdurl" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" },
+]
+
+[[package]]
+name = "mdurl"
+version = "0.1.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
+]
+
+[[package]]
+name = "multidict"
+version = "6.7.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174, upload-time = "2026-01-26T02:44:18.509Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116, upload-time = "2026-01-26T02:44:19.745Z" },
+ { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524, upload-time = "2026-01-26T02:44:21.571Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368, upload-time = "2026-01-26T02:44:22.803Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952, upload-time = "2026-01-26T02:44:24.306Z" },
+ { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317, upload-time = "2026-01-26T02:44:25.772Z" },
+ { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132, upload-time = "2026-01-26T02:44:27.648Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140, upload-time = "2026-01-26T02:44:29.588Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277, upload-time = "2026-01-26T02:44:30.902Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291, upload-time = "2026-01-26T02:44:32.31Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156, upload-time = "2026-01-26T02:44:33.734Z" },
+ { url = "https://files.pythonhosted.org/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742, upload-time = "2026-01-26T02:44:35.222Z" },
+ { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221, upload-time = "2026-01-26T02:44:36.604Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664, upload-time = "2026-01-26T02:44:38.008Z" },
+ { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490, upload-time = "2026-01-26T02:44:39.386Z" },
+ { url = "https://files.pythonhosted.org/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695, upload-time = "2026-01-26T02:44:41.318Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884, upload-time = "2026-01-26T02:44:42.488Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122, upload-time = "2026-01-26T02:44:43.664Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175, upload-time = "2026-01-26T02:44:44.894Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460, upload-time = "2026-01-26T02:44:46.106Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930, upload-time = "2026-01-26T02:44:47.278Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582, upload-time = "2026-01-26T02:44:48.604Z" },
+ { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031, upload-time = "2026-01-26T02:44:50.544Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596, upload-time = "2026-01-26T02:44:51.951Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492, upload-time = "2026-01-26T02:44:53.902Z" },
+ { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899, upload-time = "2026-01-26T02:44:55.316Z" },
+ { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970, upload-time = "2026-01-26T02:44:56.783Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060, upload-time = "2026-01-26T02:44:58.195Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888, upload-time = "2026-01-26T02:44:59.57Z" },
+ { url = "https://files.pythonhosted.org/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554, upload-time = "2026-01-26T02:45:01.054Z" },
+ { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341, upload-time = "2026-01-26T02:45:02.484Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391, upload-time = "2026-01-26T02:45:03.862Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422, upload-time = "2026-01-26T02:45:05.296Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" },
+ { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" },
+]
+
+[[package]]
+name = "muse-glimmer-token-service"
+version = "0.1.0"
+source = { editable = "apps/token-service" }
+dependencies = [
+ { name = "fastapi" },
+ { name = "livekit-api" },
+ { name = "pydantic-settings" },
+ { name = "uvicorn", extra = ["standard"] },
+]
+
+[package.dev-dependencies]
+dev = [
+ { name = "httpx" },
+ { name = "pyjwt" },
+ { name = "pytest" },
+ { name = "ruff" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "fastapi", specifier = ">=0.116,<1" },
+ { name = "livekit-api", specifier = ">=1.2,<2" },
+ { name = "pydantic-settings", specifier = ">=2.10,<3" },
+ { name = "uvicorn", extras = ["standard"], specifier = ">=0.35,<1" },
+]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "httpx", specifier = ">=0.28,<1" },
+ { name = "pyjwt", specifier = ">=2.10,<3" },
+ { name = "pytest", specifier = ">=8.4,<9" },
+ { name = "ruff", specifier = ">=0.12,<1" },
+]
+
+[[package]]
+name = "muse-glimmer-voice-agent-workspace"
+version = "0.1.0"
+source = { virtual = "." }
+
+[package.dev-dependencies]
+dev = [
+ { name = "jsonschema" },
+ { name = "pytest" },
+ { name = "ruff" },
+]
+
+[package.metadata]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "jsonschema", specifier = ">=4.25,<5" },
+ { name = "pytest", specifier = ">=8.4,<9" },
+ { name = "ruff", specifier = ">=0.12,<1" },
+]
+
+[[package]]
+name = "muse-glimmer-worker"
+version = "0.1.0"
+source = { editable = "apps/worker" }
+dependencies = [
+ { name = "livekit-agents", extra = ["openai", "silero"] },
+ { name = "livekit-plugins-executorch" },
+]
+
+[package.dev-dependencies]
+dev = [
+ { name = "pytest" },
+ { name = "pytest-asyncio" },
+ { name = "ruff" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "livekit-agents", extras = ["openai", "silero"], specifier = ">=1.6.9,<2" },
+ { name = "livekit-plugins-executorch", editable = "packages/livekit-plugins-executorch" },
+]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "pytest", specifier = ">=8.4,<9" },
+ { name = "pytest-asyncio", specifier = ">=0.25,<2" },
+ { name = "ruff", specifier = ">=0.12,<1" },
+]
+
+[[package]]
+name = "nest-asyncio"
+version = "1.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" },
+]
+
+[[package]]
+name = "numpy"
+version = "2.5.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/9a/80/db0b4559e57ec36362bedbb05530a87fafbcb6067708c946967a41d449e7/numpy-2.5.2.tar.gz", hash = "sha256:d482d171c406ae88c5b19cad3b6a1c4c5209f886ab74bc44c2c865c23f52d860", size = 20773161, upload-time = "2026-08-09T13:48:27.962Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f5/d2/6b24738a0ef4557d189b150046cd07823c50e4273e8aebd651222e24306f/numpy-2.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8e4cb9a754c8a0c62eaa88273a5fba3391f4a610d1dee893c0755da31c083f15", size = 16886595, upload-time = "2026-08-09T13:45:27.323Z" },
+ { url = "https://files.pythonhosted.org/packages/65/60/f2d208d366f263f39c6e69ed309290717aab41078b6d04c9be2a84fa2a07/numpy-2.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:52c808f96484f5571a5cc863775ce50247c17dfb3b0361f8ed6b4b0456f80080", size = 11896845, upload-time = "2026-08-09T13:45:31.638Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/79/81e0bf24f4d020a2b1d5cd297a9f60c3f24eeb116f9bba5870443f7b6a4a/numpy-2.5.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:29d81e97f668489cba8ebfd796b9bdd453525d35dd9e162e2daec94bf3fc7740", size = 5343880, upload-time = "2026-08-09T13:45:34.373Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/cc/e3141cf06d1a8a2c7e107543fe1269c1d1af760d4d683c0794a4ee1127c2/numpy-2.5.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afb3f0632d6b2e3ba04dbce8d1e48d321b369138b73830b5ca371a0e8d479d56", size = 6682264, upload-time = "2026-08-09T13:45:36.7Z" },
+ { url = "https://files.pythonhosted.org/packages/29/f1/2a64a307d92c5d98f5255a4014eb43bb6103ee477087b61ecae44a3aa9b9/numpy-2.5.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0aadf13b60048d501e05fa699efaf7734e2494f3498a4c2a5521d822640324f3", size = 15609566, upload-time = "2026-08-09T13:45:39.518Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/44/59a1eb68e773c4098d107ef34a0dbdeca501d72ffcfbff9a7707343921ce/numpy-2.5.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29b86ff8a6cc556b47ec6b64b194815cc80e6bf5eedcc6cddfd65318cb0b4eee", size = 16709995, upload-time = "2026-08-09T13:45:43.661Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/4c/3e54d4ddbc359a1295f8b633e8106bcd4d7d4a206e82df051bdfb3058755/numpy-2.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6950c4b7dd562453090548ba7f5da7e59f57f85663f15d5dcc60e249192f7e59", size = 16972511, upload-time = "2026-08-09T13:45:47.094Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/9f/02e371638ebf19b66d46231e4be52999e87f32d1961b113bc45656608b22/numpy-2.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9727f472d2f3888053b8a75ab0cb94745a9de224bb5846dbadc0092101bc71d", size = 18465609, upload-time = "2026-08-09T13:45:50.808Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/ae/ad6645abc7a3510fe48e8ea1ab4598166f500057ef4ebf38bfad4f1577de/numpy-2.5.2-cp313-cp313-win32.whl", hash = "sha256:4f9744f9fbdcea0bc552e8f19e1f141f811a3f9bc2be2cc6e86d982cab23e3f4", size = 6070204, upload-time = "2026-08-09T13:45:54.111Z" },
+ { url = "https://files.pythonhosted.org/packages/15/20/f3489f86d81ea460b2bcdceaed094142ca6579f6be0ec527b781d39afe68/numpy-2.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:85aaccb24182c25df891ad0ec333585967e115269d5f1b17f2c9ae005bc96657", size = 12460532, upload-time = "2026-08-09T13:45:57.167Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/21/35b31dde1b283b79de828b80f876afd8c94e28fe1e9c375f89e261cc4c0d/numpy-2.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:bd68ece1553d2023c09a4226d9e41c586ad2d20594d1a456186c33513d2cb3f2", size = 10396725, upload-time = "2026-08-09T13:46:00.478Z" },
+]
+
+[[package]]
+name = "onnxruntime"
+version = "1.29.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "flatbuffers" },
+ { name = "numpy" },
+ { name = "packaging" },
+ { name = "protobuf" },
+]
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/41/f8/d375facf60edaf41f5732f9f689c98a800fcc52df5cf6ddfb406703eb5a1/onnxruntime-1.29.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:be0f8ed688cfb1d4d5765a137193b7bfab0c8ea214eed99260b380bb525a3a7f", size = 21429708, upload-time = "2026-08-17T22:54:01.44Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/17/b9ad04051a8c4f504852ce0e8e10f9a6b2f1a331eedcdcc503df776dd0ea/onnxruntime-1.29.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:d67673c5367727860922c5262d724472f1b5539fb7ccf4c81a638f9b71719803", size = 20816263, upload-time = "2026-08-17T22:54:04.088Z" },
+ { url = "https://files.pythonhosted.org/packages/83/2c/d8eb945d2a372149df9705a8d5c8d7c6c46c987c5446dbcea9e1ea7f6556/onnxruntime-1.29.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e2128f31f449e922c62dbe5d8b6b7b079f0bcaf2d56a102fa203cb6e5bb5ab19", size = 23136817, upload-time = "2026-08-17T22:54:06.714Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/3b/66b424c63fa92dfaa48d1719efaae66fc8c256b9426a832eda51d8dfe1e9/onnxruntime-1.29.0-cp313-cp313-win_amd64.whl", hash = "sha256:2945e1f82f81f27e88decea88c7861f45baea23818950d467bf3909aa303119e", size = 14001310, upload-time = "2026-08-17T22:54:09.13Z" },
+ { url = "https://files.pythonhosted.org/packages/83/22/d6a700e3a6322fa3d56fbe7cee9ffc53f35e77ffcd6b7e97f4b7722a27ab/onnxruntime-1.29.0-cp313-cp313-win_arm64.whl", hash = "sha256:4b940b0d777590c7e20bf298f5c16af1ea6ad1b400a1c822a6be192f64f4d954", size = 13747112, upload-time = "2026-08-17T22:54:11.608Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/89/c4af146de3d60a32c89fea48d5d34bfd044faaf8957270043a03bd1b462b/onnxruntime-1.29.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:533f8370ce124304e5cb08ab961836cf755631e3dd77adc5f3bbdab70c2b7d99", size = 20826136, upload-time = "2026-08-17T22:54:14.315Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/f2/e6bbacd11dfe8d070613261a758795ea128b9fc9bea391a2a7da2e4c7a08/onnxruntime-1.29.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:c1ad3f437153fe77f9d01a08fbaac0beb030e09b8a80ace1603bcf69b6c95481", size = 23138951, upload-time = "2026-08-17T22:54:17.154Z" },
+]
+
+[[package]]
+name = "openai"
+version = "2.54.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+ { name = "distro" },
+ { name = "httpx" },
+ { name = "jiter" },
+ { name = "pydantic" },
+ { name = "sniffio" },
+ { name = "tqdm" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/50/9a/8c75e8c8a5b407a0586faeb2afac91674ff955c191ecc1d6d3b6669f6788/openai-2.54.0.tar.gz", hash = "sha256:e3e6f8bc1ba30ddf381ace1a14340eed381cb984a1a59bd0f34b5be3b5d49cfa", size = 1100285, upload-time = "2026-08-11T18:46:59.035Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/64/a8/bb76c7356de8ad57f59d5ff993d434df0607f07f08bcc9c9a5c275e399c0/openai-2.54.0-py3-none-any.whl", hash = "sha256:89089789197ccdb87f173a03145ed1598d00795220c93e96cf712b1cbf5e5f2b", size = 1660351, upload-time = "2026-08-11T18:46:56.684Z" },
+]
+
+[package.optional-dependencies]
+realtime = [
+ { name = "websockets" },
+]
+
+[[package]]
+name = "opentelemetry-api"
+version = "1.44.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ee/8b/aa9e2d8b8dfa7c946f7dec5d1f8f6ba8eca062f43509a06bdb5ce93d26c0/opentelemetry_api-1.44.0.tar.gz", hash = "sha256:67647e5e9566edcf421166fdf022b3537f818635daa852b289e34604dc6fb33a", size = 72406, upload-time = "2026-07-16T15:25:32.678Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ca/6f/a04e900f465ff3221ccc395522503e2d10e79fa21f2723c8e177aae1e0d1/opentelemetry_api-1.44.0-py3-none-any.whl", hash = "sha256:94b98c893a91b88657eaac1e3ba89618cdb85be6918196705354f34728b2cdef", size = 60018, upload-time = "2026-07-16T15:25:11.657Z" },
+]
+
+[[package]]
+name = "opentelemetry-exporter-otlp"
+version = "1.44.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "opentelemetry-exporter-otlp-proto-grpc" },
+ { name = "opentelemetry-exporter-otlp-proto-http" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f2/45/7af37fe54e5d3e66e7dcd7ba8b8aeee73f202bfac909cc94b8c4e428f9ac/opentelemetry_exporter_otlp-1.44.0.tar.gz", hash = "sha256:af1cde7c33ea8ed624bf04ac49a885730fe44c1f1ad698656e592c38f70ce106", size = 6090, upload-time = "2026-07-16T15:25:34.585Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/18/c3/7b466a9463944e70b37b744072a0c1b88a425dade3fff0631adec66c9bcc/opentelemetry_exporter_otlp-1.44.0-py3-none-any.whl", hash = "sha256:4a498fa8d8fd8be9e8e2d175fe5524a3fe581ccffadd8509db86526a5fb97051", size = 6727, upload-time = "2026-07-16T15:25:14.445Z" },
+]
+
+[[package]]
+name = "opentelemetry-exporter-otlp-proto-common"
+version = "1.44.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "opentelemetry-proto" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/61/09/4d717852c1cf3f854b76c7110a5d00883bc3c99288b9b0dbcbeb9e306eb6/opentelemetry_exporter_otlp_proto_common-1.44.0.tar.gz", hash = "sha256:dc87a5a5bc58f149a56d1547e4691588fa12994cdc3bc039a694ccb3375862ac", size = 20202, upload-time = "2026-07-16T15:25:37.658Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5e/71/65fd9d54c10b860f87c045ccee1264cab7011268895d3528818a29c1172a/opentelemetry_exporter_otlp_proto_common-1.44.0-py3-none-any.whl", hash = "sha256:9a9fe61bba73d802904bc989f1d6b4a7b1ee40f06c40e98d6f85af65aaebb694", size = 17045, upload-time = "2026-07-16T15:25:18.201Z" },
+]
+
+[[package]]
+name = "opentelemetry-exporter-otlp-proto-grpc"
+version = "1.44.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "googleapis-common-protos" },
+ { name = "grpcio" },
+ { name = "opentelemetry-api" },
+ { name = "opentelemetry-exporter-otlp-proto-common" },
+ { name = "opentelemetry-proto" },
+ { name = "opentelemetry-sdk" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/1f/47/80d9e9d468dc5de3af5096f5ccdb065fa4dd1470f74495cc53e59e397f47/opentelemetry_exporter_otlp_proto_grpc-1.44.0.tar.gz", hash = "sha256:40d1ae9e03fcc36de3cbac610cc99f35894938bff9cfd90fc4ec68bd85448463", size = 27225, upload-time = "2026-07-16T15:25:38.308Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/54/29/6ae42ba32b153ae0a44ae125f0caff2188bbe62d99c82d1768da30864e72/opentelemetry_exporter_otlp_proto_grpc-1.44.0-py3-none-any.whl", hash = "sha256:6a1a645ea182a2f59440c51fa8301d309f3324a8f9d65f8395584b064b67ee4e", size = 19624, upload-time = "2026-07-16T15:25:19.096Z" },
+]
+
+[[package]]
+name = "opentelemetry-exporter-otlp-proto-http"
+version = "1.44.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "googleapis-common-protos" },
+ { name = "opentelemetry-api" },
+ { name = "opentelemetry-exporter-otlp-proto-common" },
+ { name = "opentelemetry-proto" },
+ { name = "opentelemetry-sdk" },
+ { name = "requests" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/1a/87/95e2a5aaa795b4e2260d74e16df2d5541deb2ea9de010bcd615f4dee2654/opentelemetry_exporter_otlp_proto_http-1.44.0.tar.gz", hash = "sha256:c633d7270ad6b57cd4cfbe8b0007a9e2e7c0cb50bd6c50fe2a7b245f721a09d8", size = 25806, upload-time = "2026-07-16T15:25:39.162Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cd/d0/fdeb1a98d8d3a6205f5f297c51b4a9bfe65126ab60339669bbe3dd54c2e2/opentelemetry_exporter_otlp_proto_http-1.44.0-py3-none-any.whl", hash = "sha256:838592fce774c1c8bb7b9a0a7facbfa82e17be5a8a4e94cef10cb84ae026bae3", size = 21850, upload-time = "2026-07-16T15:25:20.006Z" },
+]
+
+[[package]]
+name = "opentelemetry-proto"
+version = "1.44.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "protobuf" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/64/01/40ac4ae9a149263cc52c2cee200ddd80cb6d8db1a4610abf8eabce0fe771/opentelemetry_proto-1.44.0.tar.gz", hash = "sha256:c547a79c2f8c0c515d31509154682e5921c7cfd5ca67b70e1f9266e2c3e103f3", size = 46488, upload-time = "2026-07-16T15:25:45.34Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d1/7c/8be563d68e93bbefa5c8affb82ddcff91b3ad858ce49957ba7b16fd3e0ab/opentelemetry_proto-1.44.0-py3-none-any.whl", hash = "sha256:898b155a0e1557afd867478fb6158e8122a46329ca0bb8dc53cc55e98f017f56", size = 72483, upload-time = "2026-07-16T15:25:28.429Z" },
+]
+
+[[package]]
+name = "opentelemetry-sdk"
+version = "1.44.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "opentelemetry-api" },
+ { name = "opentelemetry-semantic-conventions" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5d/77/a6592cbc7c8d9bcc9d6757a9df45e04a7c585e3e6e7a13456da522b21109/opentelemetry_sdk-1.44.0.tar.gz", hash = "sha256:cebe7f65dc12f26ead75c6064de12fd2a9052e5060c0272d402cfa203aae123b", size = 208624, upload-time = "2026-07-16T15:25:46.078Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e7/23/ff077e61886ee020a17ce9c8b6fa11c601c8d8345b09ea24f605445df62a/opentelemetry_sdk-1.44.0-py3-none-any.whl", hash = "sha256:df081c4c6bcfdb1211e3e86140376792643128a25f8d72d1d27675936e7e96ad", size = 137221, upload-time = "2026-07-16T15:25:29.534Z" },
+]
+
+[[package]]
+name = "opentelemetry-semantic-conventions"
+version = "0.65b0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "opentelemetry-api" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/8f/73/0cbdebcb4cf545fdd328da14f5137e37d0770c3f26185e478b0d15d94f50/opentelemetry_semantic_conventions-0.65b0.tar.gz", hash = "sha256:f9b2b81e9d5b64f11bc952075e7e9c7fb0aab075c7fd1c46d597f1b919852d60", size = 148774, upload-time = "2026-07-16T15:25:46.902Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a6/0e/49df70d9b81fb5cbae4bbf2a49d865b09bcbcbc4eb53f5851b1027738d78/opentelemetry_semantic_conventions-0.65b0-py3-none-any.whl", hash = "sha256:1cacde7b0ad306f84c5ef08c3dbe1bbaf20165bba6f8bff43b670e555a086bcb", size = 204645, upload-time = "2026-07-16T15:25:30.688Z" },
+]
+
+[[package]]
+name = "packaging"
+version = "26.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" },
+]
+
+[[package]]
+name = "pillow"
+version = "12.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9d/ac/31fb64e1e7efb5a4b50cd3d92049ba89ac6e4d8d3bb6a74e15048ca3353e/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89", size = 4161684, upload-time = "2026-07-01T11:54:25.934Z" },
+ { url = "https://files.pythonhosted.org/packages/87/b4/9805e23d2b4d77842b468513841fda254ee42f0289d25088340e4ff46e2d/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace", size = 4255487, upload-time = "2026-07-01T11:54:27.935Z" },
+ { url = "https://files.pythonhosted.org/packages/df/39/ecf519435a200c693fe053a6ee4d835b41cf963a4dfc2551c4e637cb2a71/pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec", size = 3696433, upload-time = "2026-07-01T11:54:29.813Z" },
+ { url = "https://files.pythonhosted.org/packages/42/92/2fc3ffad878ae8dd5469ec1bc8eb83b71f48e13efdf68f02709003982a32/pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66", size = 5345889, upload-time = "2026-07-01T11:54:31.97Z" },
+ { url = "https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35", size = 4780109, upload-time = "2026-07-01T11:54:34.026Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/01/e18aff37cb0b4aac47ac90f016d347a49aca667ef97f190b06ac2aabc928/pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65", size = 6263736, upload-time = "2026-07-01T11:54:36.131Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3", size = 6937129, upload-time = "2026-07-01T11:54:38.216Z" },
+ { url = "https://files.pythonhosted.org/packages/70/4d/105627a13300c5e0df1d174230b32fd1273062c96f7745fd552b945d1e1d/pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a", size = 6339562, upload-time = "2026-07-01T11:54:40.354Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/1d/f13de01a553988ab895ba1c722e06cf3144d4f57656fd5b81b6d881f1179/pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e", size = 7049439, upload-time = "2026-07-01T11:54:42.489Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/f9/066794cca041b969964f779ee5fa66a9498bbf34248ac39c5d7954e4198f/pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f", size = 6473287, upload-time = "2026-07-01T11:54:44.9Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/9b/7a58e61d62be561da3a356fe2384d4059a6345fc130e23ef1c36a5b81d24/pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8", size = 7239691, upload-time = "2026-07-01T11:54:47.141Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/b0/c4ed4f0ef8f8fa5ee8351537db6650bb8189f7e118842978dd6589065692/pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b", size = 2568185, upload-time = "2026-07-01T11:54:49.137Z" },
+]
+
+[[package]]
+name = "pluggy"
+version = "1.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
+]
+
+[[package]]
+name = "prometheus-client"
+version = "0.26.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/52/73/f1334c29c2af4cd9dba6c7817e61b611bd0215e2eb5565c6064a4de18802/prometheus_client-0.26.0.tar.gz", hash = "sha256:04a91bcf94e2cf74a44a1a874d651a2e853ed354b6e822f3b7487751465d5c2b", size = 92910, upload-time = "2026-07-24T19:36:41.893Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/eb/a3/b69efbf4143b5b9859b977770bbbabcc2796b702fa69dc40271e45cd5a56/prometheus_client-0.26.0-py3-none-any.whl", hash = "sha256:fa93d06737aa02bacd05794768508bb97d2fbee28cb3bca04eaae92f0ca953d6", size = 64494, upload-time = "2026-07-24T19:36:40.854Z" },
+]
+
+[[package]]
+name = "propcache"
+version = "0.5.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ec/44/c87281c333769159c50594f22610f77398a47ccbfbbf23074e744e86f87c/propcache-0.5.2.tar.gz", hash = "sha256:01c4fc7480cd0598bb4b57022df55b9ca296da7fc5a8760bd8451a7e63a7d427", size = 50208, upload-time = "2026-05-08T21:02:12.199Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c5/09/f049e45385503fe67db75a6b6186a7b9f0c3930366dc960522c312a825b1/propcache-0.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:099aaf4b4d1a02265b92a977edf00b5c4f63b3b17ac6de39b0d637c9cac0188a", size = 94457, upload-time = "2026-05-08T21:00:36.355Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/65/83d1d05655baf63113731bd5a1008435e14f8d1e5a06cbe4ec5b23ad7a31/propcache-0.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68ce1c44c7a813a7f71ea04315a8c7b330b63db99d059a797a4651bb6f69f117", size = 53835, upload-time = "2026-05-08T21:00:38.072Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/12/a6ba6482bb5ea3260c000c9b20881c95fa11c6b30173715668259f844ed7/propcache-0.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fc299c129490f55f254cd90be0deca4764e36e9a7c08b4aa588479a3bbed3098", size = 54545, upload-time = "2026-05-08T21:00:39.319Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/19/7fa086f5764c59ec8a8e157cd93aa8497acc00aba9dcdec56bfffb32602d/propcache-0.5.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6ae2198be502c10f09b2516e7b5d019816924bc3183a43ce792a7bd6625e6f4", size = 59886, upload-time = "2026-05-08T21:00:40.621Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/e4/5d7663dc8235956c8f5281698a3af1d351d8820341ddd890f59d9a9127f2/propcache-0.5.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6041d31504dc1779d700e1edcfb08eea334b357620b06681a4eabb57a74e574e", size = 63261, upload-time = "2026-05-08T21:00:41.775Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/4a/15a03adee24d6350da4292caeac44c34c033d2afe5e87eb370f38854560f/propcache-0.5.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7eabc04151c78a9f4d5bbb5f1faf571e4defeb4b585e0fe95b60ff2dbe4d3d7", size = 64184, upload-time = "2026-05-08T21:00:43.018Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/c6/979176efdaa3d239e36d503d5af63a0a773b36662ed8f52e5b6a6d9fd40e/propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4db0ba63d693afd40d249bd93f842b5f144f8fcbb83de05660373bcf30517b1d", size = 61534, upload-time = "2026-05-08T21:00:44.507Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/22/63e8cd1bae4c2d2be6493b6b7d10566ddafad88137cfbc99964a1119853c/propcache-0.5.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dbcf7675229b35d31abb6547d8ebc8c27a830ac3f9a794edff6254873ec7c0a", size = 61500, upload-time = "2026-05-08T21:00:45.796Z" },
+ { url = "https://files.pythonhosted.org/packages/60/5a/28e5d9acbac1cc9ccb67045e8c1b943aa8d79fdf39c93bd73cacd68008ea/propcache-0.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d310c013aad2c72f1c3f2f8dd3279d460a858c551f97aeb8c63e4693cca7b4d2", size = 59994, upload-time = "2026-05-08T21:00:47.093Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/40/db650677f554a95b9c01a7c9d93d629e93a15562f5deb4573c9ee136fed2/propcache-0.5.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:06187263ddad280d05b4d8a8b3bb7d164cbebd469236544a42e6d9b28ac6a4fa", size = 56884, upload-time = "2026-05-08T21:00:48.376Z" },
+ { url = "https://files.pythonhosted.org/packages/80/45/70b39b89516ff8b96bf732fa6fded8cef20f293cb1508690101c3c07ec51/propcache-0.5.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3115559b8effafd63b142ea5ed53d63a16ea6469cbc63dce4ee194b42db5d853", size = 63464, upload-time = "2026-05-08T21:00:49.954Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/e2/fa59d3a89eac5534293124af4f1d0d0ada091ce4a0ab4610ce03fd2bdd8d/propcache-0.5.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c60462af8e6dc30c35407c7237ea908d777b22862bbee27bc4699c0d8bcdc45a", size = 61588, upload-time = "2026-05-08T21:00:51.281Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/97/efb547a55c4bc7381cfb202d6a2239ac621045277bc1ea5dfd3a7f0516c0/propcache-0.5.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40314bca9ac559716fe374094fc81c11dcc34b64fd6c585360f5775690505704", size = 64667, upload-time = "2026-05-08T21:00:52.602Z" },
+ { url = "https://files.pythonhosted.org/packages/92/56/f5c7d9b4b7595d5127da38974d791b2153f3d1eae6c674af3583ace92ad3/propcache-0.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cfa21e036ce1e1db2be04ba3b85d2df1bb1702fa01932d984c5464c665228ff4", size = 62463, upload-time = "2026-05-08T21:00:54.303Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/3b/484a3a65fc9f9f60c41dcd17b428bace5389544e2c680994534a20755066/propcache-0.5.2-cp313-cp313-win32.whl", hash = "sha256:f156a3529f38063b6dbaf356e15602a7f95f8055b1295a438433a6386f10463d", size = 38621, upload-time = "2026-05-08T21:00:55.808Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/fd/3f0f10dba4dabad3bf53102be007abf55481067952bde0fdddff439e7c61/propcache-0.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:dfed59d0a5aeb01e242e66ff0300bc4a265a7c05f612d30016f0b60b1017d757", size = 41649, upload-time = "2026-05-08T21:00:57.061Z" },
+ { url = "https://files.pythonhosted.org/packages/90/ec/6ce619cc32bb500a482f811f9cd509368b4e58e638d13f2c68f370d6b475/propcache-0.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:ba338430e87ceb9c8f0cf754de38a9860560261e56c00376debd628698a7364f", size = 37636, upload-time = "2026-05-08T21:00:58.646Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/82/c1d268bbbf2ef981c5bf0fbbe746db617c66e3bcefe431a1aa8943fbe23a/propcache-0.5.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a592f5f3da71c8691c788c13cb6734b6d17663d2e1cb8caddf0673d01ef8847d", size = 98872, upload-time = "2026-05-08T21:00:59.889Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/d4/52c871e73e864e6b34c0e2d58ac1ec5ccd149497ddc7ad2137ae98323a35/propcache-0.5.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6a997d0489e9668a384fcfd5061b857aa5361de73191cac204d04b889cfbbafa", size = 56257, upload-time = "2026-05-08T21:01:01.195Z" },
+ { url = "https://files.pythonhosted.org/packages/67/f0/9b90ca2a210b3d09bcfcd96ecd0f55545c091535abce2a45de2775cfd357/propcache-0.5.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:10734b5484ea113152ee25a91dccedf81631791805d2c9ccb054958e51842c94", size = 56696, upload-time = "2026-05-08T21:01:02.941Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/0e/6e9d4ba07c8e56e21ddec1e75f12148142b21ca83a51871babce095334f4/propcache-0.5.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cafca7e56c12bb02ae16d283742bef25a61122e9dab2b5b3f2ccbe589ce32164", size = 62378, upload-time = "2026-05-08T21:01:04.475Z" },
+ { url = "https://files.pythonhosted.org/packages/65/19/c10badaa463dde8a27ce884f8ee2ec37e6035b7c9f5ff0c8f74f06f08dac/propcache-0.5.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f064f8d2b59177878b7615df1735cd8fe3462ed6be8c7b217d17a276489c2b7f", size = 65283, upload-time = "2026-05-08T21:01:05.959Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/b6/93bea99ca80e19cef6512a8580e5b7857bbe09422d9daa7fd4ef5723306c/propcache-0.5.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f78abfa8dfc32376fd1aacf597b2f2fbbe0ea751419aee718af5d4f82537ef8c", size = 66616, upload-time = "2026-05-08T21:01:07.228Z" },
+ { url = "https://files.pythonhosted.org/packages/83/e4/5c7462e50625f051f37fb38b8224f7639f667184bbd34424ec83819bb1b7/propcache-0.5.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7467da8a9822bf1a55336f877340c5bcbd3c482afc43a99771169f74a26dedc", size = 63773, upload-time = "2026-05-08T21:01:08.514Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/b6/99238894047b13c823be25027e736626cd414a52a5e30d2c3347c2733529/propcache-0.5.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a6ddc6ac9e25de626c1f129c1b467d7ecd33ce2237d3fd0c4e429feef0a7ee1f", size = 63664, upload-time = "2026-05-08T21:01:09.874Z" },
+ { url = "https://files.pythonhosted.org/packages/85/1e/a3a1a63116a2b8edb415a8bb9a6f0c34bd03830b1e18e8ce2904e1dc1cf4/propcache-0.5.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2f22cbbac9e26a8e864c0985ff1268d5d939d53d9d9411a9824279097e03a2cb", size = 62643, upload-time = "2026-05-08T21:01:11.132Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/03/893cf147de2fc6543c5eaa07ad833170e7e2a2385725bbebe8c0503723bb/propcache-0.5.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:fc76378c62a0f04d0cd82fbb1a2cd2d7e28fcb40d5873f28a6c44e388aaa2751", size = 59595, upload-time = "2026-05-08T21:01:12.387Z" },
+ { url = "https://files.pythonhosted.org/packages/86/3b/04c1a2e12c57766568ba75ba72b3bf2042818d4c1425fab6fc07155c7cff/propcache-0.5.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:acd2c8edba48e31e58a363b8cf4e5c7db3b04b3f9e371f601df30d9b0d244836", size = 65711, upload-time = "2026-05-08T21:01:13.676Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/34/80f8d0099f8d6bacc4de1624c85672681c8cd1149ca2da0e38fd120b817f/propcache-0.5.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:452b5065457eb9991ec5eb38ff41d6cd4c991c9ac7c531c4d5849ae473a9a13f", size = 64247, upload-time = "2026-05-08T21:01:14.936Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/1a/8b08f3a5f1037e9e370c55883ceeeee0f6dd0416fb2d2d67b8bfc91f2a79/propcache-0.5.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3430bb2bfe1331885c427745a751e774ee679fd4344f80b97bf879815fe8fa55", size = 67102, upload-time = "2026-05-08T21:01:16.281Z" },
+ { url = "https://files.pythonhosted.org/packages/34/68/8bdb7bb7756d76e005490649d10e4a8369e610c74d619f71e1aedf889e9c/propcache-0.5.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cef6cea3922890dd6c9654971001fa797b526c16ab5e1e46c05fd6f877be7568", size = 64964, upload-time = "2026-05-08T21:01:17.57Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/aa/50fb0b5d3968b61a510926ff8b8465f1d6e976b3ab74496d7a4b9fc42515/propcache-0.5.2-cp313-cp313t-win32.whl", hash = "sha256:72d61e16dd78228b58c5d47be830ff3da7e5f139abdf0aef9d86cde1c5cf2191", size = 42546, upload-time = "2026-05-08T21:01:18.946Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/4c/0ddbae64321bd4a95bcbfc19307238016b5b1fee645c84626c8d539e5b74/propcache-0.5.2-cp313-cp313t-win_amd64.whl", hash = "sha256:0958834041a0166d343b8d2cedcd8bcbaeb4fdbe0cf08320c5379f143c3be6e7", size = 46330, upload-time = "2026-05-08T21:01:20.162Z" },
+ { url = "https://files.pythonhosted.org/packages/00/d9/9cddc8efb78d8af264c5ec9f6d10b62f57c515feda8d321595f56010fb23/propcache-0.5.2-cp313-cp313t-win_arm64.whl", hash = "sha256:6de8bd93ddde9b992cf2b2e0d796d501a19026b5b9fd87356d7d0779531a8d96", size = 40521, upload-time = "2026-05-08T21:01:21.399Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/ed/1cdcab6ba3d6ab7feca11fc14f0eeea80755bb53ef4e892079f31b10a25f/propcache-0.5.2-py3-none-any.whl", hash = "sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe", size = 14036, upload-time = "2026-05-08T21:02:10.673Z" },
+]
+
+[[package]]
+name = "protobuf"
+version = "7.36.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a7/e7/0553e21d25ca4d9f573135775348a372c3ec34a93a71d5f297c3bac38341/protobuf-7.36.0.tar.gz", hash = "sha256:e8e09cb0d794c6687926fa558a8a6e72aa10edb997d5ca61da0765f12a3e00ea", size = 510034, upload-time = "2026-08-20T16:34:01.071Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8f/ae/58e3ca96cb2e118cc546b677359b3c6659f79a140935c08dec94c7998585/protobuf-7.36.0-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:9103532dffd80c6fab7e50c65a31007680a06eb57537d437bb1b35812c138a37", size = 453256, upload-time = "2026-08-20T16:33:53.945Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/15/5162230af4912697f0fe406f6800f80760945babcff0e2c2fe6c84ef2d5d/protobuf-7.36.0-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:bf94a5917c71058262de683669bc0a797a7669d3de71f0b36d058e3194f47b44", size = 341436, upload-time = "2026-08-20T16:33:55.134Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/09/1670b2bfc9a45e807e520c3e9be36524db9ccc7dc05ea17af7681cabdc61/protobuf-7.36.0-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:3297e60abdff301e5f74393d87f6cc59dacab5f024a89548a6e8de1d26576b16", size = 354440, upload-time = "2026-08-20T16:33:56.077Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/f8/bd5804695ba400e423c33fd4d9f58c28d86633d5ba1945c36ff3967d98cb/protobuf-7.36.0-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:70f5ec8eb0da81a44360c0dc0beac99a0d78071d21956a7076bae8bd2051841b", size = 340439, upload-time = "2026-08-20T16:33:56.992Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/9f/acd02338235a3e7d03168c4303478347b7624fc8189ff4e7f0d2654bbe86/protobuf-7.36.0-cp310-abi3-win32.whl", hash = "sha256:7326fd717bdc419162a735938d89d4032332bcc3408804012b24ff3a37086071", size = 440216, upload-time = "2026-08-20T16:33:57.99Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/4e/12cb93270967a2affff5b3f720694700d4d87712a67afd05c8cb3f6fa52c/protobuf-7.36.0-cp310-abi3-win_amd64.whl", hash = "sha256:1781cc1de61249b750848029bca452c0a8b7e990080316b9bbc2518b2117b488", size = 453731, upload-time = "2026-08-20T16:33:58.951Z" },
+ { url = "https://files.pythonhosted.org/packages/01/c3/629999e78d46c1115c11886d51c6bd68c17ce4a944f1ea3e153a91316a33/protobuf-7.36.0-py3-none-any.whl", hash = "sha256:53374d53fc29a67f7dbbf0ade47d7526a0f0137bf0f9c90e48d8a60790ef748c", size = 177024, upload-time = "2026-08-20T16:34:00.053Z" },
+]
+
+[[package]]
+name = "psutil"
+version = "7.2.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" },
+ { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" },
+ { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" },
+ { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" },
+ { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" },
+ { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" },
+]
+
+[[package]]
+name = "pycparser"
+version = "3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" },
+]
+
+[[package]]
+name = "pydantic"
+version = "2.13.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "annotated-types" },
+ { name = "pydantic-core" },
+ { name = "typing-extensions" },
+ { name = "typing-inspection" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" },
+]
+
+[[package]]
+name = "pydantic-core"
+version = "2.46.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" },
+ { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" },
+ { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" },
+ { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" },
+ { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" },
+]
+
+[[package]]
+name = "pydantic-settings"
+version = "2.15.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pydantic" },
+ { name = "python-dotenv" },
+ { name = "typing-inspection" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/68/ca/31c57507b13119d7d3cfa1576dad2911a4861e3be07b579395f4e9d393f9/pydantic_settings-2.15.0.tar.gz", hash = "sha256:694b793e84f766ba76a90ebdefc01d0a9a045dab0382bee70393da93712ad117", size = 261253, upload-time = "2026-08-07T09:24:57.419Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/30/a4/2bffa9f8e804325a09867f0e9d30795c80ea9f8d62560bd1b6ad6220eb2f/pydantic_settings-2.15.0-py3-none-any.whl", hash = "sha256:0ba092c291c94baceb5eff768aa0d56400a457585bc0175925a5a5510303da42", size = 69413, upload-time = "2026-08-07T09:24:55.839Z" },
+]
+
+[[package]]
+name = "pygments"
+version = "2.21.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" },
+]
+
+[[package]]
+name = "pyjwt"
+version = "2.13.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" },
+]
+
+[[package]]
+name = "pytest"
+version = "8.4.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ { name = "iniconfig" },
+ { name = "packaging" },
+ { name = "pluggy" },
+ { name = "pygments" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
+]
+
+[[package]]
+name = "pytest-asyncio"
+version = "1.4.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pytest" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" },
+]
+
+[[package]]
+name = "python-dotenv"
+version = "1.2.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6a/53/ed9d74092561d4b01a2ef1349d52cdbc135e526c245f366b089cfca6de49/python_dotenv-1.2.3.tar.gz", hash = "sha256:a20a594dabeaa385725aa239d5244871c143ecb356add8a20fcf23773a6c3a35", size = 58945, upload-time = "2026-08-16T16:54:54.067Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0d/17/c5c6b53ddc18f297992099b3d9ec16c855c0ccc83263a21fe4d1c625ec6c/python_dotenv-1.2.3-py3-none-any.whl", hash = "sha256:904552145e8bfed22162c09dab1c2b9b54fefa7b23ba780f4f26ca0316b0f0d9", size = 22780, upload-time = "2026-08-16T16:54:52.473Z" },
+]
+
+[[package]]
+name = "pyyaml"
+version = "6.0.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
+ { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
+ { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
+ { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
+ { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
+ { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
+ { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
+]
+
+[[package]]
+name = "referencing"
+version = "0.37.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "attrs" },
+ { name = "rpds-py" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" },
+]
+
+[[package]]
+name = "requests"
+version = "2.34.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "certifi" },
+ { name = "charset-normalizer" },
+ { name = "idna" },
+ { name = "urllib3" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" },
+]
+
+[[package]]
+name = "rich"
+version = "15.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markdown-it-py" },
+ { name = "pygments" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" },
+]
+
+[[package]]
+name = "rpds-py"
+version = "2026.6.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/aa/2a/9618a122aeb2a169a28b03889a2995fe297588964333d4a7d67bdf46e147/rpds_py-2026.6.3.tar.gz", hash = "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4", size = 64051, upload-time = "2026-06-30T07:17:53.009Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a4/9e/b818ee580026ec578138e961027a68820c40afeb1ec8f6819b54fb99e196/rpds_py-2026.6.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3cfe765c1da0072636ca06628261e0ea05688e160d5c8a03e0217c3854037223", size = 343012, upload-time = "2026-06-30T07:15:36.005Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/6b/686d9dc4359a8f163cfbbf89ee0b4e586431de22fe8248edb63a8cf50d49/rpds_py-2026.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f4d78253f6996be4901669ad25319f842f740eccf4d58e3c7f3dd39e6dde1d8f", size = 338203, upload-time = "2026-06-30T07:15:37.462Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/9b/069aa329940f8207615e091f5eedbbd40e1e15eac68a0790fd05ccdf796c/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54f45a148e28767bf343d33a684693c70e451c6f4c0e9904709a723fafbdfc1f", size = 367984, upload-time = "2026-06-30T07:15:39.008Z" },
+ { url = "https://files.pythonhosted.org/packages/14/db/34c203e4becff3703e4d3bc121842c00b8689197f398161203a880052f4e/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:842e7b070435622248c7a2c44ae53fa1440e073cc3023bc919fed570884097a7", size = 374815, upload-time = "2026-06-30T07:15:40.253Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/7d/8071067d2cc453d916ad836e828c943f575e8a44612537759002a1e07381/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8020133a74bd81b4572dd8e4be028a6b1ebcd70e6726edc3918008c08bee6ee6", size = 490545, upload-time = "2026-06-30T07:15:41.729Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/42/da06c5aa8f0484ff07f270787434204d9f4535e2f8c3b51ed402267e63c3/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdc7e35386f3847df728fbcb5e887e2d79c19e2fa1eba9e51b6621d23e3243af", size = 382828, upload-time = "2026-06-30T07:15:43.327Z" },
+ { url = "https://files.pythonhosted.org/packages/57/d7/fe978efc2ae50abe48eb7464668ea99f53c010c60aeebb7b35ad27f23661/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acac386b453c2516111b50985d60ce46e7fadb5ea71ae7b25f4c946935bf27cf", size = 365678, upload-time = "2026-06-30T07:15:44.992Z" },
+ { url = "https://files.pythonhosted.org/packages/69/9d/1d8922e1990b2a6eb532b6ff53d3e73d2b3bbffc84116c75826bee73dfc6/rpds_py-2026.6.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:425560c6fa0415f27261727bb20bd097568485e5eb0c121f1949417d1c516885", size = 377811, upload-time = "2026-06-30T07:15:46.523Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/3d/198dceafb4fb034a6a47347e1b0735d34e0bd4a50be4e898d408ee66cb14/rpds_py-2026.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a550fb4950a06dde3beb4721f5ad4b25bf4513784665b0a8522c792e2bd822a4", size = 395382, upload-time = "2026-06-30T07:15:47.955Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/f1/13968e49655d40b6b19d8b9140296bbc6f1d86b3f0f6c346cf9f1adddf4b/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f4bca01b63096f606e095734dd56e74e175f94cfbf24ff3d63281cec61f7bb7", size = 543832, upload-time = "2026-06-30T07:15:49.33Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/ab/289bcb1b90bd3e40a2900c561fa0e2087345ecbb094f0b870f2345142b7c/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ccffae9a092a00deb7efd545fe5e2c33c33b88e7c054337e9a74c179347d0b7d", size = 611011, upload-time = "2026-06-30T07:15:50.847Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/16/5043105e679436ccfbc8e5e0dd2d663ed18a8b8113515fd06a5e5d77c83e/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1cf01971c4f2c5553b772a542e4aaf191789cd331bc2cd4ff0e6e65ba49e1e97", size = 572431, upload-time = "2026-06-30T07:15:52.394Z" },
+ { url = "https://files.pythonhosted.org/packages/85/ed/adab103321c0a6565d5ae1c2998349bc3ee175b82ccc5ae8fc04cc413075/rpds_py-2026.6.3-cp313-cp313-win32.whl", hash = "sha256:8c3d1e9c15b9d51ca0391e13da1a25a0a4df3c58a37c9dc368e0736cf7f69df0", size = 201710, upload-time = "2026-06-30T07:15:53.894Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/ed/a03b09668e74e5dabbf2e211f6468e1820c0552f7b0500082da31841bf7b/rpds_py-2026.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:9250a9a0a6fd4648b3f868da8d91a4c52b5811a62df58e753d50ae4454a36f80", size = 219454, upload-time = "2026-06-30T07:15:55.25Z" },
+ { url = "https://files.pythonhosted.org/packages/27/17/b8642c12930b71bc2b25831f6708ccf0f75abcd11883932ec9ce54ba3a78/rpds_py-2026.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:900a67df3fd1660b035a4761c4ce73c382ea6b35f90f9863c36c6fd8bf8b09bb", size = 215063, upload-time = "2026-06-30T07:15:56.573Z" },
+]
+
+[[package]]
+name = "ruff"
+version = "0.16.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/00/8f/d8074b1f25e003164087a8bfe79a0f1a3945135764dbb6aaab04103dcaf9/ruff-0.16.4.tar.gz", hash = "sha256:13171aa9d9af2240ee3504e639de73122c67e74036de5ba2e1d01422cd17e3dc", size = 4899731, upload-time = "2026-08-20T17:43:59.196Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ff/80/779895ef584e089d22f2c6df0d0e99a65ec2df0805f1fffd439415b8c1f0/ruff-0.16.4-py3-none-linux_armv6l.whl", hash = "sha256:df4075f71ddac40b9934af60c3ec8a53047dd5a5fdc43224e6e4e8e9a27cb6f7", size = 10006909, upload-time = "2026-08-20T17:43:16.888Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/e6/f553199b5e8927a05cb5c422d921fd0656b29ab976e91c44802107c6b0da/ruff-0.16.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0c95538517af68004306b0fb3214ff2f2af67a65092aee77cd9eb86db6656604", size = 10240201, upload-time = "2026-08-20T17:43:19.337Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/70/4a6dc4bb34da4dee35e30f09bbd1bfbdd26f33b62fb9b8df31f08a199cd2/ruff-0.16.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:963f83df8e69e575b64d67dd447ebbc917db41a14bf38d4593a4183e7aaa8255", size = 9835122, upload-time = "2026-08-20T17:43:21.708Z" },
+ { url = "https://files.pythonhosted.org/packages/24/12/c6e22d686372c15bcb7af99831f1a1be96df696491babf4f24e4f942c527/ruff-0.16.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32a5057c7ff3f6e6480a48fccfb3a412a690f48a3d03ac5cf08177d6c2da3ade", size = 9977162, upload-time = "2026-08-20T17:43:24.236Z" },
+ { url = "https://files.pythonhosted.org/packages/46/49/72b10ec912f5ab5854992eaf7aa7cd36729b6937d9dc4e0fb41b3bf428ec/ruff-0.16.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b3dce8d9b0c57c265b91885a66a567d8ea1372e8eb4e250fa8e5e3f579e99cff", size = 9829789, upload-time = "2026-08-20T17:43:26.966Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/80/0f30e32e7f6ee26edc39075502db9d368d788a44a79b55f763eb4ab03796/ruff-0.16.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7dc651db49283c69f8e72c834eec4fe5573e4c646856aebece0ce385dceb2a80", size = 10527949, upload-time = "2026-08-20T17:43:29.384Z" },
+ { url = "https://files.pythonhosted.org/packages/52/3d/86e8ad3542169e56cac3859a343afdb9df2ad54d35a59ce1e67baee83421/ruff-0.16.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3817b87dbcabc92f13b05019257c5b89b5b4d51b5fb20f56fb5235ceb723cd07", size = 11333695, upload-time = "2026-08-20T17:43:31.872Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/16/481c29b380c20a0054a8261066665e1b3488e23636c49d0a43e75975b9bb/ruff-0.16.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9fce1499134b2c8c68e5166f95705a5812062bb93aacc5f9873bb1a27084bc7", size = 10727741, upload-time = "2026-08-20T17:43:34.596Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/b6/56bc0b8cf45b54b28b3a5e6381c8945d51b5b18adf659454c32295209a31/ruff-0.16.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2d812e482f5a7e02eee26cd73d2a37ebbdf47d795ea63ba1b89110ae93e9fb3", size = 10286522, upload-time = "2026-08-20T17:43:37.288Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/8b/b345b4fb110f2fbe2bd31eabd271e5e8b3b7e4ee6c0e02f2dc6be78db000/ruff-0.16.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:6baaf984aa7976edf93d3b627fe2d1d22ee94bbca05fa6f90fc76d73924e3454", size = 10584182, upload-time = "2026-08-20T17:43:39.984Z" },
+ { url = "https://files.pythonhosted.org/packages/29/e5/827b34041c35f58774a9681a4213994c164fc987800f4dddabcf451da0bf/ruff-0.16.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:bdfcf0b28662eb890372d50f92c283bb94e67e7635ed93c7fd533970acff7b2b", size = 10134195, upload-time = "2026-08-20T17:43:42.351Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/10/d0bffcdd6729b87afc82ba0ef377173356a7dc8e972f5179968cf2fdf98c/ruff-0.16.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b66b02cb9b04f537643cadf5768e5f98dc461890d530cb67113d71c8c76e605d", size = 9825821, upload-time = "2026-08-20T17:43:44.532Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/32/0db2a863b796ca62d83e92a07a3ccf00921b14db02059347576a2fda3d4b/ruff-0.16.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8528bf9a4b291a60bf02ea453511e8ce6215bd2b982ee80405b66b008b6c30a0", size = 10267658, upload-time = "2026-08-20T17:43:46.989Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/a0/fbdeb59e48c6261f523e56c8f12e9c08fbe693786595cc7e3959207a9232/ruff-0.16.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:fbd85d2875fdd67e833213a651f613bbf25303abf6aa822a5121f4531195678d", size = 10697071, upload-time = "2026-08-20T17:43:49.891Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/28/0c6dd865859c6d17bc8ccc34cb72b0e02d6c7eb25e8a1e22b5bea681e2c0/ruff-0.16.4-py3-none-win32.whl", hash = "sha256:312769988007aaeb8e189b443ccdd03c0e6374489e053467be6d96518ebff76e", size = 10021687, upload-time = "2026-08-20T17:43:52.281Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/03/e724450f621698117f9aa6dd241c94d0274ae96781378dc86745ae29f0e7/ruff-0.16.4-py3-none-win_amd64.whl", hash = "sha256:05d9d27a18c4bcbefada602480ec9e01e0bc949d432e0ced5df77edac195919c", size = 10567657, upload-time = "2026-08-20T17:43:54.78Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/fe/da8b9e1347696bb22120b77280ec5ce25d500ca5cb39d5ad6e5c18de19c1/ruff-0.16.4-py3-none-win_arm64.whl", hash = "sha256:a3a61621c9b6f6a89573e938a080e648f1695baa3f58570a3a707bc51ff65a21", size = 10451579, upload-time = "2026-08-20T17:43:57.135Z" },
+]
+
+[[package]]
+name = "shellingham"
+version = "1.5.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
+]
+
+[[package]]
+name = "sniffio"
+version = "1.3.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
+]
+
+[[package]]
+name = "sounddevice"
+version = "0.5.6"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cffi" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ec/db/0c890e2d9aab9ba284021efc02e1d3aebfecab1b611762d7434602209bcf/sounddevice-0.5.6.tar.gz", hash = "sha256:8ec9fbfde2e32f020b167e348f3ab3bac6625a5f15af524d790108ac7147a410", size = 1120094, upload-time = "2026-08-17T07:55:05.048Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/72/1f/62eef605172bddc1017508469a12f75bc7c4194ece35c734f822795f53b1/sounddevice-0.5.6-py3-none-any.whl", hash = "sha256:de099612311ad81e55d31ccbd83f43ea6bf4d87b48f9b6ea55a1fbcde0eee4e0", size = 32793, upload-time = "2026-08-17T07:54:57.507Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/84/85e719d49cf98b2f406d9ac9c338892286c4448eb42ef0b2625ccf159616/sounddevice-0.5.6-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:e3aef00ad8b1d1740eb66d9a7671eab88a4d2b8fa4ab33498d742e63b65c309c", size = 1009647, upload-time = "2026-08-17T07:54:58.814Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/6f/6292145099f72a153a710245f46ae43e5fb6c77bec1b6086cb76c12dc280/sounddevice-0.5.6-py3-none-win32.whl", hash = "sha256:b36b807eb02abd257198bf84b2af05e4fea199a9d2f0019014169c7136d45e9c", size = 1009627, upload-time = "2026-08-17T07:55:00.401Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/3e/cbc593c31a5f0d817b3fe97e64aa8461bd0f55cb07b67ce1b776296ae336/sounddevice-0.5.6-py3-none-win_amd64.whl", hash = "sha256:7f4162f514f007b0bf25a3ccfed3f1705bc2ec311888a90232729eec4f57a4f4", size = 1009630, upload-time = "2026-08-17T07:55:02.088Z" },
+ { url = "https://files.pythonhosted.org/packages/60/a4/b0c21c9f215a6fd9606b8f8748c21212dc098e5d5a2d93068c50edcf19b4/sounddevice-0.5.6-py3-none-win_arm64.whl", hash = "sha256:c8ae19173e5f27f8c12d4b5eee2dbfe542cee125d591e663e0fb4dfb75246d45", size = 1009630, upload-time = "2026-08-17T07:55:03.689Z" },
+]
+
+[[package]]
+name = "starlette"
+version = "1.6.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b5/b4/205b0d5241d934e8add0c38aa924c4f9fb7330834ff11e5444db964ec3f9/starlette-1.6.0.tar.gz", hash = "sha256:d4e3ac5e546444960c710297a3c9fc3f7ebae1b7e963f3d36173b49da535be9b", size = 2716969, upload-time = "2026-08-08T18:27:57.512Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c8/cb/6a6a47d5b464bd08695d254f3da6e7986cc70c9fa5d778eda57538edfe56/starlette-1.6.0-py3-none-any.whl", hash = "sha256:a86dd39d14bb45f85a3d18525215a9ef0cfd1f192ac793220e72598c90335f0c", size = 75969, upload-time = "2026-08-08T18:27:56.196Z" },
+]
+
+[[package]]
+name = "tqdm"
+version = "4.70.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/21/3b/6c24bec5be5e743ffd99576daa5cc077722fc7d5bbc00bd133fa0c698dc6/tqdm-4.70.0.tar.gz", hash = "sha256:55b0b0dbd97462d06ebee91e4dac24ed4d4702be82b24f07e6c1d27e08cea220", size = 795438, upload-time = "2026-07-27T11:33:15.271Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f9/1c/01bfd571a64e7f270e6bab5e33777debe0edc56759233ce84f27dec92d14/tqdm-4.70.0-py3-none-any.whl", hash = "sha256:7f585706bfddbdebf89daac705b2dfcc16890130727d3197ca62c732b4310953", size = 80184, upload-time = "2026-07-27T11:33:13.167Z" },
+]
+
+[[package]]
+name = "typer"
+version = "0.27.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "annotated-doc" },
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ { name = "rich" },
+ { name = "shellingham" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ae/40/4a3db7990d1f62a53182aa96eaef57aeb2886a27f90a195bc66713565d31/typer-0.27.1.tar.gz", hash = "sha256:a79bef8469a79c45498e7b814ecf8d603cc7644e9acbd9e19cac0334240b18df", size = 203994, upload-time = "2026-08-03T14:41:03.438Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/43/89/9518bc0c3929bee36b3a4a8e3daddd6e03f92f9961c66d4983b837160543/typer-0.27.1-py3-none-any.whl", hash = "sha256:53150287edd11baeb4e4722c8e394fcdf8181c0ae89485cba8d25c778d5edd56", size = 122874, upload-time = "2026-08-03T14:41:04.391Z" },
+]
+
+[[package]]
+name = "types-protobuf"
+version = "7.35.1.20260824"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5b/9a/7725bf5ee3d1da5eebee6d6df2475efb87091b2dcbf85164b396e8bf6904/types_protobuf-7.35.1.20260824.tar.gz", hash = "sha256:9c40a3d4856b7d8e47a085dcf3ee82d06ce470fbad79488dfa49027ee15c619f", size = 69619, upload-time = "2026-08-24T02:53:16.045Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e5/ca/53324903b95d00ad80eaa732a0a195714ebac21b112b62b4e0652524c93e/types_protobuf-7.35.1.20260824-py3-none-any.whl", hash = "sha256:a05660361587d210a3e1fd50e68ea8f25286b52eb342c0f6bf97d83804b05bec", size = 86411, upload-time = "2026-08-24T02:53:14.982Z" },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" },
+]
+
+[[package]]
+name = "typing-inspection"
+version = "0.4.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a3/26/b09b8010994eccc3c09092e6b34058f36a460eea2d4c3e8b910c695975a0/typing_inspection-0.4.4.tar.gz", hash = "sha256:547274fa6b0a561ccf549cc9524b999a578e737d015d8709d021f9d0d13bea47", size = 76928, upload-time = "2026-08-12T12:37:25.997Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/67/81/4add07e5172b7ac40d8ed5ff580409a7801a4fe26d529bdd915401dabfbe/typing_inspection-0.4.4-py3-none-any.whl", hash = "sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147", size = 14750, upload-time = "2026-08-12T12:37:24.648Z" },
+]
+
+[[package]]
+name = "urllib3"
+version = "2.7.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
+]
+
+[[package]]
+name = "uvicorn"
+version = "0.52.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "click" },
+ { name = "h11" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f2/0f/3f86e61397dd33bf2ccf28188c40db6a740658aeebbbf6e7dbc101a1f487/uvicorn-0.52.4.tar.gz", hash = "sha256:73acfee47a0b133c5de13d219492d62d8a31e935f4fe6e41a232451a15379f86", size = 100627, upload-time = "2026-08-19T06:27:41.821Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f1/79/4a20b54ab0491485ccd8c077db2d39187c7f12b3e15485d38a7be37c81b4/uvicorn-0.52.4-py3-none-any.whl", hash = "sha256:f86e41a149d7d05a9969337e3946a9c171c06a5d42680896daaba624aeac8da1", size = 79871, upload-time = "2026-08-19T06:27:40.36Z" },
+]
+
+[package.optional-dependencies]
+standard = [
+ { name = "httptools" },
+ { name = "python-dotenv" },
+ { name = "pyyaml" },
+ { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" },
+ { name = "watchfiles" },
+ { name = "websockets" },
+]
+
+[[package]]
+name = "uvloop"
+version = "0.22.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" },
+ { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" },
+]
+
+[[package]]
+name = "watchfiles"
+version = "1.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "anyio" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/cd/41/5e1a4bb12aac5f1493fa1bdc11154eca3b258ca4eba65d39c473fe19d8e9/watchfiles-1.2.0.tar.gz", hash = "sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838", size = 108252, upload-time = "2026-05-18T04:32:04.251Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d1/4d/70a7feced9f87e2ff26dba42667290f41694fc64646c67261fbb8cab5d5c/watchfiles-1.2.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:01ea8d66f0693b9b60a6541c8d10263091ca9a9060d242f3c1f3143f9aad2c98", size = 399730, upload-time = "2026-05-18T04:31:38.162Z" },
+ { url = "https://files.pythonhosted.org/packages/31/3a/0da302f2307aee316922806ebd5726c542cbd787c938271cf14a074c7daf/watchfiles-1.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ba0480b9a74af058f43b337e937a451e109295c420916d68ad24e3dc02f5e44", size = 392842, upload-time = "2026-05-18T04:30:27.051Z" },
+ { url = "https://files.pythonhosted.org/packages/db/ef/d5bdb705c224dbc256aa0c1ec47bf4e61ec52558f2afb44a71a1fe4d7015/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f34e26a19f91f710c08e0183429f0d1d15df734e6bc78c31e77b9ea9c433658", size = 452989, upload-time = "2026-05-18T04:31:11.945Z" },
+ { url = "https://files.pythonhosted.org/packages/71/29/5495f2c1661949ef7a35e4d71111d129cfe7606414a26887a919d0a55406/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b4e77f6a55f858504069abd35d336a637555c09bca453dde1ee1e5ada8a6a1fb", size = 458978, upload-time = "2026-05-18T04:30:52.606Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/8c/7f9c07c433811c2fffd93e13fdfb7135de9aab5f2ae41be08960fa0047dc/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0cb4d80e212f116474a545c21c912b445f16bb0cef9e6a73a498164223e14e2f", size = 490248, upload-time = "2026-05-18T04:31:36.003Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/11/d93632febc52fbc21be90231bb7c17fd5387f46c9076fd40a5f9c2ae6910/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b974946a10af379d425e2eef5b62f5c6ebeaccf91d45eaad6f5b27ecd4f91aa0", size = 571847, upload-time = "2026-05-18T04:31:10.862Z" },
+ { url = "https://files.pythonhosted.org/packages/55/b4/383173e73aabb07ad1d9c7aa859d95437ac46a6d6a1e11005facda0c9d19/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86bc13c25a8d1fcd70b51d0ce7c9b65e90de5666fcbfd3e34957cc73ee19aeb5", size = 465974, upload-time = "2026-05-18T04:30:17.006Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/6c/89b1a230a78f57c52dd8893adb1f92f94411721b6ec12596c56d98c74356/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca148d73dea36c9763aaa351e4d7a51780ec1584217c45276f4fe8239c768b71", size = 454782, upload-time = "2026-05-18T04:30:35.656Z" },
+ { url = "https://files.pythonhosted.org/packages/24/62/1732118367cfff0a9fce3bf62ff4bfded09ef5df21d9d446b858b3f70a96/watchfiles-1.2.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:c525543d91961c6955b2636b308569e84a1d1c5f5f2932041ab9ef46422f43e3", size = 465182, upload-time = "2026-05-18T04:30:20.846Z" },
+ { url = "https://files.pythonhosted.org/packages/28/96/716f7e5f51339bf22963f3345f9f27d7f3b30e2eadc597e257c881dd3c53/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a204794696ffb8f9b10fba6f7cb5216d42f3b2b71860ccac6b6e42f5f10973b0", size = 629841, upload-time = "2026-05-18T04:31:05.397Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/fe/c40783950fd771ccf66ab3ec2722d188a9af1c7f96c6e811f36e40c6e03f/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:10d86db20695afe7997ac9e1717637d6714a8d0220458c33f3d2061f54cec427", size = 658028, upload-time = "2026-05-18T04:31:48.22Z" },
+ { url = "https://files.pythonhosted.org/packages/71/72/4508db1856d1d87fcbb3b63f4839bab1b5682cb0e8d224d122263c09654a/watchfiles-1.2.0-cp313-cp313-win32.whl", hash = "sha256:eb283ee99e21ad6443c8cdb06ac5b34b1308c329cbdf03fa02b445363714c799", size = 275183, upload-time = "2026-05-18T04:30:59.57Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/36/14b76ca57652e5cc5fd1c11f32a261292c08a0d19a00351013c2549cbfb2/watchfiles-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:a0f27f01bee51861392bb6b7c4fdb290b27d1eb194e9e28788d68102a0e898d9", size = 288059, upload-time = "2026-05-18T04:32:07.937Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/8d/0a85e395398d8d20fadfe5c5d32c726eee17a519e78fb356f2cf7531bffe/watchfiles-1.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:3651aa7058595e9cfb75d35dd5ada2bf9f48a5b8a0f3562821d3e210c507e077", size = 280186, upload-time = "2026-05-18T04:31:54.484Z" },
+ { url = "https://files.pythonhosted.org/packages/37/68/36db056f1fdcc5f07302f56e631774d6835bcd6fa3ace402304621d5f9e5/watchfiles-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:faea288b6f0ab1902ef08f4ca6de005dccf856c4e0c4f21b8c5fce02d90a1b08", size = 399031, upload-time = "2026-05-18T04:30:44.576Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/64/01a9d6f66a82a5c101ce939274106cc72759d62427e153f01edd2b9f87c2/watchfiles-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01859b11fd9fbca670f4d5da00fbac282cfea9bd67a2125d8b2833a3b5617ea9", size = 391205, upload-time = "2026-05-18T04:30:25.413Z" },
+ { url = "https://files.pythonhosted.org/packages/84/2c/0a44fe058cb4bb7b8ede6b6670698bbb7c0400740e378d00022189b7b31d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fff610d7bb2256a317bb1e96f0d7862c7aa8076733ee5df0fd41bbe76a24a4f4", size = 451892, upload-time = "2026-05-18T04:32:14.005Z" },
+ { url = "https://files.pythonhosted.org/packages/67/a1/351e0d56cd35e6488b5c8b4fb11a809a5bc923e8fe8fed9faf8920be0c89/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b141a4891c995a039cd89e9a49e62df1dc8a559a5d1a6e4c7106d16c12777a55", size = 458867, upload-time = "2026-05-18T04:31:22.279Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/7d/9d09605187f1b838998624049fcf8bf47b73c1a3b76901fcac1782f62277/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f22943b7770483f6ea0721c6b11d022947a98eb0acae14694de034f4d0d38925", size = 490217, upload-time = "2026-05-18T04:31:43.657Z" },
+ { url = "https://files.pythonhosted.org/packages/60/5d/a17a16eccb182f04188cd308ec24b1a71a9b5c4e7098269cf35d9fa56d02/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bc6195825b7dcd217968bb1f801a60fd4c16e8eeab5bedc7fe917d7d5995ab4", size = 571458, upload-time = "2026-05-18T04:32:11.875Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/3d/4dd457062083ab1938e5dfd45032eb425cee2ac817287ca8ff4356183e5d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4a4b147f5dca2a5d325a06a832fb43f345751adfbc63204aec30e0d9ca965a2", size = 464707, upload-time = "2026-05-18T04:30:43.492Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/71/ea8c57b128f5383de74d0c7d2d9c57ad7c9a65a930c451bd25d524b295b7/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4543579a9bdb0c9560039b4ffddbdb39545707659fbc430ce4c10f3f68d557f9", size = 454663, upload-time = "2026-05-18T04:30:16.061Z" },
+ { url = "https://files.pythonhosted.org/packages/53/fd/2e812bf938406d7db351f0703ddd3fc6c061cf30d96153a77bc79a943a44/watchfiles-1.2.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:20aa0e708b920bde876a4aa82dc7dd6ebea228a63a67cda6632c2fc87b787efa", size = 463537, upload-time = "2026-05-18T04:31:44.9Z" },
+ { url = "https://files.pythonhosted.org/packages/86/56/d17a7f1dd1bc3035f1072694a551301272f1739c2d8e319c927cb9e29b38/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:d413349d565dab74297f2a63e84a097936be69bf8f3b3801f27f380e32040f44", size = 629194, upload-time = "2026-05-18T04:31:14.141Z" },
+ { url = "https://files.pythonhosted.org/packages/be/06/f1ff66bf5cae50aa4062779a0ecd0bbaf15e466195719074078947d9a17d/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f28b2725eb8cce327b9b3ab02415c853011dc55c95832fe90de6bc56f5315f72", size = 656194, upload-time = "2026-05-18T04:31:47.14Z" },
+]
+
+[[package]]
+name = "websockets"
+version = "15.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" },
+ { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" },
+ { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" },
+ { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" },
+ { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" },
+ { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" },
+]
+
+[[package]]
+name = "yarl"
+version = "1.24.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "idna" },
+ { name = "multidict" },
+ { name = "propcache" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/31/33/ebe9e3d1f86c7a0b51094c0a146392045ca1631d2664889539dec8088a33/yarl-1.24.5.tar.gz", hash = "sha256:e81b83143bee16329c23db3c1b2d82b29892fcbcb849186d2f6e98a5abe9a57f", size = 228679, upload-time = "2026-07-20T02:07:45.435Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e1/63/64ef361967cc983573149dc1515d531db5da8a4c92d22bb833d59e01b313/yarl-1.24.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:79af890482fc94648e8cde4c68620378f7fef60932710fa17a66abc039244da2", size = 135075, upload-time = "2026-07-20T02:05:59.671Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/89/55920fd853ce43e608adbc3962456f0d649d6bb15250dc2988321da0fe1c/yarl-1.24.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:46c2f213e23a04b93a392942d782eb9e413e6ef6bf7c8c53884e599a5c174dcb", size = 97225, upload-time = "2026-07-20T02:06:01.769Z" },
+ { url = "https://files.pythonhosted.org/packages/15/f0/7688d3f2cfff7590df2af38ec46d969f4281a4dddb08a9ad2eafbcdddf98/yarl-1.24.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92ab3e11448f2ff7bf53c5a26eff0edc086898ec8b21fb154b85839ce1d88075", size = 96751, upload-time = "2026-07-20T02:06:03.676Z" },
+ { url = "https://files.pythonhosted.org/packages/05/1a/a851a0f94aaaf379dd4f901bfc80f634280bec51eb260b47363e2a4cd62e/yarl-1.24.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ebb0ec7f17803063d5aeb982f3b1bd2b2f4e4fae6751226cbd6ba1fcfe9e63ff", size = 107960, upload-time = "2026-07-20T02:06:05.699Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/a8/faea066c12f9c77ca0de90641f1655f9dd7b412477bf28c76d692f3aecff/yarl-1.24.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:82632daed195dcc8ea664e8556dc9bdbd671960fb3776bd92806ce05792c2448", size = 103500, upload-time = "2026-07-20T02:06:07.556Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/9c/1e67084c2a6e2f2db0e3be798328cb3be42c0119b621d25461479a224d21/yarl-1.24.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:53e549287ef628fecba270045c9701b0c564563a9b0577d24a4ec75b8ab8040f", size = 115780, upload-time = "2026-07-20T02:06:09.599Z" },
+ { url = "https://files.pythonhosted.org/packages/58/86/1f94664e147474337e3359f52012cf3d02f825f694317b178bfba1078c62/yarl-1.24.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fcd3b77e2f17bbe4ca56ec7bcb07992647d19d0b9c05d84886dcd6f9eb810afd", size = 115308, upload-time = "2026-07-20T02:06:11.352Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/43/8e55ae7538ba5f28ccb3c845c6dd4549cf7016d5992e5326512519107cdd/yarl-1.24.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d46b86567dd4e248c6c159fcbcdcce01e0a5c8a7cd2334a0fff759d0fa075b16", size = 110574, upload-time = "2026-07-20T02:06:13.129Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/ba/a889ec8765cedcf2ac44dcb02d6a21e4861399b243b263c5f2dde27ee740/yarl-1.24.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7f72c74aa99359e27a2ee8d6613fefa28b5f76a983c083074dfc2aaa4ab46213", size = 109914, upload-time = "2026-07-20T02:06:15.243Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/c3/e45f821af67b791c2dbbe4a9f4137a1d33f8d386654a05a0c3f47bdfa25d/yarl-1.24.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3f45789ce415a7ec0820dc4f82925f9b5f7732070be1dec1f5f23ec381435a24", size = 107712, upload-time = "2026-07-20T02:06:17.443Z" },
+ { url = "https://files.pythonhosted.org/packages/02/00/2ab0f42c9857fcb490bfaa6647b14540b53d241ab209f23220b958cc5832/yarl-1.24.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6e73e7fe93f17a7b191f52ec9da9dd8c06a8fe735a1ecbd13b97d1c723bff385", size = 104251, upload-time = "2026-07-20T02:06:19.259Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/70/709d9a286e98af2c7fd8e4e6cada658b5c0e30d87dd7e2a63c2fb5767217/yarl-1.24.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4a36f9becdd4c5c52a20c3e9484128b070b1dcfc8944c006f3a528295a359a9c", size = 115319, upload-time = "2026-07-20T02:06:21.207Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/6c/3eaa515142991fe84cfc483ff986492211f1978f90161ccefdbec919d09b/yarl-1.24.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:7bcbe0fcf850eae67b6b01749815a4f7161c560a844c769ad7b48fcd99f791c4", size = 109163, upload-time = "2026-07-20T02:06:23.006Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/64/711dafce66c323a3144d470547a71c5384c57623308ac8bb5e4b903ac148/yarl-1.24.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:24e861e9630e0daddcb9191fb187f60f034e17a4426f8101279f0c475cd74144", size = 115435, upload-time = "2026-07-20T02:06:24.923Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/f3/9b9d0e6d84bea851eb1ba99e4bdc755b86fd813e49ec86dfe42f26befdef/yarl-1.24.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9335a099ad87287c37fe5d1a982ff392fa5efe5d14b40a730b1ec1d6a41382b4", size = 110691, upload-time = "2026-07-20T02:06:26.973Z" },
+ { url = "https://files.pythonhosted.org/packages/86/e4/62a06b7e87c4246ac76b7c2da136f972eb4a3a1fc94abb07e7022d6fdb0a/yarl-1.24.5-cp313-cp313-win_amd64.whl", hash = "sha256:2dbe06fc16bc91502bca713704022182e5729861ae00277c3a23354b40929740", size = 97454, upload-time = "2026-07-20T02:06:29.163Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/c9/5fc8025b318ab10db413b61056bd0d95c557a70e8df4210c7511f866329c/yarl-1.24.5-cp313-cp313-win_arm64.whl", hash = "sha256:6b8536851f9f65e7f00c7a1d49ba7f2be0ffe2c11555367fc9f50d9f842410a1", size = 92813, upload-time = "2026-07-20T02:06:31.113Z" },
+ { url = "https://files.pythonhosted.org/packages/61/02/962c1cbfc401a30c1d034dc67ff395f64b52302c6d62de556c1fca99acc0/yarl-1.24.5-py3-none-any.whl", hash = "sha256:a33700d13d9b7d84fd10947b09ff69fb9a792e519c8cb9764a3ca70baa6c23a7", size = 58612, upload-time = "2026-07-20T02:07:43.461Z" },
+]
From 0ffed50feca6d9977316b94c47088232d80cfebb Mon Sep 17 00:00:00 2001
From: Young Han <110819238+seyeong-han@users.noreply.github.com>
Date: Tue, 25 Aug 2026 12:25:05 -0700
Subject: [PATCH 2/5] Add Muse Glimmer macOS CI
---
.github/workflows/muse-glimmer-macos.yml | 161 +++++++++++++++++++++++
README.md | 7 +
2 files changed, 168 insertions(+)
create mode 100644 .github/workflows/muse-glimmer-macos.yml
diff --git a/.github/workflows/muse-glimmer-macos.yml b/.github/workflows/muse-glimmer-macos.yml
new file mode 100644
index 0000000000..533d008727
--- /dev/null
+++ b/.github/workflows/muse-glimmer-macos.yml
@@ -0,0 +1,161 @@
+# Copyright (c) Meta Platforms, Inc. and affiliates.
+# All rights reserved.
+#
+# This source code is licensed under the BSD-style license found in the
+# LICENSE file in the root directory of this source tree.
+
+name: Muse Glimmer macOS
+
+on:
+ pull_request:
+ paths:
+ - "muse_glimmer/macos/**"
+ - ".github/workflows/muse-glimmer-macos.yml"
+ push:
+ branches: [main]
+ paths:
+ - "muse_glimmer/macos/**"
+ - ".github/workflows/muse-glimmer-macos.yml"
+
+permissions:
+ contents: read
+
+jobs:
+ python:
+ runs-on: macos-14
+ defaults:
+ run:
+ working-directory: muse_glimmer/macos
+ steps:
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
+ with:
+ python-version: "3.13"
+ - uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e
+ with:
+ enable-cache: true
+ cache-dependency-glob: muse_glimmer/macos/uv.lock
+ - name: Sync Python dependencies
+ run: uv sync --all-packages --all-groups --frozen
+ - name: Check Python lint
+ run: uv run --all-packages --all-groups ruff check .
+ - name: Check Python formatting
+ run: uv run --all-packages --all-groups ruff format --check .
+ - name: Run Python tests
+ run: uv run --all-packages --all-groups pytest
+ - name: Validate manifests
+ run: uv run --all-packages --all-groups python -m scripts.validate_manifests
+ - name: Check publication contents
+ run: uv run --all-packages --all-groups python -m scripts.publication_check
+ - name: Build Python packages
+ run: uv build --all-packages --out-dir "$RUNNER_TEMP/muse-glimmer-dist"
+ - name: Verify package LICENSE payloads
+ env:
+ DIST_DIR: ${{ runner.temp }}/muse-glimmer-dist
+ run: |
+ uv run --all-packages --all-groups python - <<'PY'
+ import os
+ import tarfile
+ import zipfile
+ from pathlib import Path
+
+ dist = Path(os.environ["DIST_DIR"])
+ expected = {
+ "livekit_plugins_executorch": Path(
+ "packages/livekit-plugins-executorch/LICENSE"
+ ).read_bytes(),
+ "muse_glimmer_token_service": Path("apps/token-service/LICENSE").read_bytes(),
+ "muse_glimmer_worker": Path("apps/worker/LICENSE").read_bytes(),
+ }
+ archives = list(dist.glob("*.whl")) + list(dist.glob("*.tar.gz"))
+ for package, license_payload in expected.items():
+ package_archives = [archive for archive in archives if archive.name.startswith(package)]
+ if len(package_archives) != 2:
+ raise SystemExit(
+ f"expected wheel and sdist for {package}, found {len(package_archives)} archives"
+ )
+ for archive in package_archives:
+ if archive.suffix == ".whl":
+ with zipfile.ZipFile(archive) as built:
+ names = built.namelist()
+ payloads = [
+ built.read(name)
+ for name in names
+ if Path(name).name == "LICENSE"
+ ]
+ else:
+ with tarfile.open(archive, mode="r:gz") as built:
+ members = built.getmembers()
+ names = [member.name for member in members]
+ payloads = [
+ built.extractfile(member).read()
+ for member in members
+ if member.isfile() and Path(member.name).name == "LICENSE"
+ ]
+ if license_payload not in payloads:
+ raise SystemExit(f"source LICENSE payload missing from {archive.name}")
+ if any(Path(name).name == "NOTICE" for name in names):
+ raise SystemExit(f"obsolete NOTICE payload found in {archive.name}")
+ print("Verified BSD LICENSE payloads in all Python package archives.")
+ PY
+
+ web:
+ runs-on: macos-14
+ defaults:
+ run:
+ working-directory: muse_glimmer/macos/apps/web
+ steps:
+ - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
+ - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
+ with:
+ node-version: "22.12.0"
+ cache: npm
+ cache-dependency-path: muse_glimmer/macos/apps/web/package-lock.json
+ - name: Install web dependencies
+ run: npm ci
+ - name: Lint web app
+ run: npm run lint
+ - name: Type-check web app
+ run: npm run typecheck
+ - name: Test web app
+ run: npm test
+ - name: Build web app
+ run: npm run build
+ - name: Audit web dependencies
+ run: npm audit --audit-level=high
+ - name: Scan browser bundle for private runtime data
+ run: |
+ node --input-type=module <<'JS'
+ import { readdir, readFile } from "node:fs/promises";
+ import { join } from "node:path";
+
+ const forbidden = new Map([
+ ["LLM port", /127\.0\.0\.1:8000/],
+ ["model variant", /muse-glimmer-k-quant|17G|128K|dflash/i],
+ ["model runtime", /Parakeet|Supertonic|MUSE_GLIMMER_|PARAKEET_|SUPERTONIC_/],
+ ["private path", /\/Users\/|\.local\/artifacts|\.pte\b/],
+ ["credential name", /LIVEKIT_API_SECRET/],
+ ["cloud URL", /wss:\/\/|https:\/\/[^\s"']*livekit/i],
+ ]);
+
+ async function* files(directory) {
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
+ const path = join(directory, entry.name);
+ if (entry.isDirectory()) {
+ yield* files(path);
+ } else if (entry.isFile()) {
+ yield path;
+ }
+ }
+ }
+
+ for await (const path of files("dist")) {
+ const contents = await readFile(path, "utf8");
+ for (const [label, pattern] of forbidden) {
+ if (pattern.test(contents)) {
+ throw new Error(`browser bundle contains forbidden ${label}: ${path}`);
+ }
+ }
+ }
+ console.log("Browser bundle privacy scan passed.");
+ JS
diff --git a/README.md b/README.md
index 5ebde13396..cf280529fa 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,11 @@
Example apps and demos using PyTorch's [ExecuTorch](https://github.com/pytorch/executorch) framework.
+## Full applications
+
+- [Muse Glimmer Voice Agent for macOS](muse_glimmer/macos/README.md): a fully local
+ Apple silicon voice app whose native setup and release remain development-gated
+ pending a compatible pinned ExecuTorch commit.
+
## License
+
ExecuTorch is BSD licensed, as found in the LICENSE file.
From 7a6b87353fb3ef42602d7e262b09e9752fdbd2fd Mon Sep 17 00:00:00 2001
From: Young Han <110819238+seyeong-han@users.noreply.github.com>
Date: Tue, 25 Aug 2026 12:48:25 -0700
Subject: [PATCH 3/5] Harden Muse Glimmer release validation
---
.github/workflows/muse-glimmer-macos.yml | 29 +++++++++++++
.../worker/src/muse_glimmer_worker/cli.py | 22 +++++++++-
.../macos/apps/worker/tests/test_cli.py | 26 +++++++++++
.../macos/scripts/validate_manifests.py | 43 +++++++++++++++----
.../macos/tests/test_validate_manifests.py | 21 +++++++++
5 files changed, 131 insertions(+), 10 deletions(-)
diff --git a/.github/workflows/muse-glimmer-macos.yml b/.github/workflows/muse-glimmer-macos.yml
index 533d008727..a16aa24af3 100644
--- a/.github/workflows/muse-glimmer-macos.yml
+++ b/.github/workflows/muse-glimmer-macos.yml
@@ -43,6 +43,35 @@ jobs:
run: uv run --all-packages --all-groups ruff format --check .
- name: Run Python tests
run: uv run --all-packages --all-groups pytest
+ - name: Read release compatibility state
+ id: compatibility
+ run: |
+ uv run --all-packages --all-groups python - <<'PY' >> "$GITHUB_OUTPUT"
+ import json
+ import re
+ from pathlib import Path
+
+ compatibility = json.loads(
+ Path("config/dependencies/compatibility.lock.json").read_text()
+ )
+ ready = bool(compatibility["ready_for_release"])
+ commit = compatibility["executorch"]["commit"] or ""
+ if ready and re.fullmatch(r"[0-9a-f]{40}", commit) is None:
+ raise SystemExit("release-ready compatibility requires a full commit SHA")
+ print(f"ready={str(ready).lower()}")
+ print(f"commit={commit}")
+ PY
+ - name: Check out release ExecuTorch revision
+ if: steps.compatibility.outputs.ready == 'true'
+ env:
+ EXECUTORCH_COMMIT: ${{ steps.compatibility.outputs.commit }}
+ run: |
+ test -n "$EXECUTORCH_COMMIT"
+ git clone --filter=blob:none --no-checkout \
+ https://github.com/pytorch/executorch.git \
+ "$RUNNER_TEMP/executorch"
+ git -C "$RUNNER_TEMP/executorch" checkout --detach "$EXECUTORCH_COMMIT"
+ echo "GLIMMER_EXECUTORCH_ROOT=$RUNNER_TEMP/executorch" >> "$GITHUB_ENV"
- name: Validate manifests
run: uv run --all-packages --all-groups python -m scripts.validate_manifests
- name: Check publication contents
diff --git a/muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/cli.py b/muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/cli.py
index db51ac91a2..2aaa00c103 100644
--- a/muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/cli.py
+++ b/muse_glimmer/macos/apps/worker/src/muse_glimmer_worker/cli.py
@@ -434,7 +434,12 @@ def _attach_provider_reporting(
) -> None:
def metrics(stage: str, event: object) -> None:
payload = event.model_dump(mode="json") if hasattr(event, "model_dump") else str(event)
- reporter.emit("provider_metrics", stage=stage, metrics=payload, message=stage)
+ reporter.emit(
+ "provider_metrics",
+ stage=stage,
+ metrics=_redact_payload(payload, config),
+ message=stage,
+ )
def error(stage: str, event: object) -> None:
exception = getattr(event, "error", RuntimeError(str(event)))
@@ -457,6 +462,21 @@ def error(stage: str, event: object) -> None:
provider.on("error", lambda event, stage=stage: error(stage, event))
+def _redact_payload(value: object, config: GlimmerConfig | None) -> object:
+ if isinstance(value, str):
+ return _redact_text(value, config)
+ if isinstance(value, dict):
+ return {
+ _redact_text(str(key), config): _redact_payload(item, config)
+ for key, item in value.items()
+ }
+ if isinstance(value, (list, tuple)):
+ return [_redact_payload(item, config) for item in value]
+ if value is None or isinstance(value, (bool, int, float)):
+ return value
+ return _redact_text(str(value), config)
+
+
def _redact_text(value: str, config: GlimmerConfig | None) -> str:
replacements: dict[str, str] = {str(Path.home()): "~"}
for secret_name in ("LIVEKIT_API_SECRET", "MUSE_GLIMMER_API_KEY"):
diff --git a/muse_glimmer/macos/apps/worker/tests/test_cli.py b/muse_glimmer/macos/apps/worker/tests/test_cli.py
index e126ef4dbc..e89df262f9 100644
--- a/muse_glimmer/macos/apps/worker/tests/test_cli.py
+++ b/muse_glimmer/macos/apps/worker/tests/test_cli.py
@@ -4,6 +4,7 @@
import sys
import zipfile
from pathlib import Path
+from types import SimpleNamespace
from muse_glimmer_worker import cli
@@ -41,3 +42,28 @@ def test_issue_bundle_omits_runtime_log(tmp_path: Path) -> None:
with zipfile.ZipFile(bundle) as archive:
assert set(archive.namelist()) == {"report.json", "events.jsonl"}
+
+
+def test_provider_metrics_redact_nested_artifact_paths(tmp_path: Path) -> None:
+ model = tmp_path / "private" / "model.pte"
+ config = SimpleNamespace(
+ muse_glimmer_api_key="local-secret",
+ parakeet_helper_path=tmp_path / "bin" / "parakeet_helper",
+ parakeet_model_path=model,
+ parakeet_tokenizer_path=tmp_path / "private" / "tokenizer.model",
+ parakeet_delegate_data_path=None,
+ supertonic_runner_path=tmp_path / "bin" / "supertonic_runner",
+ supertonic_pte_path=tmp_path / "private" / "supertonic.pte",
+ supertonic_asset_dir=tmp_path / "private" / "assets",
+ supertonic_voice_style_path=tmp_path / "private" / "voice.json",
+ )
+
+ redacted = cli._redact_payload(
+ {"metadata": {"model_name": model}, "details": ["local-secret"]},
+ config,
+ )
+
+ serialized = str(redacted)
+ assert str(tmp_path) not in serialized
+ assert "local-secret" not in serialized
+ assert ".../model.pte" in serialized
diff --git a/muse_glimmer/macos/scripts/validate_manifests.py b/muse_glimmer/macos/scripts/validate_manifests.py
index 9687e1b0ab..8a5fb3b4d6 100644
--- a/muse_glimmer/macos/scripts/validate_manifests.py
+++ b/muse_glimmer/macos/scripts/validate_manifests.py
@@ -25,6 +25,12 @@
_REQUIRED_CAPABILITIES = {"supports_cancel", "supertonic_server_jsonl"}
_REQUIRED_GATES = {"supertonic_runtime", "supports_cancel", "supertonic_server_jsonl"}
_GATE_STATUSES = {"landed", "pending", "unsubmitted"}
+_EXECUTORCH_REPOSITORY = "https://github.com/pytorch/executorch.git"
+_EXECUTORCH_ARTIFACT_ROLES = {
+ "parakeet_helper",
+ "muse_glimmer_worker",
+ "supertonic_runner",
+}
def _load(path: Path) -> dict[str, object]:
@@ -98,6 +104,8 @@ def _validate_compatibility(
executorch = compatibility.get("executorch")
if not isinstance(executorch, dict):
raise RuntimeError("compatibility lock has no ExecuTorch configuration")
+ if executorch.get("repository") != _EXECUTORCH_REPOSITORY:
+ raise RuntimeError("compatibility lock must use the official ExecuTorch repository")
capabilities = executorch.get("required_capabilities")
if not isinstance(capabilities, list) or not set(capabilities) >= _REQUIRED_CAPABILITIES:
raise RuntimeError("compatibility lock omits required runtime capabilities")
@@ -146,6 +154,28 @@ def _validate_compatibility(
)
+def _validate_release_artifacts(artifacts: object, final_executorch_commit: object) -> None:
+ if not isinstance(artifacts, list) or not isinstance(final_executorch_commit, str):
+ raise RuntimeError("release-ready artifact validation requires an ExecuTorch commit")
+ for artifact in artifacts:
+ if not isinstance(artifact, dict):
+ raise RuntimeError("release-ready artifact entries must be objects")
+ role = artifact.get("role")
+ if not all(artifact.get(field) for field in ("source", "revision", "sha256")):
+ raise RuntimeError(f"release-ready artifact provenance is incomplete: {role}")
+ if artifact.get("size_bytes") is None or str(artifact.get("license", "")).startswith(
+ "See "
+ ):
+ raise RuntimeError(f"release-ready artifact metadata is incomplete: {role}")
+ source = artifact.get("source")
+ if role in _EXECUTORCH_ARTIFACT_ROLES and source != "executorch":
+ raise RuntimeError(f"ExecuTorch runtime artifact has invalid source: {role}")
+ if source == "executorch" and artifact.get("revision") != final_executorch_commit:
+ raise RuntimeError(
+ f"ExecuTorch-built artifact must match the final compatibility commit: {role}"
+ )
+
+
def main() -> int:
_validate_artifacts()
schema = _load(ROOT / "artifacts/manifest.schema.json")
@@ -165,15 +195,10 @@ def main() -> int:
)
_validate_compatibility(compatibility, release_checkout=release_checkout)
if compatibility.get("ready_for_release"):
- for artifact in _load(ARTIFACT_LOCK)["artifacts"]:
- if not all(artifact.get(field) for field in ("source", "revision", "sha256")):
- raise RuntimeError(
- f"release-ready artifact provenance is incomplete: {artifact['role']}"
- )
- if artifact.get("size_bytes") is None or str(artifact["license"]).startswith("See "):
- raise RuntimeError(
- f"release-ready artifact metadata is incomplete: {artifact['role']}"
- )
+ _validate_release_artifacts(
+ _load(ARTIFACT_LOCK)["artifacts"],
+ compatibility["executorch"].get("commit"),
+ )
toolchain = _load(TOOLCHAIN_LOCK)
if toolchain.get("platform") != {"system": "Darwin", "machine": "arm64"}:
diff --git a/muse_glimmer/macos/tests/test_validate_manifests.py b/muse_glimmer/macos/tests/test_validate_manifests.py
index c9a06e143f..fe2d7841ae 100644
--- a/muse_glimmer/macos/tests/test_validate_manifests.py
+++ b/muse_glimmer/macos/tests/test_validate_manifests.py
@@ -122,3 +122,24 @@ def test_release_ready_verifies_all_landed_gate_commits(
validate_manifests._validate_compatibility(value, release_checkout=tmp_path)
assert calls == [(tmp_path, "b" * 40, ("1" * 40, "2" * 40, "3" * 40))]
+
+
+def test_executorch_artifact_revision_must_match_final_commit() -> None:
+ artifact = {
+ "role": "supertonic_runner",
+ "source": "executorch",
+ "revision": "a" * 40,
+ "sha256": "c" * 64,
+ "size_bytes": 1,
+ "license": "BSD-3-Clause",
+ }
+
+ with pytest.raises(RuntimeError, match="must match the final compatibility commit"):
+ validate_manifests._validate_release_artifacts([artifact], "b" * 40)
+
+ artifact["revision"] = "b" * 40
+ validate_manifests._validate_release_artifacts([artifact], "b" * 40)
+
+ artifact["source"] = "other"
+ with pytest.raises(RuntimeError, match="runtime artifact has invalid source"):
+ validate_manifests._validate_release_artifacts([artifact], "b" * 40)
From 1ebc1bc9e6453256fbc6cbcbe3f417d27c3b563e Mon Sep 17 00:00:00 2001
From: Young Han <110819238+seyeong-han@users.noreply.github.com>
Date: Thu, 27 Aug 2026 00:56:13 -0700
Subject: [PATCH 4/5] Record landed LLM cancellation support
---
.../macos/config/dependencies/compatibility.lock.json | 6 +++---
muse_glimmer/macos/docs/upstream-pins.md | 4 ++--
muse_glimmer/macos/tests/test_validate_manifests.py | 11 ++++++++---
3 files changed, 13 insertions(+), 8 deletions(-)
diff --git a/muse_glimmer/macos/config/dependencies/compatibility.lock.json b/muse_glimmer/macos/config/dependencies/compatibility.lock.json
index cae25cfa83..5f005bb32d 100644
--- a/muse_glimmer/macos/config/dependencies/compatibility.lock.json
+++ b/muse_glimmer/macos/config/dependencies/compatibility.lock.json
@@ -18,9 +18,9 @@
"commit": "81969a92dd2e5515fa23ccdf9d87346cf3ba2ba2"
},
"supports_cancel": {
- "status": "pending",
+ "status": "landed",
"pull_request": "https://github.com/pytorch/executorch/pull/22070",
- "commit": null
+ "commit": "5bd86e50fcd986999e4c09b82de040a3ba224466"
},
"supertonic_server_jsonl": {
"status": "unsubmitted",
@@ -35,5 +35,5 @@
"server_requirement": ">=1.9,<2"
},
"ready_for_release": false,
- "release_blocker": "Land every required capability and select one verified descendant ExecuTorch commit."
+ "release_blocker": "Land the persistent Supertonic JSONL protocol and select one verified descendant ExecuTorch commit."
}
diff --git a/muse_glimmer/macos/docs/upstream-pins.md b/muse_glimmer/macos/docs/upstream-pins.md
index d49bd0e864..6549e8f81a 100644
--- a/muse_glimmer/macos/docs/upstream-pins.md
+++ b/muse_glimmer/macos/docs/upstream-pins.md
@@ -10,8 +10,8 @@ commit contains every required interface.
base Supertonic export and native runtime landed at
`81969a92dd2e5515fa23ccdf9d87346cf3ba2ba2`.
- ExecuTorch PR [#22070](https://github.com/pytorch/executorch/pull/22070):
- bounded generic LLM worker cancellation and health propagation are pending
- merge. The application will record the public landed commit after merge.
+ bounded generic LLM worker cancellation and health propagation landed at
+ `5bd86e50fcd986999e4c09b82de040a3ba224466`.
- Persistent Supertonic JSONL mode: the follow-up is not submitted yet. Its
protocol-v1 ready frame must report `sample_rate: 44100` together with load
and warmup timing so the Python adapter and native runtime enforce one schema.
diff --git a/muse_glimmer/macos/tests/test_validate_manifests.py b/muse_glimmer/macos/tests/test_validate_manifests.py
index fe2d7841ae..c257b63843 100644
--- a/muse_glimmer/macos/tests/test_validate_manifests.py
+++ b/muse_glimmer/macos/tests/test_validate_manifests.py
@@ -5,6 +5,7 @@
import pytest
from scripts import validate_manifests
+from scripts.repository import landed_gate_commits
@pytest.fixture
@@ -29,9 +30,9 @@ def compatibility() -> dict[str, object]:
"commit": "81969a92dd2e5515fa23ccdf9d87346cf3ba2ba2",
},
"supports_cancel": {
- "status": "pending",
+ "status": "landed",
"pull_request": "https://github.com/pytorch/executorch/pull/22070",
- "commit": None,
+ "commit": "5bd86e50fcd986999e4c09b82de040a3ba224466",
},
"supertonic_server_jsonl": {
"status": "unsubmitted",
@@ -54,6 +55,10 @@ def _gates(compatibility: dict[str, object]) -> dict[str, dict[str, object]]:
def test_accepts_development_gated_capabilities(compatibility: dict[str, object]) -> None:
validate_manifests._validate_compatibility(compatibility)
+ assert landed_gate_commits(compatibility) == (
+ "5bd86e50fcd986999e4c09b82de040a3ba224466",
+ "81969a92dd2e5515fa23ccdf9d87346cf3ba2ba2",
+ )
def test_landed_gate_requires_commit(compatibility: dict[str, object]) -> None:
@@ -66,7 +71,7 @@ def test_landed_gate_requires_commit(compatibility: dict[str, object]) -> None:
def test_unlanded_gate_rejects_commit(compatibility: dict[str, object]) -> None:
value = deepcopy(compatibility)
- _gates(value)["supports_cancel"]["commit"] = "a" * 40
+ _gates(value)["supertonic_server_jsonl"]["commit"] = "a" * 40
with pytest.raises(RuntimeError, match="unlanded compatibility gate cannot have a commit"):
validate_manifests._validate_compatibility(value)
From 42a992b8a783b01c65fdfb30a9f8c709d81d1127 Mon Sep 17 00:00:00 2001
From: Young Han <110819238+seyeong-han@users.noreply.github.com>
Date: Thu, 27 Aug 2026 01:28:40 -0700
Subject: [PATCH 5/5] Track Supertonic JSONL follow-up
---
.../macos/config/dependencies/compatibility.lock.json | 6 +++---
muse_glimmer/macos/docs/upstream-pins.md | 7 ++++---
2 files changed, 7 insertions(+), 6 deletions(-)
diff --git a/muse_glimmer/macos/config/dependencies/compatibility.lock.json b/muse_glimmer/macos/config/dependencies/compatibility.lock.json
index 5f005bb32d..580a5a3c37 100644
--- a/muse_glimmer/macos/config/dependencies/compatibility.lock.json
+++ b/muse_glimmer/macos/config/dependencies/compatibility.lock.json
@@ -23,10 +23,10 @@
"commit": "5bd86e50fcd986999e4c09b82de040a3ba224466"
},
"supertonic_server_jsonl": {
- "status": "unsubmitted",
- "pull_request": null,
+ "status": "pending",
+ "pull_request": "https://github.com/pytorch/executorch/pull/22208",
"commit": null,
- "blocker": "Protocol v1 ready frame must report sample_rate 44100."
+ "blocker": "Merge the protocol-v1 server with a ready frame that reports sample_rate 44100."
}
}
},
diff --git a/muse_glimmer/macos/docs/upstream-pins.md b/muse_glimmer/macos/docs/upstream-pins.md
index 6549e8f81a..f1cc0fdc43 100644
--- a/muse_glimmer/macos/docs/upstream-pins.md
+++ b/muse_glimmer/macos/docs/upstream-pins.md
@@ -12,9 +12,10 @@ commit contains every required interface.
- ExecuTorch PR [#22070](https://github.com/pytorch/executorch/pull/22070):
bounded generic LLM worker cancellation and health propagation landed at
`5bd86e50fcd986999e4c09b82de040a3ba224466`.
-- Persistent Supertonic JSONL mode: the follow-up is not submitted yet. Its
- protocol-v1 ready frame must report `sample_rate: 44100` together with load
- and warmup timing so the Python adapter and native runtime enforce one schema.
+- ExecuTorch PR [#22208](https://github.com/pytorch/executorch/pull/22208):
+ persistent Supertonic JSONL mode is pending review. Its protocol-v1 ready
+ frame reports `sample_rate: 44100` together with load and warmup timing so the
+ Python adapter and native runtime enforce one schema.
The merged base Supertonic runtime alone is not a release pin. A temporary
public PR commit may be used for local development only when it is pinned by