From bdfd30955d2c19b14cc2db4f45e753d1be3c558b Mon Sep 17 00:00:00 2001 From: Faizan Naeem Date: Fri, 28 Aug 2026 14:14:55 +0500 Subject: [PATCH 1/9] feat(media): add filename and readiness filters to shared listing (#126) * Added Search filters,Added file names,Implemented CLI,MCP and HTTP projections * Fixed the issue of import in the file named mcp.py * Fixed the same import issue in the file named ports.py * Fixes of media_service.py and test_frontend.py as proposed by the Reviewer * Missed the service variable that was defined at the top of the function * align workspace actions, cursor scope, and ready-only frontend listing * Minor changes * fix(media): preserve filtered pagination and matching --------- Co-authored-by: Talha --- src/vidxp/api_routes/media.py | 16 +++- src/vidxp/api_routes/platform.py | 16 +++- src/vidxp/application_models.py | 9 ++ src/vidxp/cli_commands/media.py | 16 +++- src/vidxp/control_plane.py | 5 +- src/vidxp/frontend.py | 6 +- src/vidxp/infrastructure/sql_catalog.py | 77 ++++++++++++++--- src/vidxp/mcp.py | 25 +++++- src/vidxp/media_service.py | 23 +++++- src/vidxp/ports.py | 10 ++- tests/test_api.py | 51 ++++++++++++ tests/test_cli.py | 19 +++++ tests/test_control_plane.py | 59 +++++++++++++ tests/test_frontend.py | 8 +- tests/test_mcp.py | 28 +++++++ tests/test_media_catalog.py | 105 ++++++++++++++++++++++++ tests/test_media_services.py | 62 +++++++++++++- 17 files changed, 508 insertions(+), 27 deletions(-) diff --git a/src/vidxp/api_routes/media.py b/src/vidxp/api_routes/media.py index 0122bd22..504af0f7 100644 --- a/src/vidxp/api_routes/media.py +++ b/src/vidxp/api_routes/media.py @@ -26,6 +26,7 @@ from vidxp.api_models import UploadIntentResponse from vidxp.composition import HttpApplicationContext from vidxp.core.identifiers import MediaId +from vidxp.core.media import MediaState router = APIRouter(prefix="/media", tags=["media"]) @@ -176,9 +177,22 @@ def list_media( str | None, Query(min_length=1, max_length=512), ] = None, + filename: Annotated[ + str | None, + Query(min_length=1), + ] = None, + state: Annotated[ + MediaState | None, + Query(), + ] = None, ) -> MediaPage: return service.application.list_media( - ListMediaCommand(page_size=page_size, cursor=cursor) + ListMediaCommand( + page_size=page_size, + cursor=cursor, + filename=filename, + state=state, + ) ) diff --git a/src/vidxp/api_routes/platform.py b/src/vidxp/api_routes/platform.py index 1e3be995..56b06a8e 100644 --- a/src/vidxp/api_routes/platform.py +++ b/src/vidxp/api_routes/platform.py @@ -11,6 +11,7 @@ WorkspaceOverview, ) from vidxp.composition import HttpApplicationContext +from vidxp.core.media import MediaState router = APIRouter( @@ -32,9 +33,22 @@ def workspace( str | None, Query(min_length=1, max_length=512), ] = None, + filename: Annotated[ + str | None, + Query(min_length=1), + ] = None, + state: Annotated[ + MediaState | None, + Query(), + ] = None, ) -> WorkspaceOverview: return service.application.workspace( - ListMediaCommand(page_size=page_size, cursor=cursor) + ListMediaCommand( + page_size=page_size, + cursor=cursor, + filename=filename, + state=state, + ) ) diff --git a/src/vidxp/application_models.py b/src/vidxp/application_models.py index 585205d4..e2bb1eb7 100644 --- a/src/vidxp/application_models.py +++ b/src/vidxp/application_models.py @@ -398,6 +398,15 @@ class ListMediaCommand(ApplicationModel): max_length=512, description="Opaque next_cursor from the previous list_media page.", ) + filename: str | None = Field( + default=None, + min_length=1, + description="Filter media records by filename.", + ) + state: MediaState | None = Field( + default=None, + description="Filter media records by readiness/state.", + ) class MediaPage(Page[MediaAsset]): diff --git a/src/vidxp/cli_commands/media.py b/src/vidxp/cli_commands/media.py index dcd32344..3905b4be 100644 --- a/src/vidxp/cli_commands/media.py +++ b/src/vidxp/cli_commands/media.py @@ -16,6 +16,7 @@ require_media_runtime, state_from_context, ) +from vidxp.core.media import MediaState app = typer.Typer(no_args_is_help=True, help="Import and inspect local media.") @@ -68,6 +69,14 @@ def list_media( str | None, typer.Option("--cursor", help="Cursor returned by the previous page."), ] = None, + filename: Annotated[ + str | None, + typer.Option("--filename", help="Filter media by filename."), + ] = None, + media_state: Annotated[ + MediaState | None, + typer.Option("--state", help="Filter media by readiness/state."), + ] = None, json_output: Annotated[ bool, typer.Option("--json", help="Emit machine-readable JSON."), @@ -77,7 +86,12 @@ def list_media( state = state_from_context(ctx) page = state.service.list_media( - ListMediaCommand(page_size=limit, cursor=cursor) + ListMediaCommand( + page_size=limit, + cursor=cursor, + filename=filename, + state=media_state, + ) ) assets = page.items payload = page.model_dump(mode="json") diff --git a/src/vidxp/control_plane.py b/src/vidxp/control_plane.py index 08278d0a..91150bff 100644 --- a/src/vidxp/control_plane.py +++ b/src/vidxp/control_plane.py @@ -117,6 +117,9 @@ def list_media(self, command: ListMediaCommand) -> MediaPage: @application_boundary def workspace(self, command: ListMediaCommand) -> WorkspaceOverview: page = self.list_media(command) + # Workspace actions are repository-level guidance, so compare against + # repository-wide totals rather than a potentially filtered page total. + repository_media_total = self.list_media(ListMediaCommand(page_size=1)).total index = self.index_status() snapshot = self._read_active_snapshot() capabilities = self.list_capabilities() @@ -166,7 +169,7 @@ def workspace(self, command: ListMediaCommand) -> WorkspaceOverview: next_actions = [] if page.total == 0: next_actions.append("register_media") - if page.total > len(indexed_media) or any( + if repository_media_total > len(indexed_media) or any( item.media_id not in indexed_media for item in page.items ): next_actions.append("index_media") diff --git a/src/vidxp/frontend.py b/src/vidxp/frontend.py index b9f095df..a9fa7f20 100644 --- a/src/vidxp/frontend.py +++ b/src/vidxp/frontend.py @@ -754,7 +754,7 @@ def _select_video(busy, media_id, media_page): assets = tuple( asset for asset in (media_page.items if media_page is not None else ()) - if asset.state == MediaState.ready + if getattr(asset, "state", None) == MediaState.ready ) media_id = _default_media_id(media_id, assets) if media_id is not None: @@ -998,7 +998,9 @@ def run(): media_id = indexed_media[0] st.session_state[MEDIA_ID_KEY] = media_id try: - media_page = service.list_media(ListMediaCommand(page_size=100)) + media_page = service.list_media( + ListMediaCommand(page_size=100, state=MediaState.ready) + ) except ApplicationError: media_page = None st.warning( diff --git a/src/vidxp/infrastructure/sql_catalog.py b/src/vidxp/infrastructure/sql_catalog.py index c4336652..c35a59ab 100644 --- a/src/vidxp/infrastructure/sql_catalog.py +++ b/src/vidxp/infrastructure/sql_catalog.py @@ -68,6 +68,41 @@ def _record(model: Any, value: Any) -> Any: return model.model_validate(_payload(value), strict=False) +def _escape_like_pattern(value: str) -> str: + escaped = ( + value.replace("\\", "\\\\") + .replace("%", "\\%") + .replace("_", "\\_") + ) + return f"%{escaped}%" + + +def _media_payload_text(key: str): + return media.c.payload[key].as_string() + + +def _media_list_conditions( + *, + dialect_name: str, + filename: str | None, + state: MediaState | None, +) -> tuple[Any, ...]: + conditions: list[Any] = [] + if state is not None: + conditions.append(_media_payload_text("state") == state.value) + if filename is not None: + filename_text = _media_payload_text("original_filename") + if dialect_name == "postgresql": + filename_text = filename_text.collate("pg_unicode_fast") + conditions.append( + func.casefold(filename_text).like( + _escape_like_pattern(filename.casefold()), + escape="\\", + ) + ) + return tuple(conditions) + + def _upload_record(row: Any) -> UploadIntentRecord: return UploadIntentRecord( intent_id=row.intent_id, @@ -180,6 +215,12 @@ def transaction(self) -> Iterator[Connection]: @staticmethod def _configure_sqlite(dbapi_connection: Any, _record: Any) -> None: + dbapi_connection.create_function( + "casefold", + 1, + str.casefold, + deterministic=True, + ) cursor = dbapi_connection.cursor() try: cursor.execute("PRAGMA foreign_keys = ON") @@ -316,28 +357,44 @@ def list_media( *, limit: int, offset: int = 0, + filename: str | None = None, + state: MediaState | None = None, ) -> tuple[MediaRecord, ...]: if limit <= 0 or offset < 0: raise ValueError("limit must be positive and offset nonnegative") + conditions = _media_list_conditions( + dialect_name=self.engine.dialect.name, + filename=filename, + state=state, + ) + query = select(media.c.payload).order_by(media.c.created_at, media.c.media_id) + if conditions: + query = query.where(and_(*conditions)) with self.engine.connect() as connection: payloads = connection.execute( - select(media.c.payload) - .order_by(media.c.created_at, media.c.media_id) - .limit(limit) - .offset(offset) + query.limit(limit).offset(offset) ).scalars() return tuple( _record(MediaRecord, payload) for payload in payloads ) - def count_media(self) -> int: + def count_media( + self, + *, + filename: str | None = None, + state: MediaState | None = None, + ) -> int: + conditions = _media_list_conditions( + dialect_name=self.engine.dialect.name, + filename=filename, + state=state, + ) + query = select(func.count()).select_from(media) + if conditions: + query = query.where(and_(*conditions)) with self.engine.connect() as connection: - return int( - connection.execute( - select(func.count()).select_from(media) - ).scalar_one() - ) + return int(connection.execute(query).scalar_one()) def reserve_media_import( self, diff --git a/src/vidxp/mcp.py b/src/vidxp/mcp.py index c2f25ed0..c359c335 100644 --- a/src/vidxp/mcp.py +++ b/src/vidxp/mcp.py @@ -109,6 +109,7 @@ load_mcp_app_html, ) from vidxp.core.identifiers import ArtifactId +from vidxp.core.media import MediaState from vidxp.evidence_delivery import ( EvidenceDeliveryService, require_completed_evidence_result, @@ -1277,13 +1278,23 @@ async def get_workspace( str | None, Field(min_length=1, max_length=512), ] = None, + filename: Annotated[ + str | None, + Field(min_length=1), + ] = None, + state: MediaState | None = None, ) -> WorkspaceOverview: return await _invoke_async( context, default_principal=default_principal, permission=RepositoryPermission.read, operation=lambda _actor: context.application.workspace( - ListMediaCommand(page_size=page_size, cursor=cursor) + ListMediaCommand( + page_size=page_size, + cursor=cursor, + filename=filename, + state=state, + ) ), ) @@ -1349,13 +1360,23 @@ async def list_media( str | None, Field(min_length=1, max_length=512), ] = None, + filename: Annotated[ + str | None, + Field(min_length=1), + ] = None, + state: MediaState | None = None, ) -> MediaPage: return await _invoke_async( context, default_principal=default_principal, permission=RepositoryPermission.read, operation=lambda _actor: context.application.list_media( - ListMediaCommand(page_size=page_size, cursor=cursor) + ListMediaCommand( + page_size=page_size, + cursor=cursor, + filename=filename, + state=state, + ) ), ) diff --git a/src/vidxp/media_service.py b/src/vidxp/media_service.py index e208efae..37125260 100644 --- a/src/vidxp/media_service.py +++ b/src/vidxp/media_service.py @@ -1,8 +1,8 @@ from __future__ import annotations +import hashlib import json from pathlib import Path -import hashlib from uuid import uuid4 from vidxp.application_models import ( @@ -274,14 +274,29 @@ def get(self, media_id: str) -> MediaAsset: return media_asset(record) def list(self, command: ListMediaCommand) -> MediaPage: - scope = hashlib.sha256( + repository_scope = hashlib.sha256( str(self.settings.repository_root.resolve()).encode() ).hexdigest() + scope = repository_scope + if command.filename is not None or command.state is not None: + scope = hashlib.sha256( + json.dumps( + [ + repository_scope, + command.filename, + command.state.value if command.state is not None else None, + ], + separators=(",", ":"), + ).encode() + ).hexdigest() try: offset = decode_offset_cursor(command.cursor, scope=scope) except CursorError as exc: raise ValueError("The media cursor is invalid.") from exc - total = self.catalog.count_media() + total = self.catalog.count_media( + filename=command.filename, + state=command.state, + ) if offset > total: raise ValueError("The media cursor is outside the result set.") items = tuple( @@ -289,6 +304,8 @@ def list(self, command: ListMediaCommand) -> MediaPage: for record in self.catalog.list_media( limit=command.page_size, offset=offset, + filename=command.filename, + state=command.state, ) ) next_offset = offset + len(items) diff --git a/src/vidxp/ports.py b/src/vidxp/ports.py index 18c3ad23..492636ae 100644 --- a/src/vidxp/ports.py +++ b/src/vidxp/ports.py @@ -40,6 +40,7 @@ from vidxp.core.media import ( MediaProbe, MediaRecord, + MediaState, StagedMedia, StoredMedia, ) @@ -104,9 +105,16 @@ def list_media( *, limit: int, offset: int = 0, + filename: str | None = None, + state: MediaState | None = None, ) -> tuple[MediaRecord, ...]: ... - def count_media(self) -> int: ... + def count_media( + self, + *, + filename: str | None = None, + state: MediaState | None = None, + ) -> int: ... def reserve_media_import( self, diff --git a/tests/test_api.py b/tests/test_api.py index 09fd7565..1e510ff4 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -39,6 +39,7 @@ JobWaitResult, IndexStatus, MediaAsset, + MediaPage, Principal, SearchCommand, SearchJobResult, @@ -372,6 +373,56 @@ def test_workspace_endpoint_returns_actionable_repository_state(self): command = context.application.workspace.call_args.args[0] self.assertEqual(command.page_size, 25) + def test_list_media_passes_filters_to_application(self): + with TemporaryDirectory() as directory: + context = self.context(Path(directory)) + context.application.list_media.return_value = MediaPage( + items=(), + total=0, + ) + with TestClient(create_app(context=context)) as client: + response = client.get( + "/api/v1/media", + params={ + "page_size": 10, + "filename": "clip.mp4", + "state": "ready", + }, + ) + + self.assertEqual(response.status_code, 200) + command = context.application.list_media.call_args.args[0] + self.assertEqual(command.page_size, 10) + self.assertEqual(command.filename, "clip.mp4") + self.assertEqual(command.state, MediaState.ready) + + def test_workspace_passes_filters_to_application(self): + with TemporaryDirectory() as directory: + context = self.context(Path(directory)) + context.application.workspace.return_value = WorkspaceOverview( + media_total=0, + index=IndexStatus( + schema_version=2, + state="missing", + stage="status", + message="No index.", + ), + next_actions=("register_media",), + ) + with TestClient(create_app(context=context)) as client: + response = client.get( + "/api/v1/workspace", + params={ + "filename": "batch", + "state": "pending", + }, + ) + + self.assertEqual(response.status_code, 200) + command = context.application.workspace.call_args.args[0] + self.assertEqual(command.filename, "batch") + self.assertEqual(command.state, MediaState.pending) + def test_repository_scopes_are_enforced_per_operation(self): with TemporaryDirectory() as directory: context = self.context( diff --git a/tests/test_cli.py b/tests/test_cli.py index f88d4677..be5165c2 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -465,6 +465,25 @@ def test_media_list_shows_pending_and_failed_states(self): self.assertIn("failed", result.output) self.assertIn("-", result.output) + def test_media_list_passes_filters_to_service(self): + self.service.list_media.return_value = MediaPage(items=(), total=0) + + result = self.invoke( + [ + "media", + "list", + "--filename", + "clip.mp4", + "--state", + "ready", + ] + ) + + self.assertEqual(result.exit_code, 0, result.output) + command = self.service.list_media.call_args.args[0] + self.assertEqual(command.filename, "clip.mp4") + self.assertEqual(command.state, MediaState.ready) + def test_ui_share_uses_streamlit_wildcard_bind_and_warns(self): with ( patch( diff --git a/tests/test_control_plane.py b/tests/test_control_plane.py index 05a7d8d5..446f57b9 100644 --- a/tests/test_control_plane.py +++ b/tests/test_control_plane.py @@ -180,6 +180,65 @@ def test_workspace_projects_index_coverage_roles_and_next_actions(self): ) self.assertEqual(workspace.media[1].capabilities[0].roles, ()) + def test_workspace_index_action_uses_repository_total_when_filtered(self): + indexed = media_asset(MEDIA_ID, "indexed.mp4") + snapshot = IndexSnapshot( + snapshot_id=SNAPSHOT_ID, + created_at=datetime.now(timezone.utc), + config_fingerprint="b" * 64, + configuration={}, + generations={ + MEDIA_ID: GenerationReference( + generation_id=GENERATION_ID, + media_id=MEDIA_ID, + manifest_sha256="c" * 64, + input_sha256="d" * 64, + config_fingerprint="e" * 64, + modalities=("scene",), + record_counts={"scene": 12}, + store_size_bytes_at_commit=100, + ), + OTHER_MEDIA_ID: GenerationReference( + generation_id="523456781234423481234567890abcde", + media_id=OTHER_MEDIA_ID, + manifest_sha256="f" * 64, + input_sha256="a" * 64, + config_fingerprint="b" * 64, + modalities=("scene",), + record_counts={"scene": 8}, + store_size_bytes_at_commit=90, + ), + }, + ) + media = Mock() + + def list_media(command: ListMediaCommand) -> MediaPage: + if command.filename == "clip": + return MediaPage(items=(indexed,), total=1) + return MediaPage(items=(indexed,), total=3) + + media.list.side_effect = list_media + with TemporaryDirectory() as directory: + root = Path(directory) + application = ControlPlaneApplication( + layout=RepositoryLayout(root=root), + capabilities=CapabilityService(create_capability_registry()), + media=media, + artifacts=Mock(), + index_status=lambda: { + "schema_version": 2, + "state": "ready", + "stage": "status", + "message": "Index ready.", + }, + active_snapshot=lambda: snapshot, + model_cache=root / "models", + ) + + workspace = application.workspace(ListMediaCommand(filename="clip")) + + self.assertIn("index_media", workspace.next_actions) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_frontend.py b/tests/test_frontend.py index b7205069..464384a3 100644 --- a/tests/test_frontend.py +++ b/tests/test_frontend.py @@ -249,8 +249,10 @@ def test_busy_video_layout_keeps_controls_and_preview_stable(self): def test_registered_video_selector_lists_only_ready_media(self): service = Mock() + ready_id = MEDIA_ID - pending_id = "223456781234423481234567890abcde" + pending_id = "pending-media-id" + media_page = SimpleNamespace( items=( SimpleNamespace( @@ -262,7 +264,7 @@ def test_registered_video_selector_lists_only_ready_media(self): SimpleNamespace( media_id=pending_id, original_filename="pending.mp4", - duration_seconds=None, + duration_seconds=12.0, state=MediaState.pending, ), ), @@ -298,7 +300,7 @@ def test_registered_video_selector_lists_only_ready_media(self): ): _uploaded, media_id = frontend._select_video( False, - pending_id, + ready_id, media_page, ) diff --git a/tests/test_mcp.py b/tests/test_mcp.py index c0d04eb0..3f44ec3d 100644 --- a/tests/test_mcp.py +++ b/tests/test_mcp.py @@ -90,6 +90,7 @@ from vidxp.composition import HttpApplicationContext from vidxp.control_plane import ControlPlaneApplication from vidxp.core.artifacts import ArtifactKind, ArtifactState +from vidxp.core.media import MediaState from vidxp.core.uploads import UploadSessionState, UploadState from vidxp.job_service import JobService from vidxp.mcp import VidXPTokenVerifier, create_mcp_server, create_remote_mcp @@ -1366,6 +1367,33 @@ async def test_discovery_tools_use_shared_media_and_job_pages(self): ) self.assertEqual(context.jobs.list.call_args.args[0].page_size, 9) + async def test_list_media_passes_filters_to_application(self): + with TemporaryDirectory() as directory: + context = self.context(Path(directory)) + context.application.list_media.return_value = MediaPage(total=0) + server = create_mcp_server( + context, + default_principal=Principal( + subject="agent", + scopes=frozenset({"vidxp.read"}), + ), + ) + async with Client(server) as client: + media = await client.call_tool( + "list_media", + { + "page_size": 5, + "filename": "clip.mp4", + "state": "ready", + }, + ) + + self.assertFalse(media.is_error) + command = context.application.list_media.call_args.args[0] + self.assertEqual(command.page_size, 5) + self.assertEqual(command.filename, "clip.mp4") + self.assertEqual(command.state, MediaState.ready) + async def test_failed_model_preparation_job_is_structured_over_mcp(self): with TemporaryDirectory() as directory: context = self.context(Path(directory)) diff --git a/tests/test_media_catalog.py b/tests/test_media_catalog.py index a9df9fd0..16a76b95 100644 --- a/tests/test_media_catalog.py +++ b/tests/test_media_catalog.py @@ -29,6 +29,7 @@ MEDIA_ID = "123456781234423481234567890abcde" OTHER_MEDIA_ID = "223456781234423481234567890abcde" +THIRD_MEDIA_ID = "423456781234423481234567890abcde" ARTIFACT_ID = "323456781234423481234567890abcde" @@ -144,6 +145,110 @@ def test_catalog_persists_and_deduplicates_media_by_checksum(self): self.assertEqual(reopened.put_media(duplicate), record) self.assertEqual(reopened.list_media(limit=10), (record,)) + def test_catalog_list_media_filters_by_state(self): + with TemporaryDirectory() as directory: + catalog = LocalCatalog(Path(directory) / "catalog.sqlite3") + ready = media_record() + pending = incomplete_media_record( + state=MediaState.pending, + media_id=OTHER_MEDIA_ID, + checksum="2" * 64, + ).model_copy(update={"original_filename": "pending.mp4"}) + failed = incomplete_media_record( + state=MediaState.failed, + media_id=THIRD_MEDIA_ID, + checksum="3" * 64, + ).model_copy(update={"original_filename": "failed.mp4"}) + catalog.put_media(ready) + catalog.put_media(pending) + catalog.put_media(failed) + + self.assertEqual(catalog.count_media(), 3) + self.assertEqual(catalog.count_media(state=MediaState.ready), 1) + self.assertEqual(catalog.count_media(state=MediaState.pending), 1) + self.assertEqual(catalog.count_media(state=MediaState.failed), 1) + self.assertEqual( + catalog.list_media(limit=10, state=MediaState.ready), + (ready,), + ) + + def test_catalog_list_media_filters_by_filename(self): + with TemporaryDirectory() as directory: + catalog = LocalCatalog(Path(directory) / "catalog.sqlite3") + clip = media_record().model_copy( + update={"original_filename": "Straße-Épisode-Clip.mp4"} + ) + other = media_record( + OTHER_MEDIA_ID, + checksum="2" * 64, + ).model_copy(update={"original_filename": "other.mov"}) + catalog.put_media(clip) + catalog.put_media(other) + + self.assertEqual(catalog.count_media(filename="clip"), 1) + self.assertEqual(catalog.count_media(filename="CLIP"), 1) + self.assertEqual(catalog.count_media(filename="STRASSE"), 1) + self.assertEqual(catalog.count_media(filename="éPISODE"), 1) + self.assertEqual( + catalog.list_media(limit=10, filename="clip"), + (clip,), + ) + self.assertEqual(catalog.count_media(filename="other.mov"), 1) + + def test_catalog_list_media_applies_combined_filters(self): + with TemporaryDirectory() as directory: + catalog = LocalCatalog(Path(directory) / "catalog.sqlite3") + ready_clip = media_record().model_copy( + update={"original_filename": "ready-clip.mp4"} + ) + pending_clip = incomplete_media_record( + state=MediaState.pending, + media_id=OTHER_MEDIA_ID, + checksum="2" * 64, + ).model_copy(update={"original_filename": "pending-clip.mp4"}) + catalog.put_media(ready_clip) + catalog.put_media(pending_clip) + + self.assertEqual( + catalog.count_media( + filename="clip", + state=MediaState.ready, + ), + 1, + ) + self.assertEqual( + catalog.list_media( + limit=10, + filename="clip", + state=MediaState.ready, + ), + (ready_clip,), + ) + + def test_catalog_list_media_supports_filtered_pagination(self): + with TemporaryDirectory() as directory: + catalog = LocalCatalog(Path(directory) / "catalog.sqlite3") + first = media_record().model_copy( + update={"original_filename": "batch-001.mp4"} + ) + second = media_record( + OTHER_MEDIA_ID, + checksum="2" * 64, + ).model_copy(update={"original_filename": "batch-002.mp4"}) + third = media_record( + THIRD_MEDIA_ID, + checksum="3" * 64, + ).model_copy(update={"original_filename": "other.mp4"}) + catalog.put_media(first) + catalog.put_media(second) + catalog.put_media(third) + + self.assertEqual(catalog.count_media(filename="batch"), 2) + page = catalog.list_media(limit=1, offset=0, filename="batch") + self.assertEqual(page, (first,)) + page = catalog.list_media(limit=1, offset=1, filename="batch") + self.assertEqual(page, (second,)) + def test_catalog_replaces_pending_and_failed_media(self): with TemporaryDirectory() as directory: catalog = LocalCatalog(Path(directory) / "catalog.sqlite3") diff --git a/tests/test_media_services.py b/tests/test_media_services.py index d2dcb280..5134e448 100644 --- a/tests/test_media_services.py +++ b/tests/test_media_services.py @@ -1,3 +1,4 @@ +import hashlib import unittest from contextlib import nullcontext from datetime import datetime, timezone @@ -19,6 +20,7 @@ ) from vidxp.core.artifacts import ArtifactState from vidxp.core.contracts import CancellationToken, IndexCancelledError +from vidxp.core.cursors import encode_offset_cursor from vidxp.core.media import ( MediaProbe, MediaUnavailableError, @@ -475,7 +477,8 @@ def test_publish_failure_catalogs_failed_media_without_publishing(self): def test_media_pages_are_bounded_and_cursor_scoped(self): with TemporaryDirectory() as directory: - service, catalog, _store, _probe = self.service(Path(directory)) + root = Path(directory) + service, catalog, _store, _probe = self.service(root) catalog.count_media.return_value = 3 catalog.list_media.side_effect = [ (record(), record().model_copy( @@ -501,7 +504,11 @@ def test_media_pages_are_bounded_and_cursor_scoped(self): self.assertEqual(first.total, 3) self.assertEqual(len(first.items), 2) - self.assertIsNotNone(first.next_cursor) + scope = hashlib.sha256(str(root.resolve()).encode()).hexdigest() + self.assertEqual( + first.next_cursor, + encode_offset_cursor(2, scope=scope), + ) self.assertEqual(len(second.items), 1) self.assertIsNone(second.next_cursor) self.assertEqual( @@ -509,6 +516,57 @@ def test_media_pages_are_bounded_and_cursor_scoped(self): 2, ) + def test_filtered_media_cursor_is_bounded_and_filter_scoped(self): + with TemporaryDirectory() as directory: + service, catalog, _store, _probe = self.service(Path(directory)) + catalog.count_media.return_value = 2 + catalog.list_media.side_effect = [(record(),), (record(),)] + command = ListMediaCommand( + page_size=1, + filename="É" * 255, + state=MediaState.ready, + ) + + first = service.list(command) + second = service.list( + command.model_copy(update={"cursor": first.next_cursor}) + ) + for changed_filter in ( + {"filename": "other.mp4"}, + {"state": MediaState.failed}, + ): + with ( + self.subTest(changed_filter=changed_filter), + self.assertRaisesRegex(ValueError, "cursor"), + ): + service.list( + command.model_copy( + update={"cursor": first.next_cursor, **changed_filter} + ) + ) + + self.assertLessEqual(len(first.next_cursor or ""), 512) + self.assertIsNone(second.next_cursor) + + def test_media_list_passes_filters_to_catalog_count(self): + with TemporaryDirectory() as directory: + service, catalog, _store, _probe = self.service(Path(directory)) + catalog.count_media.return_value = 1 + catalog.list_media.return_value = (record(),) + + service.list( + ListMediaCommand( + page_size=10, + filename="clip.mp4", + state=MediaState.ready, + ) + ) + + catalog.count_media.assert_called_once_with( + filename="clip.mp4", + state=MediaState.ready, + ) + def test_failed_checksum_is_retried_to_ready(self): with TemporaryDirectory() as directory: root = Path(directory) From c8942c6fda009a50bb18ceac887039a329cdb5fb Mon Sep 17 00:00:00 2001 From: Talha Amjad Date: Fri, 28 Aug 2026 14:40:12 +0500 Subject: [PATCH 2/9] feat(sound): add FineLAP audio event search (#130) * docs(benchmarking): refresh multimodal model direction * feat(sound): add FineLAP audio event search * fix(desktop): refresh model cache catalog --- .github/release-intro.md | 2 +- INSTALLATION_GUIDE.md | 6 + README.md | 10 +- desktop/model-cache-catalog.json | 20 + desktop/src/components/TargetSummary.tsx | 1 + docs/architecture/platform.md | 12 + docs/benchmarking/README.md | 21 +- docs/benchmarking/benchmark_catalog.md | 87 ++-- docs/benchmarking/direction.md | 6 + docs/benchmarking/execution_readiness.md | 5 + docs/benchmarking/model_selection.md | 135 ++++++ docs/benchmarking/paper_validation.md | 15 +- docs/benchmarking/published_results.md | 64 ++- docs/benchmarking/research_papers.md | 34 +- docs/benchmarking/results.md | 25 +- docs/local-api.md | 6 + pyproject.toml | 12 +- src/vidxp/capabilities/registry.py | 3 +- src/vidxp/capabilities/search.py | 3 + src/vidxp/capabilities/sound/__init__.py | 1 + src/vidxp/capabilities/sound/config.py | 15 + src/vidxp/capabilities/sound/definition.py | 100 ++++ src/vidxp/capabilities/sound/indexing.py | 265 +++++++++++ src/vidxp/capabilities/sound/models.py | 228 +++++++++ src/vidxp/capabilities/sound/operations.py | 110 +++++ src/vidxp/capabilities/sound/requirements.txt | 8 + src/vidxp/capabilities/sound/specs.py | 76 +++ src/vidxp/frontend.py | 3 +- tests/test_capabilities.py | 16 +- tests/test_frontend.py | 9 +- tests/test_frontend_app.py | 2 +- tests/test_local_probe.py | 6 +- tests/test_mcp.py | 2 +- tests/test_sound.py | 234 +++++++++ uv.lock | 448 +++++++++++++++++- 35 files changed, 1910 insertions(+), 80 deletions(-) create mode 100644 docs/benchmarking/model_selection.md create mode 100644 src/vidxp/capabilities/sound/__init__.py create mode 100644 src/vidxp/capabilities/sound/config.py create mode 100644 src/vidxp/capabilities/sound/definition.py create mode 100644 src/vidxp/capabilities/sound/indexing.py create mode 100644 src/vidxp/capabilities/sound/models.py create mode 100644 src/vidxp/capabilities/sound/operations.py create mode 100644 src/vidxp/capabilities/sound/requirements.txt create mode 100644 src/vidxp/capabilities/sound/specs.py create mode 100644 tests/test_sound.py diff --git a/.github/release-intro.md b/.github/release-intro.md index 55f5757a..94726190 100644 --- a/.github/release-intro.md +++ b/.github/release-intro.md @@ -1,6 +1,6 @@ ## Download VidXP -{release_notice}VidXP turns video into searchable dialogue, scenes, people, and +{release_notice}VidXP turns video into searchable dialogue, sounds, scenes, actions, people, and inspectable evidence. Choose the desktop app for the guided local setup, or use the Python package and containers for command-line and server deployments. diff --git a/INSTALLATION_GUIDE.md b/INSTALLATION_GUIDE.md index db5b6d07..9288c417 100644 --- a/INSTALLATION_GUIDE.md +++ b/INSTALLATION_GUIDE.md @@ -34,6 +34,7 @@ Approximate model downloads are: | Feature | Download | |---|---:| | Dialogue search | 2.64 GiB | +| Sound event search | 0.94 GiB | | Scene search | 1.43 GiB | | Action search | 0.93 GiB | | Actor matching | 37 MiB | @@ -161,6 +162,7 @@ asking for confirmation. Download only selected features when preferred: vidxp prepare --modalities scene vidxp prepare --modalities dialogue,actor vidxp prepare --modalities videoprism # action search +vidxp prepare --modalities sound # music and environmental sounds ``` For a noninteractive script, add `--yes`. Indexing and search commands do not @@ -193,6 +195,9 @@ vidxp search scene "a yellow taxi on a city street" # Find an action or event (`videoprism` is the CLI name for action search) vidxp search videoprism "a person opens a door and walks outside" +# Find music or an environmental sound +vidxp search sound "an alarm ringing" + # Find something that was said vidxp search dialogue "the bread just came out of the oven" ``` @@ -277,6 +282,7 @@ assembling a custom installation: | Extra | Adds | |---|---| | `dialogue` | Transcription, dialogue embeddings, and storage | +| `sound` | Music and environmental-sound search and storage | | `scene` | Scene search and storage | | `videoprism` | Action search and storage | | `actor` | Actor matching and storage | diff --git a/README.md b/README.md index 5c012ac8..7f08f1b3 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@

- Dialogue search · Scene search · Action search · Actor grouping + Dialogue search · Sound search · Scene search · Action search · Actor grouping

@@ -122,7 +122,7 @@ See the [Coolify guide](docs/deployment/coolify.md) for the complete setup. ## What you can do today - Build searchable libraries from individual videos or whole collections. -- Find dialogue by meaning and visual moments by describing the scene. +- Find dialogue, sound events, visual scenes, and multi-frame actions by description. - Ask grounded questions and inspect the supporting boards, frames, or clips. - Group recurring faces and render highlighted actor overlays. - Keep personal, client, or project libraries separate. @@ -146,6 +146,9 @@ vidxp search scene "a yellow taxi on a city street" # Find an action or event vidxp search videoprism "a person opens a door and walks outside" +# Find a sound event +vidxp search sound "a dog barking over traffic noise" + # Find something that was said vidxp search dialogue "the bread just came out of the oven" ``` @@ -205,12 +208,13 @@ approximately 3 GiB. | Capability | Approximate model download | |---|---:| | Dialogue search | 2.64 GiB | +| Sound event search | 0.94 GiB | | Scene search | 1.43 GiB | | Action search | 0.93 GiB | | Actor matching | 37 MiB | A full local Desktop setup with every search capability uses approximately -8.1 GiB. Leave additional temporary space during installation and for indexes, +9.0 GiB. Leave additional temporary space during installation and for indexes, source videos, and exported results. By default, the CLI and desktop app share the same VidXP data directory: diff --git a/desktop/model-cache-catalog.json b/desktop/model-cache-catalog.json index 8284badf..49590f9b 100644 --- a/desktop/model-cache-catalog.json +++ b/desktop/model-cache-catalog.json @@ -1,9 +1,29 @@ [ + { + "id": "AndreasXi/FineLAP", + "label": "AndreasXi/FineLAP", + "relative_artifact": "models--AndreasXi--FineLAP/snapshots/b419aa22947d29907a5567f21b81bf3b39a40449/model.safetensors" + }, { "id": "dropbox-dash/faster-whisper-large-v3-turbo", "label": "dropbox-dash/faster-whisper-large-v3-turbo", "relative_artifact": "models--dropbox-dash--faster-whisper-large-v3-turbo/snapshots/0a363e9161cbc7ed1431c9597a8ceaf0c4f78fcf/model.bin" }, + { + "id": "FacebookAI/roberta-base config", + "label": "FacebookAI/roberta-base config", + "relative_artifact": "finelap-tokenizer/config.json" + }, + { + "id": "FacebookAI/roberta-base merges", + "label": "FacebookAI/roberta-base merges", + "relative_artifact": "finelap-tokenizer/merges.txt" + }, + { + "id": "FacebookAI/roberta-base vocab", + "label": "FacebookAI/roberta-base vocab", + "relative_artifact": "finelap-tokenizer/vocab.json" + }, { "id": "google/siglip2-base-patch16-224", "label": "google/siglip2-base-patch16-224", diff --git a/desktop/src/components/TargetSummary.tsx b/desktop/src/components/TargetSummary.tsx index 06cee6f0..3311ae85 100644 --- a/desktop/src/components/TargetSummary.tsx +++ b/desktop/src/components/TargetSummary.tsx @@ -48,6 +48,7 @@ const CAPABILITY_LABELS: Record = { dialogue: 'Dialogue search', media: 'Video tools', scene: 'Visual scene search', + sound: 'Sound event search', videoprism: 'Temporal video search', }; diff --git a/docs/architecture/platform.md b/docs/architecture/platform.md index d4251ff4..c8dd8ef8 100644 --- a/docs/architecture/platform.md +++ b/docs/architecture/platform.md @@ -773,6 +773,18 @@ composition root and is sorted deterministically. `97b0c614be4d77ee51c0cef4e5f07c00f9eb65b3`; its published multilingual MTEB retrieval results materially exceed the older multilingual E5 baseline and its Apache-2.0 license permits the intended deployment. +- Sound: use FineLAP at immutable Hugging Face revision + `b419aa22947d29907a5567f21b81bf3b39a40449`. Each video audio stream is decoded + once into ten-second windows. The sound collection stores one normalized global + embedding per window and the model's normalized dense embeddings as timestamped + activation records. Both use the shared text/audio space, and search results + retain `representation`, window, and activation provenance. FineLAP requires + repository-supplied Transformers code; VidXP loads only the pinned snapshot, + keeps runtime loading offline, and prepares the two small pinned RoBERTa + tokenizer artifacts explicitly instead of allowing a constructor-time model + download. The Hugging Face model card declares MIT; the upstream GitHub source + repository does not contain a separate license file, so redistribution review + must preserve that qualification. - Actor: replace `face_recognition`/dlib with OpenCV Zoo YuNet plus SFace through OpenCV's maintained DNN APIs. Model files are retrieved with `pooch`, pinned to OpenCV Zoo commit `47534e27c9851bb1128ccc0102f1145e27f23f98`, and verified diff --git a/docs/benchmarking/README.md b/docs/benchmarking/README.md index cfe692fd..e4aecea9 100644 --- a/docs/benchmarking/README.md +++ b/docs/benchmarking/README.md @@ -16,7 +16,8 @@ installation and product usage, start with the main | Guided input preparation | Complete | `vidxp benchmark prepare` estimates and confirms downloads, verifies pinned artifacts, validates DiDeMo media, resumes partial transfers, and prints the runnable benchmark command | | DiDeMo visual localization | Legacy full result + current smoke | The legacy CLIP stack completed 4,021 official test queries over 1,037 videos; the current SigLIP2 stack passed a one-annotation real execution smoke | | HiREST transcript localization | Legacy full result + current smoke | The legacy MiniLM stack scored all 193 validation pairs; current Qwen3 passed a two-video real execution smoke; 776 released test predictions remain unscored because their public bounds are placeholders | -| LongVALE combined evaluation | Next | Build the visual-plus-speech adapter and validate one evaluation archive before scheduling the full run | +| Environmental-sound retrieval | Implementation complete; benchmark pending | FineLAP stores global ten-second windows and dense timestamped sound activations; no VidXP quality score is claimed yet | +| LongVALE combined evaluation | Next adapter and pilot | Validate vision, environmental sound, and speech together on one evaluation archive before scheduling the full run | | Actor clustering | Data-gated | The preferred BBT/Buffy evaluation still requires lawful access to the source episodes | Read [current results](results.md) for the scores, plain-language metric @@ -30,6 +31,7 @@ definitions, honest comparisons, and the next benchmark decision. | Reproduce DiDeMo or HiREST | [Adapter validation ledger](adapter_validation.md) | | Understand the benchmark-ready Python structure | [Core contract](core_contract.md) | | See which benchmarks exist and what each measures | [Benchmark catalog](benchmark_catalog.md) | +| Understand the current model and benchmark choices | [Multimodal model direction](model_selection.md) | | Find exact published competitor scores | [Published comparison results](published_results.md) | | Review the relevant papers | [Research-paper inventory](research_papers.md) | | Audit what was checked in each paper | [Paper-validation ledger](paper_validation.md) | @@ -42,17 +44,20 @@ they are not the current task list. ## Current benchmark position -No single published benchmark covers dialogue retrieval, scene retrieval, and -actor clustering together. +No single published benchmark covers speech retrieval, scene/action retrieval, +environmental-sound retrieval, exact temporal boundaries, and actor clustering +together. The retained full DiDeMo and HiREST results establish separate legacy-provider visual and transcript baselines. Current SigLIP2 and Qwen3 checks establish adapter/runtime compatibility only; they do not yet provide full-corpus quality -comparisons. LongVALE is the next combined test because it asks a system to find -described events in long videos using visual, speech, and general audio -evidence. VidXP can currently contribute visual and speech evidence; it does -not recognize general sounds such as music, alarms, or barking. Any LongVALE -result must keep that limitation visible. +comparisons. VidXP now contributes separate visual, speech, and FineLAP sound +evidence, including global windows and dense timestamps for music, alarms, +barking, and other non-speech events. The next target is the LongVALE adapter and +one-archive pilot. That work must measure the integration before any VidXP sound +quality or combined-system claim is made. The +[current model direction](model_selection.md) records the selection evidence and +remaining controls. ## Evidence rules diff --git a/docs/benchmarking/benchmark_catalog.md b/docs/benchmarking/benchmark_catalog.md index 58cd92d1..5bf609b1 100644 --- a/docs/benchmarking/benchmark_catalog.md +++ b/docs/benchmarking/benchmark_catalog.md @@ -4,9 +4,10 @@ Collection index: [Benchmarking research](README.md) Status: Candidate research complete; execution status updated -Last verified: 2026-07-27 +Last verified: 2026-08-27 -Scope: Capabilities implemented in the current `src/vidxp` package +Scope: Capabilities implemented in the current `src/vidxp` package, including +timestamped environmental-sound retrieval This catalog is the detailed decision record for published benchmarks. Current VidXP measurements are reported separately in [results](results.md). Published @@ -37,18 +38,21 @@ The selected suite remains component-based: 5. **BCL on BBT/Buffy** as the best reproducible actor-clustering protocol, provided the team can obtain the underlying episodes or explicitly limits the experiment to BCL's supplied features. -6. **LongVALE** as a combined vision–speech temporal test using a frozen, - non-learned fusion rule while clearly disclosing unsupported generic audio. -7. A fixed local indexing/query timing protocol, reported as a VidXP engineering +6. **MAEB**, **MVEB**, and **AEGBench** as component-selection and regression + sources for audio embeddings, video embeddings, and sound-event boundaries. +7. **LongVALE** as the primary combined vision–sound–speech temporal test now that + the environmental-sound provider exists. +8. A fixed local indexing/query timing protocol, reported as a VidXP engineering measurement rather than as a score directly comparable with unrelated hardware. LongVALE is the strongest peer-reviewed combined vision–audio–speech temporal benchmark found. It still omits actor clustering and expects genuinely fused -multi-modal interval predictions. A fixed rank/score fusion adapter makes an -official run possible without claiming full omni-modal coverage. FLARE is a smaller -downloadable audio-visual stress test, but it is a 2026 -preprint benchmark with generated, filtered queries. It belongs in a secondary -experiment or watchlist until peer review and benchmark stability improve. +multi-modal interval predictions. FineLAP now supplies separate global-window and +dense timestamped sound evidence, but the LongVALE adapter and fusion rule remain +unimplemented and no quality score is claimed. FLARE is a smaller downloadable +audio-visual stress test, but it is a 2026 preprint benchmark with generated, +filtered queries. It belongs in a secondary experiment or watchlist until peer +review and benchmark stability improve. See [execution readiness](execution_readiness.md) for the corrected implementation boundary and per-benchmark engineering classification. @@ -111,12 +115,12 @@ protocol. It does not mean that a similar published score is already comparable. | Movie face clustering | [MovieFaceCluster / VideoClusterNet](https://www.ecva.net/papers/eccv_2024/papers_ECCV/html/4432_ECCV_2024_paper.php) | Unsupervised face-track clustering across nine movies | Whether VidXP actor clustering generalizes to a recent movie protocol | Executable evidence while the dataset link and code remain unavailable | Blocked/reference-only | | Egocentric face clustering | [EasyCom-Clustering](https://github.com/ibug-group/Easycom-Clustering) | Face-track clustering in egocentric social video | Whether actor clusters remain stable under first-person views, occlusion, and viewpoint changes | Any executable result until the promised data is released | Blocked/reference-only | | Face verification provenance | [LFW / dlib protocol](https://github.com/ageitgey/face_recognition) | Same/different identity verification on still-image pairs | Only the provenance and generic discrimination of the underlying face embedding | VidXP actor clustering, face detection coverage, temporal continuity, or unknown-K performance | Not an actor benchmark; provenance only | -| Combined temporal | [LongVALE](https://github.com/ttgeng233/LongVALE) | Vision, speech, and generic-audio event grounding in long videos, with one interval per event query | Whether a frozen VidXP interval proposal and scene/speech fusion localize multimodal events | Actor clustering or full omni-modal coverage because VidXP lacks generic-audio recognition | Engineering A/medium; artifacts reachable; compliance, interval, and runtime gates remain | +| Combined temporal | [LongVALE](https://github.com/ttgeng233/LongVALE) | Vision, speech, and generic-audio event grounding in long videos, with one interval per event query | Whether a frozen VidXP interval proposal and scene/sound/speech fusion localize multimodal events | Actor clustering or proof of omni-modal quality before the new sound path and fusion adapter are evaluated | Engineering A/medium; artifacts reachable; compliance, interval, and runtime gates remain | | Audiovisual retrieval | [FLARE](https://flarebench.github.io/) | Caption-to-clip/video retrieval plus clip-level model-simulated visual-only, audio-only, and joint query retrieval | Where VidXP's scene, speech, and fixed-fusion clip rankings succeed or fail across evidence types | Actor performance, human-authored-query generalization, or full generic-sound capability | Engineering A/medium; ready/watchlist preprint | | Large-corpus event | [MultiVENT 2.0](https://huggingface.co/datasets/hltcoe/MultiVENT2.0) | Multilingual event-centric ranked-video retrieval using visual, speech/ASR, embedded-text/OCR, and human-description metadata evidence | Whether existing VidXP visual, speech, and metadata paths scale to a large heterogeneous corpus; unsupported OCR becomes a measurable weakness | Timestamp localization, actor clustering, generic acoustic-event retrieval, or multilingual adequacy of the current Qwen3 embedding provider | Engineering A with large operational adapter; approximately 1.93 TB gated | | Large-corpus shot | [TRECVID AVS / V3C](https://www-nlpir.nist.gov/projects/tv2025/avs.html) | Natural-language query to up to 1,000 ranked master shots, measured with mean xinfAP | Whether VidXP scene retrieval scales to over a million pooled/judged shots and returns useful corpus rankings | Dialogue or actor performance, or comparable latency across different hardware | Archived 2024/2025 protocol; agreement and approximately 1.6 TB gated | | Multimodal whole-video | [MUVR](https://github.com/debby-0527/MUVR) | Paired query-video plus detailed text to ranked short normalized videos; pure-text and pure-video are ablations | Whether VidXP ranks relevant videos for a declared pure-text ablation or a reusable-CLIP visual slice | Timestamp localization, audio/dialogue understanding, actors, or equivalence to the defining paired-query task | Ablation slice adaptable; lower priority | -| Audiovisual whole-video | [VALOR-32K](https://github.com/TXH-mercury/VALOR) | Bidirectional audiovisual-text retrieval and audiovisual captioning over 32,000 ten-second clips with human captions | Whether VidXP's visual/speech or fixed audiovisual representation ranks captioned clips on a standard released split | Temporal localization, actor clustering, long-video search, or full parity without a generic-audio model | Benchmark and official artifacts verified; media links/source rights must be checked | +| Audiovisual whole-video | [VALOR-32K](https://github.com/TXH-mercury/VALOR) | Bidirectional audiovisual-text retrieval and audiovisual captioning over 32,000 ten-second clips with human captions | Whether VidXP's visual/sound/speech or fixed audiovisual representation ranks captioned clips on a standard released split | Temporal localization, actor clustering, long-video search, or full parity before a matching adapter run | Benchmark and official artifacts verified; media links/source rights must be checked | | Agentic multimodal retrieval | [MM-MSRVTT and TVR-1200](https://openaccess.thecvf.com/content/WACV2026/html/Shah_VRAgent_Self-Refining_Agent_for_Zero-Shot_Multimodal_Video_Retrieval_WACV_2026_paper.html) | Visual-plus-ASR/joint whole-video retrieval on 500 generated MSR-VTT queries and 1,200 adapted TVR queries | A future released run could compare VidXP's frozen fusion with an agent-refined multimodal query strategy | Any current reproducible claim while the defining annotations and code are absent | Benchmark-defining but artifact-blocked | | Advertising-context retrieval | [ContextIQ Val-1](https://openaccess.thecvf.com/content/WACV2025/html/Chaubey_ContextIQ_A_Multimodal_Expert-Based_Video_Retrieval_System_for_Contextual_Advertising_WACV_2025_paper.html) | Eight ad-concept queries over 500 YouTube movie clips using manually judged top-five results | Whether VidXP's modality experts produce useful contextual whole-video rankings on this small released annotation set | Standard large-corpus generalization, temporal localization, actors, or comparison to a widely adopted protocol | Low-priority custom benchmark; IDs/splits/queries public, media survival gated | | Vector database | [VectorDBBench](https://github.com/zilliztech/vectordbbench) | ANN index build time, recall, latency, and throughput | Whether Chroma retrieves VidXP embeddings accurately and efficiently at a stated scale | Video decoding/model cost or end-to-end retrieval quality | Engineering A; ready diagnostic | @@ -146,8 +150,8 @@ their published numbers alone do not answer a VidXP capability question: | 5 | Dialogue | TVR, `t` subset | A, medium adapter | Gated | Lawful TV clips with original audio | | 6 | Actor | BCL on BBT/Buffy | A, medium clustering adapter | Gated | Released inference script cannot score VidXP clusters; lawful raw episodes needed | | 7 | Visual | Charades-STA | A, medium adapter | Gated | Dataset agreement and narrow staged domain | -| 8 | Whole system | LongVALE | A, medium fixed-fusion adapter | Artifacts reachable; compliance/runtime gate | Evaluation-only raw archives are 40.523 GiB; full 254 GB repository is not required; no generic-audio capability | -| 9 | Whole system | FLARE | A, medium adapter | Ready artifacts; runtime gate/watchlist | 66.267 GiB release; preprint; generated rank-filtered queries; generic audio unsupported | +| 8 | Whole system | LongVALE | A/medium fusion adapter | Artifacts reachable; compliance/runtime gates | Evaluation-only raw archives are 40.523 GiB; full 254 GB repository is not required; FineLAP sound records are available, while the evaluation adapter remains | +| 9 | Whole system | FLARE | A/medium adapter | Ready artifacts; runtime gate/watchlist | 66.267 GiB release; preprint; generated rank-filtered queries; visual/audio/joint coverage now needs adapter validation | | 10 | Actor | Hannah | A, medium evaluator adapter | Gated | Research agreement and separately obtained movie | | 11 | Actor/system | MovieNet | A for component slices | Gated | Registration; movies excluded; actor labels are keyframe-oriented | @@ -480,6 +484,22 @@ The evaluator, not VidXP, should compute the published metrics. | [MovieFaceCluster / VideoClusterNet](https://www.ecva.net/papers/eccv_2024/papers_ECCV/html/4432_ECCV_2024_paper.php) | Relevant nine-movie protocol; paper's dataset URL currently returns 404 and no code was found | Reference-only | | [EasyCom-Clustering](https://github.com/ibug-group/Easycom-Clustering) | 22 sessions, 94,047 face tracks, 1,623,633 facial images, and 53 participants; repository still promises a future download and has no release | Reference-only | +## Model-selection and component regression benchmarks + +These sources narrow provider choices before VidXP spends local compute. They do +not replace end-to-end temporal evaluation. + +| Benchmark | Exact role | VidXP use | Important limit | +| --- | --- | --- | --- | +| [MAEB](https://arxiv.org/abs/2602.16008) | Thirty representative audio-embedding tasks selected from a 98-task pool, spanning speech, music, environmental sounds, and audio-text reasoning | Select and regression-test audio representations by domain instead of assuming one audio model leads every task | It evaluates embeddings, not VidXP's long-media windowing, boundaries, fusion, or system cost | +| [MVEB](https://arxiv.org/abs/2606.14958) | Twenty-three representative video-embedding tasks selected from a 184-task pool, with modality-restricted tables and paired video-only/audio-plus-video variants | Select video retrieval candidates and measure when preserving audio helps | It does not provide a checked VideoPrism row or unrestricted long-video temporal-grounding score | +| [AEGBench](https://arxiv.org/abs/2607.04383) | Open-vocabulary audio-event grounding with exact intervals, including difficult, repeated, and overlapping events | Evaluate environmental-sound boundary quality after retrieval | It is an audio component test and does not exercise visual scenes, speech, or cross-modal fusion | +| [OVSD](https://research.ibm.com/publications/robust-and-efficient-video-scene-detection-using-optimal-sequential-grouping) | Semantic scene segmentation over open-licensed movies and animations | Test temporal-unit or scene-boundary proposals without copyright-gated commercial films | It has no natural-language query, action-label, environmental-sound, speech, or retrieval task | + +The active provider conclusions and exact published selection scores are in +[multimodal model direction](model_selection.md) and +[published comparison results](published_results.md). + ## Whole-system and efficiency evaluation ### LongVALE @@ -512,15 +532,18 @@ The evaluator, not VidXP, should compute the published metrics. - **VidXP fit:** strongest peer-reviewed combined temporal target. It covers vision, speech, and generic audio but not actors. VidXP must freeze a point-to-interval or interval-proposal rule and emit one top-ranked interval, then combine its - existing visual and speech evidence with a frozen, non-learned fusion rule. + visual, environmental-sound, and speech evidence with a frozen, + provenance-preserving fusion rule. The FineLAP provider now exists, but that + fusion adapter does not. Returning top three alone does not satisfy the protocol. Generic-audio evidence - within official event queries remains unsupported; keep all 13,867 queries in - the denominator unless a separately justified diagnostic slice is declared. + within official event queries is unsupported by the current implementation; + keep all 13,867 queries in the denominator unless a separately justified + diagnostic slice is declared. Released LongVALE features are precomputed inputs to the official LongVALE-LLM path, not a standalone reproduction package. That path additionally needs its base model/projector/stage weights, CUDA environment, and evaluator. In all cases, those features bypass VidXP and cannot substitute for running VidXP's - configured scene/transcription/dialogue providers on the raw evaluation + configured scene/sound/transcription/dialogue providers on the raw evaluation videos. - **Operations:** raw evaluation media are now packaged directly, so YouTube survival is no longer the access gate. The raw MP4 payload expands to @@ -531,8 +554,8 @@ The evaluator, not VidXP, should compute the published metrics. require a measured pilot. No LongVALE model checkpoint is needed for a VidXP run. - **Verdict/confidence:** **Adaptable with reachable evaluation artifacts and - license/compliance plus runtime/capacity gates; confirmed.** Generic-audio - coverage remains an explicit limitation. + license/compliance plus runtime/capacity gates; confirmed.** Run one archive + through the new sound path and fusion adapter before the complete protocol. ### FLARE @@ -556,9 +579,9 @@ The evaluator, not VidXP, should compute the published metrics. 71,153,909,874 bytes (66.267 GiB). It contains 399 directories and 87,697 segmented MP4s with audio. Code is MIT; the dataset states CC BY 4.0; the repository includes a harness for 15 retrievers. -- **VidXP fit:** closest obtainable combined scene/dialogue retrieval stress test. - It does not test actor clustering. Audio queries include music and sound events, - so a declared speech-only subset is necessary for VidXP's dialogue path. +- **VidXP fit:** closest obtainable combined visual/sound/speech retrieval stress + test. It does not test actor clustering. The sound provider now exists; a full + protocol claim still requires the FLARE adapter and visual/audio/joint run. - **Caveat:** a unified generated query was retained when it succeeded at rank one while its component queries failed, creating material model-selection bias. - **Verdict/confidence:** **Adaptable watchlist; artifacts confirmed, publication @@ -702,18 +725,20 @@ Completed: Next: -1. Implement the fixed LongVALE visual/speech adapter and validate one evaluation - archive before committing to the full 1,171-video run. -2. Add fixed hardware-aware indexing and query measurements to each subsequent +1. Complete a bounded real-media FineLAP integration smoke, retaining LAION-CLAP + as the mature comparison. +2. Implement the fixed LongVALE visual/sound/speech adapter and validate one + evaluation archive before committing to the full 1,171-video run. +3. Add fixed hardware-aware indexing and query measurements to each subsequent full experiment. -3. Run QVHighlights validation, first on a declared subset for preprocessing +4. Run QVHighlights validation, first on a declared subset for preprocessing validation and then in full if storage permits; the subset does not reduce the monolithic archive download. -4. Resolve TVR media access. If successful, run `t`, `v`, and `vt` separately. -5. Resolve lawful BBT/Buffy media access before implementing the VidXP actor +5. Resolve TVR media access. If successful, run `t`, `v`, and `vt` separately. +6. Resolve lawful BBT/Buffy media access before implementing the VidXP actor scoring adapter. Reproduce BCL's supplied-feature pipeline only as a separate protocol check. -6. Audit and prepare QuerYD raw-video media; use released features only for a +7. Audit and prepare QuerYD raw-video media; use released features only for a clearly labelled Collaborative Experts protocol/reference check. -7. Consider Charades-STA, FLARE, and large-corpus MultiVENT/TRECVID experiments +8. Consider Charades-STA, FLARE, and large-corpus MultiVENT/TRECVID experiments only after the core suite produces valid, archived predictions. diff --git a/docs/benchmarking/direction.md b/docs/benchmarking/direction.md index c5e2a477..2b8eae23 100644 --- a/docs/benchmarking/direction.md +++ b/docs/benchmarking/direction.md @@ -8,6 +8,12 @@ Established: 2026-07-25 Applies to: VidXP / ActorDB paper benchmarking work +> **Current direction:** The 2026-08-27 +> [multimodal model decision](model_selection.md) supersedes this brief's +> implemented-capability-only sequencing. Environmental-sound retrieval has now +> been implemented; full vision/sound/speech evaluation is the next phase. +> This file remains the historical discovery contract. + This document preserves the rules used to start the benchmark research. It predates the benchmark-ready core and completed DiDeMo/HiREST runs. Use [current results](results.md) and the [collection index](README.md) for the diff --git a/docs/benchmarking/execution_readiness.md b/docs/benchmarking/execution_readiness.md index 245b3fdf..74c959d8 100644 --- a/docs/benchmarking/execution_readiness.md +++ b/docs/benchmarking/execution_readiness.md @@ -1,5 +1,10 @@ # Benchmark execution readiness +> **Historical assessment:** Statements below that generic sound was unsupported +> accurately describe the implementation when this assessment was written. The +> active [multimodal model direction](model_selection.md) now records the shipped +> FineLAP sound layer and places LongVALE and FLARE adapter validation next. + Collection index: [Benchmarking research](README.md) Status: Historical pre-implementation assessment diff --git a/docs/benchmarking/model_selection.md b/docs/benchmarking/model_selection.md new file mode 100644 index 00000000..de3acf68 --- /dev/null +++ b/docs/benchmarking/model_selection.md @@ -0,0 +1,135 @@ +# Multimodal model and benchmark direction + +Collection index: [Benchmarking research](README.md) + +Status: Current decision record; FineLAP and VideoPrism are implemented, while +other candidate providers remain planned unless architecture says otherwise + +Last verified: 2026-08-27 + +## Product requirement + +VidXP needs three independently searchable, timestamped evidence channels: + +1. visual scenes and actions; +2. environmental sounds, music, and other non-speech acoustic events; and +3. spoken words through ASR and text retrieval. + +Query-time fusion must preserve which channel produced each hit. A shared +embedding model is optional; collapsing the channels is not the requirement. +LongVALE makes this boundary explicit because its events can depend on vision, +generic audio, speech, or their temporal relationship. + +## Repository baseline + +The history before this change contained no shipped CLAP provider or generic-sound +capability. CLAP appears in the later landscape/roadmap research, not in the +application implementation history, so it was not removed by the VideoPrism +change. This branch now implements the missing layer with FineLAP; LAION-CLAP +remains the mature comparison rather than the production provider. + +VideoPrism is different: it is a current, separately registered temporal-video +capability using `google/videoprism-lvt-base-f16r288` through Transformers. The +new model direction keeps that implementation as the incumbent control while +testing whether Qwen3-VL-Embedding improves text-to-video scene/action retrieval. + +## How models are selected + +Published benchmark tables, release history, licensing, adoption, artifact +format, and runtime size are sufficient to choose the first integration +candidates. VidXP does not need to spend model or agent runs recreating public +leaderboards before implementation. + +Local evaluation has a narrower purpose: verify preprocessing, timestamps, +memory, latency, index size, failure behavior, and regressions in this repository. +It does not substitute a tiny private sample for broad published comparisons. +Promptfoo is therefore not required for component-model selection; an agent +evaluation harness would answer a different question. + +## Current provider direction + +| Role | First direction | Control or ceiling | Reason | +| --- | --- | --- | --- | +| Speech transcription and semantic search | Keep faster-whisper plus the current Qwen3 text-embedding path | Existing released-ASR benchmark paths | Speech and acoustic-event retrieval are different tasks; MAEB shows that no single audio encoder dominates linguistic and environmental-sound work. | +| Environmental-sound retrieval | [FineLAP](https://github.com/xiquan-li/FineLAP), now integrated | [LAION-CLAP](https://github.com/LAION-AI/CLAP) as the mature native-Transformers baseline | FineLAP combines global audio-text retrieval with dense frame features and leads the checked same-table AudioCaps comparison. VidXP supplies fixed ten-second windowing and timestamped dense records. | +| Open-vocabulary sound localization | FineLAP dense features, now stored; compare [PE-A-Frame](https://github.com/facebookresearch/perception_models) | AEGBench methods as research ceilings | Clip retrieval alone cannot identify exact sound intervals, especially repeated or overlapping events. FineLAP integration does not establish boundary quality until AEGBench or LongVALE is run. | +| Visual scene/action retrieval | Evaluate [Qwen3-VL-Embedding-2B](https://huggingface.co/Qwen/Qwen3-VL-Embedding-2B) as the practical candidate | Qwen3-VL-Embedding-8B as the quality ceiling; VideoPrism as the incumbent control | MVEB's text-video table ranks Qwen 8B and 2B first and second. The checked table has no directly comparable VideoPrism row, so this is stronger current selection evidence, not proof that VideoPrism lost a head-to-head. | +| Visual temporal grounding | Evaluate [TimeLens2-4B](https://github.com/MCG-NJU/TimeLens2) after candidate retrieval | TimeLens2-8B and existing temporal baselines | The published 4B average nearly matches 8B at much lower cost. TimeLens2 is visual-only and cannot replace the sound or speech channels. | +| Cross-modal fusion | Keep modality-specific providers and fuse timestamped candidates | A unified permissive audio-video-text encoder can be a later comparison | Separate providers preserve provenance, allow independent upgrades, and match the evidence that different model families lead different modalities and tasks. | + +Before promotion, every new checkpoint still needs an immutable revision, artifact +hash, license review, safe-loading review, dependency fit, and a bounded real-media +smoke test. + +## Published selection evidence + +Scores are comparable only within the named paper and task. + +| Source and task | Relevant result | Decision use | +| --- | --- | --- | +| [FineLAP, AudioCaps retrieval](https://aclanthology.org/2026.acl-long.473/) | FineLAP T→A/A→T R@1: 45.7/62.5; the paper's LAION-CLAP row: 35.1/44.2 | Select FineLAP for the first sound integration and retain CLAP as the mature control. | +| [MVEB text-video leaderboard](https://arxiv.org/abs/2606.14958) | Qwen3-VL-Embedding-8B: 60.9 mean; 2B: 58.1; LCO-Embedding-Omni-7B: 56.8 | Prefer Qwen 2B for the practical visual candidate and 8B only when maximizing published quality. | +| [TimeLens2 visual grounding](https://github.com/MCG-NJU/TimeLens2) | Seven-dataset average mIoU: 47.7 for 4B and 48.0 for 8B | Start with 4B; the 0.3-point gain does not justify making 8B the default candidate. | +| [AEGBench](https://arxiv.org/abs/2607.04383) | PE-A-Frame Large: 0.389 mIoU, 0.407 event-F1, 0.607 segment-F1 in the checked table | Use a released specialist to test exact open-vocabulary sound intervals. | + +VideoPrism remains a credible multi-frame video encoder. The decision above does +not reject it on quality. It rejects two unsupported claims: that implementation +friction still blocks it, and that it is automatically the first text-video +retrieval pick despite being absent from the current common MVEB comparison. + +## What each dataset or benchmark contributes + +| Dataset or benchmark | Use in VidXP | Does not establish | +| --- | --- | --- | +| [MAEB](https://arxiv.org/abs/2602.16008) | Broad audio-embedding selection across speech, music, environmental sound, and audio-text tasks | Long-video timestamp accuracy or end-to-end VidXP quality | +| [MVEB](https://arxiv.org/abs/2606.14958) | Common video-embedding selection across retrieval and other representation tasks, including paired video-only and audio-plus-video variants | A direct VideoPrism comparison, unrestricted temporal localization, or system latency | +| [AEGBench](https://arxiv.org/abs/2607.04383) | Open-vocabulary environmental-sound interval grounding, including difficult and repeated events | Visual or speech retrieval | +| [LongVALE](https://github.com/ttgeng233/LongVALE) | Primary combined target: Omni-TVG for vision, sound, and speech event localization in long videos | Actor clustering; its captioning tasks are relevant only if VidXP claims generation | +| [FLARE](https://flarebench.github.io/) | Secondary long-video retrieval stress test with visual-only, audio-only, and hard joint queries | Human-authored-query generalization; the queries are model-generated and filtered | +| [OVSD](https://research.ibm.com/publications/robust-and-efficient-video-scene-detection-using-optimal-sequential-grouping) | Open-licensed scene-boundary segmentation data and a useful temporal-unit regression set | Natural-language retrieval, action recognition, environmental-sound search, speech search, or cross-modal fusion | +| [MultiVENT 2.0](https://huggingface.co/datasets/hltcoe/MultiVENT2.0) | Large-corpus event retrieval for visual, ASR, OCR, and metadata channels | Generic acoustic-event retrieval or moment boundaries | + +LongVALE supplies three tasks: omni-modal temporal grounding, dense video +captioning, and segment captioning. Omni-TVG directly matches VidXP's search and +timestamp contract. The two captioning tasks should not be adopted merely because +they share the dataset. + +## Remaining benchmark gap + +A generic centralized audio or video embedding leaderboard is not new white +space: MAEB and MVEB already provide that infrastructure in the MTEB ecosystem, +and AEGBench, LongVALE, and FLARE cover adjacent temporal and multimodal slices. + +The defensible gap is narrower: a live, reproducible long-video system benchmark +that combines scene/action, environmental-sound, and speech queries; scores both +retrieval and exact boundaries; includes modality-isolation and fusion ablations; +uses realistic queries; and reports latency, memory, index size, and +commodity-hardware behavior. If VidXP publishes this, it should extend or +interoperate with the MTEB/MOEB ecosystem instead of creating an isolated model +leaderboard. + +## Cost and execution policy + +Reading published papers, leaderboards, model cards, and open benchmark metadata +does not consume Codex, Claude, or model-inference runs. Downloading and running +open checkpoints locally normally has no per-call API charge, but it does consume +the machine's storage, memory, electricity, and time; dataset and checkpoint +licenses can also restrict use. + +Metered model or agent comparisons are not part of the selection gate. Spend +local compute only after the provider exists, using the smallest smoke that can +catch integration defects. Schedule full MAEB, MVEB, LongVALE, FLARE, or AEGBench +runs only when their result answers an approved paper or release question. + +## Implementation order + +1. Validate the implemented FineLAP sound capability on a bounded real-media + sample, then run the LongVALE one-archive adapter pilot. +2. Compare LAION-CLAP as the mature integration baseline and PE-A-Frame where + boundary quality requires a specialist. +3. Add or replace the visual video-embedding provider with + Qwen3-VL-Embedding-2B while keeping current and VideoPrism controls. +4. Add TimeLens2-4B only after cheap candidate retrieval, for visual temporal + proposal or reranking work. +5. Run LongVALE Omni-TVG and FLARE with all three evidence channels and frozen + fusion. Keep OVSD as a scene-boundary component test. diff --git a/docs/benchmarking/paper_validation.md b/docs/benchmarking/paper_validation.md index a3bdfc25..c068f2b3 100644 --- a/docs/benchmarking/paper_validation.md +++ b/docs/benchmarking/paper_validation.md @@ -22,10 +22,23 @@ The inventory contained 57 paper rows when this validation pass began. The audit first added 18 omitted benchmark-defining, protocol-lineage, and direct-comparator papers, then the published-results pass added three directly relevant retrieval comparators that the first audit missed: MMMORRF, OmniEmbed-MultiVENT, and Q2E. -The reconciled inventory contains 79 unique paper rows. Every inventory paper's +The 2026-08-27 model-selection refresh added MAEB, MVEB, FineLAP, Auto-AEG/ +AEGBench, TimeLens2, and the OVSD-defining paper. The reconciled inventory now +contains 85 unique paper rows. Every inventory paper's exact source URL appears in a paper-level ledger row below; the final coverage check found zero omissions. +## Current component-model selection + +| Paper or release | Evidence checked | Actual experimental use | Measures/results reported | Validation outcome | +| --- | --- | --- | --- | --- | +| [MAEB](https://arxiv.org/abs/2602.16008) | Full text and released MTEB relationship checked | Thirty representative audio-embedding tasks selected from a 98-task pool; 50+ models across speech, music, environmental sound, and cross-modal audio-text work | Task-family metrics and aggregate/Borda comparisons | Correct broad source for audio-provider selection. It shows that speech-pretrained and contrastive audio-text models lead different domains; it does not measure long-video windowing or VidXP. | +| [MVEB](https://arxiv.org/abs/2606.14958) | Full text and main/appendix result tables checked | Twenty-three representative video-embedding tasks selected from a 184-task pool; 33 models; paired video-only and audio-plus-video variants plus modality-restricted tables | Classification, clustering, retrieval, QA, and aggregate means; text-video Table 11 | Qwen3-VL-Embedding-8B/2B rank first/second on the checked text-video table at 60.9/58.1. VideoPrism is absent, so no direct quality claim between them is valid. | +| [FineLAP](https://aclanthology.org/2026.acl-long.473/) | Full text, official repository, and checkpoint surface checked | AudioCaps/Clotho retrieval, classification, sound-event detection, and text-to-audio grounding | Retrieval R@1 plus task-specific dense metrics | Supports FineLAP as the first sound candidate: AudioCaps T→A/A→T R@1 45.7/62.5 versus the paper's LAION-CLAP 35.1/44.2. Fixed ten-second input remains a VidXP integration constraint. | +| [Auto-AEG and AEGBench](https://arxiv.org/abs/2607.04383) | Full text, HTML tables, and dataset link checked | Open-vocabulary audio event grounding over 3,427 items/9,790 queries with difficulty-stratified hard cases | mIoU, recall/precision IoU, event F1, segment F1, and onset precision/recall | Direct environmental-sound boundary benchmark. Table 3 reports PE-A-Frame Large at 0.389 mIoU/0.407 event-F1/0.607 segment-F1; the larger trained Auto-AEG system is research ceiling context. | +| [TimeLens2](https://github.com/MCG-NJU/TimeLens2) | Official paper/repository and released checkpoint table checked | Seven visual temporal-grounding datasets with 2B/4B/8B checkpoints | Average mIoU and per-dataset temporal-grounding metrics | The official release reports 47.7 average mIoU for 4B and 48.0 for 8B. Select 4B first; all variants are visual-only. | +| [OVSD defining paper](https://research.ibm.com/publications/robust-and-efficient-video-scene-detection-using-optimal-sequential-grouping) | Primary IBM publication and later dataset-use records checked | Scene-boundary segmentation over open-licensed movies and animations | Scene-segmentation measures | Useful temporal-unit regression source only. OVSD contains no text-query retrieval, action, environmental-sound, speech, or fusion objective. | + ## Whole-system and multimodal benchmark definitions | Paper or specification | Evidence checked | Actual benchmark protocol | Measures/results reported | Validation outcome | diff --git a/docs/benchmarking/published_results.md b/docs/benchmarking/published_results.md index daaa5d58..de30385e 100644 --- a/docs/benchmarking/published_results.md +++ b/docs/benchmarking/published_results.md @@ -4,7 +4,7 @@ Collection index: [Benchmarking research](README.md) Status: Primary-source result extraction complete -Last verified: 2026-07-26 +Last verified: 2026-08-27 This is the answer to “what did the published competitors actually score?” It is the result-level companion to the capability matrix in the @@ -33,6 +33,68 @@ Scores are preserved on the authors' scale. In particular, FLARE, LongVALE, VALOR-32K, CLaMR, MUVR, and several temporal-retrieval papers report percentages, while MultiVENT 2.0 and TRECVID report fractions in `[0, 1]`. +## Component-model selection results + +These results choose first integration candidates. They are not VidXP scores and +do not remove the need for bounded repository-specific validation after a provider +exists. + +### FineLAP: audio-text retrieval and dense sound features + +Source: [ACL 2026 paper](https://aclanthology.org/2026.acl-long.473/), Table 2, +proceedings pp. 10398–10399. + +| Model | AudioCaps text→audio R@1 | AudioCaps audio→text R@1 | Selection use | +| --- | ---: | ---: | --- | +| FineLAP | 45.7 | 62.5 | First environmental-sound provider; also exposes dense frame features | +| LAION-CLAP | 35.1 | 44.2 | Mature native-Transformers integration baseline | + +The same paper uses fixed ten-second FineLAP inputs and identifies variable-length +audio as future work. Long-media integration therefore still needs timestamped +windowing, overlap, and span merging owned by VidXP. + +### MVEB: current text-video embedding comparison + +Source: [MVEB paper](https://arxiv.org/pdf/2606.14958), Table 11, PDF p. 30. + +| Model | MVEB(text, video) mean | Selection use | +| --- | ---: | --- | +| Qwen3-VL-Embedding-8B | 60.9 | Published quality ceiling | +| Qwen3-VL-Embedding-2B | 58.1 | Practical first visual scene/action candidate | +| LCO-Embedding-Omni-7B | 56.8 | Strong audio-video-text context, but substantially larger | + +The checked MVEB paper contains no VideoPrism row. These numbers support the Qwen +candidate order, but they do not establish a direct Qwen-versus-VideoPrism win. +VideoPrism remains an incumbent multi-frame encoder control. + +### TimeLens2: visual temporal grounding size trade-off + +Source: [official TimeLens2 release table](https://github.com/MCG-NJU/TimeLens2), +checked 2026-08-27. + +| Checkpoint | Seven-dataset average mIoU | Selection use | +| --- | ---: | --- | +| TimeLens2-4B | 47.7 | First visual temporal-grounding candidate after cheap recall | +| TimeLens2-8B | 48.0 | Quality ceiling; not the practical default for a 0.3-point gain | + +Both checkpoints are visual-only. Neither evaluates environmental audio or spoken +content, so neither can cover LongVALE's full task alone. + +### AEGBench: open-vocabulary sound boundaries + +Source: [Auto-AEG/AEGBench](https://arxiv.org/html/2607.04383v4), Table 3. + +| Model | mIoU | Event F1 | Segment F1 | Selection use | +| --- | ---: | ---: | ---: | --- | +| PE-A-Frame Large | 0.389 | 0.407 | 0.607 | Released sound-localization specialist and integration comparator | +| DASM | 0.204 | 0.215 | 0.277 | Lower detector baseline | +| Qwen3-Omni-30B + SFT + GRPO | 0.480 | 0.524 | 0.697 | Training-heavy research ceiling, not the local first pick | + +AEGBench contains 3,427 items and 9,790 queries with multiple hard-case labels, +including repeated occurrence, polyphonic overlap, gradual boundaries, and long +duration. It is a component benchmark, not evidence of end-to-end visual/sound/ +speech fusion. + ## Whole-system and multimodal retrieval ### LongVALE: known-video omni-modal temporal grounding diff --git a/docs/benchmarking/research_papers.md b/docs/benchmarking/research_papers.md index fdc5d2c5..09911664 100644 --- a/docs/benchmarking/research_papers.md +++ b/docs/benchmarking/research_papers.md @@ -4,7 +4,7 @@ Collection index: [Benchmarking research](README.md) Status: Paper-level benchmark-use audit complete; reading queue active -Last verified: 2026-07-26 +Last verified: 2026-08-27 Related decision record: [Published benchmark catalog](benchmark_catalog.md) @@ -21,21 +21,33 @@ actually relies on. Start with these papers before reviewing individual model variants: -1. **TVR / XML** for the closest peer-reviewed corpus-level visual/transcript +1. **MAEB** and **MVEB** for the current common audio/video embedding landscape. +2. **FineLAP** and **AEGBench** for environmental-sound retrieval and boundaries. +3. **LongVALE** and **FLARE** for combined long-video vision, sound, and speech. +4. **TVR / XML** for the closest peer-reviewed corpus-level visual/transcript temporal-retrieval task. -2. **Localizing Moments in Video with Natural Language** for the simplest +5. **Localizing Moments in Video with Natural Language** for the simplest executable visual moment benchmark. -3. **QVHighlights / Moment-DETR** for modern interval and highlight evaluation. -4. **Zero-shot Video Moment Retrieval With Off-the-Shelf Models** for the closest +6. **QVHighlights / Moment-DETR** for modern interval and highlight evaluation. +7. **Zero-shot Video Moment Retrieval With Off-the-Shelf Models** for the closest methodological comparison to VidXP's untuned CLIP retrieval. -5. **HiREST** and **QuerYD** for speech-backed retrieval options. -6. **BCL** for unknown-number video face clustering and its WCP/NMI protocol. -7. **VPCD** and **C1C** for stronger person/track constraints and dataset context. -8. **LongVALE** for the strongest peer-reviewed combined vision–audio–speech - temporal benchmark. -9. **Towards a Complete Benchmark on Video Moment Localization** for cross-dataset +8. **HiREST** and **QuerYD** for speech-backed retrieval options. +9. **BCL** for unknown-number video face clustering and its WCP/NMI protocol. +10. **VPCD** and **C1C** for stronger person/track constraints and dataset context. +11. **Towards a Complete Benchmark on Video Moment Localization** for cross-dataset bias and evaluation methodology. +## Current model-selection and modality benchmarks + +| Paper | Venue/year | Benchmarks or models | Why it belongs | +| --- | --- | --- | --- | +| [MAEB: Massive Audio Embedding Benchmark](https://arxiv.org/abs/2602.16008) | arXiv 2026 | 30-task MAEB from a 98-task pool; 50+ models | Current common audio-embedding landscape across speech, music, environmental sound, and audio-text work; shows why speech and sound need separate providers | +| [MVEB: Massive Video Embedding Benchmark](https://arxiv.org/abs/2606.14958) | arXiv 2026 | 23-task MVEB from a 184-task pool; 33 models | Current common video-embedding comparison, with Qwen3-VL-Embedding leading its text-video table and paired video/audio variants | +| [FineLAP: Taming Heterogeneous Supervision for Fine-grained Language-Audio Pretraining](https://aclanthology.org/2026.acl-long.473/) | ACL 2026 | AudioCaps, Clotho, classification, sound-event detection, and text-to-audio grounding | Implemented environmental-sound provider because one model exposes both global retrieval and dense localization features | +| [Auto-AEG and AEGBench](https://arxiv.org/abs/2607.04383) | arXiv 2026 | Open-vocabulary audio-event grounding and AEGBench | Direct sound-interval benchmark for hard, repeated, and overlapping environmental events | +| [TimeLens2](https://github.com/MCG-NJU/TimeLens2) | arXiv 2026 | Seven visual temporal-grounding datasets | Supports the 4B visual-localizer choice; it has no audio input and cannot cover LongVALE alone | +| [Robust and Efficient Video Scene Detection using Optimal Sequential Grouping](https://research.ibm.com/publications/robust-and-efficient-video-scene-detection-using-optimal-sequential-grouping) | ISM 2016 | Introduces OVSD | Open-licensed semantic scene-boundary source; useful for segmentation only, not query retrieval, actions, sound, or speech | + ## Multimodal and whole-system retrieval | Paper | Venue/year | Benchmarks introduced or used | Why it belongs | diff --git a/docs/benchmarking/results.md b/docs/benchmarking/results.md index ea5e2334..0a2e8be7 100644 --- a/docs/benchmarking/results.md +++ b/docs/benchmarking/results.md @@ -152,20 +152,25 @@ The result is a useful legacy validation baseline, not a final held-out paper result. The current two-video Qwen3 smoke establishes compatibility only; it does not supersede this score. -## Next benchmark: LongVALE +## Next combined benchmark -LongVALE is the next useful experiment because it combines visual and spoken -evidence in longer videos. The immediate work is limited to: +The FineLAP environmental-sound layer is implemented but has no VidXP quality +result yet. LongVALE is the primary next experiment because it contains visual, +generic-audio, and spoken evidence in long videos. The work is ordered as follows: -1. Convert LongVALE event descriptions into VidXP visual and dialogue searches. -2. Combine those two result lists using one fixed rule. -3. Return the single start/end range required by the official evaluator. -4. Process one of the nine evaluation archives to measure runtime, temporary +1. Complete a bounded real-media FineLAP integration smoke and record resource use. +2. Convert LongVALE event descriptions into visual, sound, and dialogue searches. +3. Combine those result lists using one fixed, provenance-preserving rule. +4. Return the single start/end range required by the official evaluator. +5. Process one of the nine evaluation archives to measure runtime, temporary storage, and index growth. -5. Run the complete evaluation only if that pilot finishes cleanly. +6. Run the complete evaluation only if that pilot finishes cleanly. -VidXP does not currently understand general sound events. Sound-only misses must -remain in the official result and be disclosed rather than filtered out. +VidXP now indexes general sound events, but implementation is not evidence of +retrieval or boundary quality. The full LongVALE query set must remain in the +official denominator, including sound-only misses. See +[multimodal model direction](model_selection.md) for the selection evidence and +benchmark roles. ## Sources and reproduction diff --git a/docs/local-api.md b/docs/local-api.md index fcd41d7f..9c0a26cc 100644 --- a/docs/local-api.md +++ b/docs/local-api.md @@ -94,6 +94,12 @@ The file data stays on the computer and does not pass through MCP. As with an upload, the client can use `modalities` to select a smaller set of search features. +Use the `sound` modality to index or search music, environmental sounds, and +other non-speech audio events. CLI, HTTP, local stdio MCP, remote MCP, browser, +and Desktop all resolve that name through the same capability contract. Spoken +words remain a separate `dialogue` modality, while multi-frame visible actions +use `videoprism`. + ## Connect from another computer To make the API reachable from another device on the same trusted network, diff --git a/pyproject.toml b/pyproject.toml index fe2a0df6..78ce7670 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ authors = [ { name = "Saad Bazaz" }, ] -description = "VidXP - Video indexing and search by dialogue, scene, and actor" +description = "VidXP - Search video by dialogue, sound, scenes, actions, and actors" readme = "README.md" requires-python = ">=3.11,<3.15" license = "MIT" @@ -99,12 +99,17 @@ videoprism = { file = [ "src/vidxp/requirements/storage.txt", "src/vidxp/capabilities/videoprism/requirements.txt", ] } +sound = { file = [ + "src/vidxp/requirements/storage.txt", + "src/vidxp/capabilities/sound/requirements.txt", +] } all = { file = [ "src/vidxp/requirements/storage.txt", "src/vidxp/capabilities/dialogue/requirements.txt", "src/vidxp/capabilities/scene/requirements.txt", "src/vidxp/capabilities/actor/requirements.txt", "src/vidxp/capabilities/videoprism/requirements.txt", + "src/vidxp/capabilities/sound/requirements.txt", ] } local-worker = { file = [ "src/vidxp/requirements/storage.txt", @@ -113,6 +118,7 @@ local-worker = { file = [ "src/vidxp/capabilities/scene/requirements.txt", "src/vidxp/capabilities/actor/requirements.txt", "src/vidxp/capabilities/videoprism/requirements.txt", + "src/vidxp/capabilities/sound/requirements.txt", ] } mcp = { file = ["src/vidxp/requirements/mcp.txt"] } slm = { file = ["src/vidxp/requirements/slm.txt"] } @@ -128,6 +134,7 @@ server-worker = { file = [ "src/vidxp/capabilities/scene/requirements.txt", "src/vidxp/capabilities/actor/requirements.txt", "src/vidxp/capabilities/videoprism/requirements.txt", + "src/vidxp/capabilities/sound/requirements.txt", ] } test = { file = ["src/vidxp/requirements/test.txt"] } frontend = { file = ["src/vidxp/requirements/frontend.txt"] } @@ -146,6 +153,9 @@ torch = [ torchvision = [ { index = "pytorch-cpu", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, ] +torchaudio = [ + { index = "pytorch-cpu", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] [[tool.uv.index]] name = "pytorch-cpu" diff --git a/src/vidxp/capabilities/registry.py b/src/vidxp/capabilities/registry.py index 681d814d..c9ed84c1 100644 --- a/src/vidxp/capabilities/registry.py +++ b/src/vidxp/capabilities/registry.py @@ -503,9 +503,10 @@ def _builtin_plugins() -> tuple[CapabilityPlugin, ...]: from vidxp.capabilities.actor.definition import PLUGIN as actor from vidxp.capabilities.dialogue.definition import PLUGIN as dialogue from vidxp.capabilities.scene.definition import PLUGIN as scene + from vidxp.capabilities.sound.definition import PLUGIN as sound from vidxp.capabilities.videoprism.definition import PLUGIN as videoprism - return dialogue, scene, actor, videoprism + return dialogue, sound, scene, actor, videoprism def _external_entry_points(allowlist: tuple[str, ...]) -> tuple[EntryPoint, ...]: diff --git a/src/vidxp/capabilities/search.py b/src/vidxp/capabilities/search.py index 484abe61..9c57ddc3 100644 --- a/src/vidxp/capabilities/search.py +++ b/src/vidxp/capabilities/search.py @@ -22,6 +22,9 @@ "timestamp", "fps", "duration", + "representation", + "window_index", + "activation_index", } ) diff --git a/src/vidxp/capabilities/sound/__init__.py b/src/vidxp/capabilities/sound/__init__.py new file mode 100644 index 00000000..e64fd75b --- /dev/null +++ b/src/vidxp/capabilities/sound/__init__.py @@ -0,0 +1 @@ +"""Fine-grained sound indexing and search.""" diff --git a/src/vidxp/capabilities/sound/config.py b/src/vidxp/capabilities/sound/config.py new file mode 100644 index 00000000..0b3413b0 --- /dev/null +++ b/src/vidxp/capabilities/sound/config.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +from pydantic import Field + +from vidxp.capabilities.contracts import CapabilityConfig +from vidxp.core.contracts import IndexConfig + + +class SoundConfig(CapabilityConfig): + batch_size: int = Field(default=1, gt=0) + window_seconds: float = Field(default=10.0, gt=0, le=10.0) + + +def sound_config(config: IndexConfig) -> SoundConfig: + return SoundConfig.model_validate(config.options_for("sound")) diff --git a/src/vidxp/capabilities/sound/definition.py b/src/vidxp/capabilities/sound/definition.py new file mode 100644 index 00000000..5c0caea5 --- /dev/null +++ b/src/vidxp/capabilities/sound/definition.py @@ -0,0 +1,100 @@ +from __future__ import annotations + +from typing import Any, Mapping + +from vidxp.application_models import CapabilityRole +from vidxp.capabilities.contracts import ( + CapabilityDefinition, + CapabilityExecutor, + CapabilityPlugin, + OperationDefinition, + PreparationContext, + module_import_check, +) +from vidxp.capabilities.schemas import SearchInput, SearchResult +from vidxp.capabilities.sound.config import SoundConfig +from vidxp.capabilities.sound.models import get_sound_model +from vidxp.capabilities.sound.operations import index_capability, search_operation +from vidxp.capabilities.sound.specs import FINELAP_MODEL, SOUND_MODEL_SPECS +from vidxp.core.contracts import IndexConfig, VideoSource +from vidxp.core.indexing_common import ProgressCallback, report_preparation + + +def prepare_models( + context: PreparationContext, + progress: ProgressCallback | None, +) -> tuple[str, ...]: + SoundConfig.model_validate(context.settings) + report_preparation( + progress, + "sound_model", + f"Preparing sound model: {FINELAP_MODEL.model_id}", + ) + get_sound_model(context.runtime, download=True, progress=progress) + return tuple(spec.model_id for spec in SOUND_MODEL_SPECS) + + +def model_manifest( + _config: IndexConfig, + _sources: tuple[VideoSource, ...], +) -> Mapping[str, Any]: + return { + "sound": FINELAP_MODEL.identity(), + "sound_text_assets": [ + spec.identity() for spec in SOUND_MODEL_SPECS[1:] + ], + } + + +DEFINITION = CapabilityDefinition( + name="sound", + description="Index and search music, environmental sounds, and audio events.", + extra="sound", + config_model=SoundConfig, + collection_name="sound", + index_stage="sound_indexing", + execution_group="sound", + prepares_models=True, + roles=(CapabilityRole.searchable, CapabilityRole.queryable), + model_specs=SOUND_MODEL_SPECS, + operations={ + "search": OperationDefinition( + input_model=SearchInput, + output_model=SearchResult, + ) + }, +) + + +def create_executor() -> CapabilityExecutor: + return CapabilityExecutor( + indexer=index_capability, + operations={"search": search_operation}, + prepare=prepare_models, + model_manifest=model_manifest, + runtime_checks=( + module_import_check("PyAV audio import", "av", "AudioResampler"), + module_import_check("NumPy import", "numpy"), + module_import_check("Torch import", "torch"), + module_import_check("TorchAudio import", "torchaudio"), + module_import_check("timm import", "timm"), + module_import_check( + "Transformers FineLAP import", + "transformers", + "AutoConfig", + "RobertaModel", + "RobertaTokenizer", + ), + module_import_check( + "Hugging Face Hub import", + "huggingface_hub", + "snapshot_download", + ), + ), + ) + + +PLUGIN = CapabilityPlugin( + definition=DEFINITION, + executor_factory=create_executor, +) diff --git a/src/vidxp/capabilities/sound/indexing.py b/src/vidxp/capabilities/sound/indexing.py new file mode 100644 index 00000000..3ab601b2 --- /dev/null +++ b/src/vidxp/capabilities/sound/indexing.py @@ -0,0 +1,265 @@ +from __future__ import annotations + +from dataclasses import dataclass +from itertools import chain, islice +from pathlib import Path +from typing import Any, Iterable, Sequence + +from vidxp.capabilities.sound.config import sound_config +from vidxp.capabilities.sound.models import get_sound_model +from vidxp.capabilities.sound.specs import FINELAP_MODEL +from vidxp.core.contracts import ( + CancellationToken, + IndexConfig, + StorageRecord, + VideoSource, + stable_source_id, +) +from vidxp.core.indexing_common import ProgressCallback, report_progress +from vidxp.ports import IndexStore, ModelRuntimePort + + +SAMPLE_RATE = 16_000 +PCM_BYTES_PER_SAMPLE = 2 +DENSE_INTERVAL_SECONDS = 0.16 + + +@dataclass(frozen=True) +class AudioWindow: + index: int + start: float + end: float + pcm: bytes + + +def _resampled_pcm_frames(frame: Any, resampler: Any) -> Iterable[bytes]: + import numpy as np + + converted = resampler.resample(frame) + if converted is None: + return + frames = converted if isinstance(converted, list) else [converted] + for item in frames: + values = item.to_ndarray().reshape(-1) + yield np.asarray(values, dtype=" Iterable[AudioWindow]: + import av + + samples_per_window = round(window_seconds * SAMPLE_RATE) + bytes_per_window = samples_per_window * PCM_BYTES_PER_SAMPLE + pending = bytearray() + emitted_samples = 0 + window_index = 0 + with av.open(str(input_path)) as container: + if not container.streams.audio: + return + max_samples = ( + round(float(container.duration * av.time_base) * SAMPLE_RATE) + if container.duration is not None + else None + ) + + def append_pcm(block: bytes) -> None: + if max_samples is None: + pending.extend(block) + return + buffered_samples = len(pending) // PCM_BYTES_PER_SAMPLE + remaining = max_samples - emitted_samples - buffered_samples + if remaining > 0: + pending.extend(block[: remaining * PCM_BYTES_PER_SAMPLE]) + + stream = container.streams.audio[0] + resampler = av.AudioResampler( + format="s16", + layout="mono", + rate=SAMPLE_RATE, + ) + for frame in container.decode(stream): + cancellation.raise_if_cancelled() + for block in _resampled_pcm_frames(frame, resampler): + append_pcm(block) + while len(pending) >= bytes_per_window: + pcm = bytes(pending[:bytes_per_window]) + del pending[:bytes_per_window] + start = emitted_samples / SAMPLE_RATE + emitted_samples += samples_per_window + yield AudioWindow( + index=window_index, + start=start, + end=emitted_samples / SAMPLE_RATE, + pcm=pcm, + ) + window_index += 1 + for block in _resampled_pcm_frames(None, resampler): + append_pcm(block) + if pending: + sample_count = len(pending) // PCM_BYTES_PER_SAMPLE + if sample_count: + pcm = bytes(pending[: sample_count * PCM_BYTES_PER_SAMPLE]) + start = emitted_samples / SAMPLE_RATE + yield AudioWindow( + index=window_index, + start=start, + end=(emitted_samples + sample_count) / SAMPLE_RATE, + pcm=pcm, + ) + + +def _window_batches( + windows: Iterable[AudioWindow], + batch_size: int, +) -> Iterable[tuple[AudioWindow, ...]]: + iterator = iter(windows) + while group := tuple(islice(iterator, batch_size)): + yield group + + +def sound_records( + windows: Sequence[AudioWindow], + global_embeddings: Any, + dense_embeddings: Any, + config: IndexConfig, +) -> list[StorageRecord]: + records = [] + for window, global_vector, dense_vectors in zip( + windows, + global_embeddings, + dense_embeddings, + ): + window_id = stable_source_id( + config.run_id, + str(config.video_id), + "sound", + f"w{window.index:08d}", + generation_id=config.generation_id, + ) + records.append( + StorageRecord( + source_id=window_id, + embedding=global_vector.tolist(), + metadata={ + **config.record_identity("sound", window_id), + "representation": "window", + "window_index": window.index, + "start": window.start, + "end": window.end, + "duration": round(window.end - window.start, 6), + }, + ) + ) + for activation_index, dense_vector in enumerate(dense_vectors): + start = round( + window.start + activation_index * DENSE_INTERVAL_SECONDS, + 6, + ) + if start >= window.end: + break + end = round( + min(window.end, start + DENSE_INTERVAL_SECONDS), + 6, + ) + source_id = stable_source_id( + config.run_id, + str(config.video_id), + "sound", + f"w{window.index:08d}-a{activation_index:04d}", + generation_id=config.generation_id, + ) + records.append( + StorageRecord( + source_id=source_id, + embedding=dense_vector.tolist(), + metadata={ + **config.record_identity("sound", source_id), + "representation": "activation", + "window_index": window.index, + "activation_index": activation_index, + "start": start, + "end": end, + "duration": round(end - start, 6), + }, + ) + ) + return records + + +def index_sound( + source: VideoSource, + *, + config: IndexConfig, + storage: IndexStore, + cancellation: CancellationToken, + runtime: ModelRuntimePort, + progress: ProgressCallback | None = None, +) -> dict[str, Any]: + if config.video_id is None: + raise ValueError("IndexConfig.video_id is required for indexing.") + if source.path is None: + raise ValueError("Sound indexing requires a video or audio path.") + settings = sound_config(config) + windows = iter_audio_windows( + source.path, + window_seconds=settings.window_seconds, + cancellation=cancellation, + ) + groups = iter(_window_batches(windows, settings.batch_size)) + first_group = next(groups, None) + if first_group is None: + report_progress( + progress, + "sound_skipped", + "No audio samples were found; sound indexing was skipped.", + ) + return {"sound_windows": 0, "sound_activations": 0} + report_progress( + progress, + "preparing_sound_model", + f"Preparing sound model: {FINELAP_MODEL.model_id}.", + ) + provider = get_sound_model(runtime) + report_progress( + progress, + "sound_indexing", + "Indexing sound windows and dense activations.", + 0, + None, + ) + stored_windows = 0 + stored_activations = 0 + for group in chain((first_group,), groups): + cancellation.raise_if_cancelled() + global_embeddings, dense_embeddings = provider.encode_audio( + [window.pcm for window in group] + ) + records = sound_records( + group, + global_embeddings, + dense_embeddings, + config, + ) + storage.upsert( + "sound", + records, + batch_size=config.storage_batch_size, + cancellation=cancellation, + ) + stored_windows += len(group) + stored_activations += len(records) - len(group) + report_progress( + progress, + "sound_indexing", + "Indexing sound windows and dense activations.", + stored_windows, + None, + ) + return { + "sound_windows": stored_windows, + "sound_activations": stored_activations, + } diff --git a/src/vidxp/capabilities/sound/models.py b/src/vidxp/capabilities/sound/models.py new file mode 100644 index 00000000..00dbebd1 --- /dev/null +++ b/src/vidxp/capabilities/sound/models.py @@ -0,0 +1,228 @@ +from __future__ import annotations + +from dataclasses import dataclass +import sys +from typing import Any, Callable, Sequence + +from vidxp.capabilities.sound.specs import ( + FINELAP_MODEL, + ROBERTA_CONFIG, + ROBERTA_MERGES, + ROBERTA_VOCAB, +) +from vidxp.core.indexing_common import report_preparation +from vidxp.model_contracts import loaded_compute_precision +from vidxp.ports import ModelRuntimePort + + +@dataclass(frozen=True) +class FineLAPProvider: + model: Any + device: str + + def encode_audio( + self, + pcm_windows: Sequence[bytes], + ) -> tuple[Any, Any]: + """Return normalized global and dense embeddings in one audio pass.""" + import torch + import torchaudio + + mels = [] + for pcm in pcm_windows: + waveform = ( + torch.frombuffer(bytearray(pcm), dtype=torch.int16) + .to(torch.float32) + / 32768.0 + ) + if waveform.numel() < 400: + waveform = torch.nn.functional.pad( + waveform, + (0, 400 - waveform.numel()), + ) + waveform = waveform - waveform.mean() + mel = torchaudio.compliance.kaldi.fbank( + waveform.unsqueeze(0), + htk_compat=True, + sample_frequency=16_000, + use_energy=False, + window_type="hanning", + num_mel_bins=128, + dither=0.0, + frame_shift=10, + ) + if mel.shape[0] < 1024: + mel = torch.nn.functional.pad( + mel, + (0, 0, 0, 1024 - mel.shape[0]), + ) + else: + mel = mel[:1024, :] + mels.append((mel - (-4.268)) / (4.569 * 2)) + mel_batch = torch.stack(mels, dim=0).unsqueeze(1).to(self.device) + + with torch.inference_mode(): + outputs = self.model.audio_encoder.extract_features(mel_batch) + raw = outputs["x"] if isinstance(outputs, dict) else outputs + batch, tokens, width = raw[:, 1:, :].shape + patches = raw[:, 1:, :].reshape( + batch, + tokens // 8, + 8, + width, + ).mean(dim=2) + features = torch.cat([raw[:, 0:1, :], patches], dim=1) + global_embeddings = torch.nn.functional.normalize( + self.model.global_audio_proj(features[:, 0, :]), + dim=-1, + ) + dense_embeddings = self.model.local_audio_proj(features[:, 1:, :]) + if self.model.local_audio_proj_type == "rnn": + dense_embeddings = dense_embeddings[0] + if self.model.config.normalize_dense_audio_embeds: + dense_embeddings = torch.nn.functional.normalize( + dense_embeddings, + dim=-1, + ) + return global_embeddings.cpu(), dense_embeddings.cpu() + + def encode_text(self, query: str) -> list[float]: + import torch + + with torch.inference_mode(): + embedding = self.model.get_global_text_embeds( + [query], + device=self.device, + ) + return embedding.cpu().numpy().tolist()[0] + + +def _load_finelap_class(snapshot: str, module_cache: str) -> type: + from transformers import AutoConfig + from transformers import dynamic_module_utils + + dynamic_module_utils.HF_MODULES_CACHE = module_cache + dynamic_module_utils.init_hf_modules() + + config = AutoConfig.from_pretrained( + snapshot, + trust_remote_code=True, + local_files_only=True, + ) + class_reference = config.auto_map["AutoModel"] + return dynamic_module_utils.get_class_from_dynamic_module( + class_reference, + snapshot, + local_files_only=True, + ) + + +def _load_finelap_model( + model_class: type, + snapshot: str, + *, + config_path: str, + vocab_path: str, + merges_path: str, +) -> Any: + """Load pinned FineLAP code without an implicit RoBERTa download. + + FineLAP's checkpoint already contains the trained RoBERTa weights, but its + constructor asks Transformers to fetch a second copy of roberta-base. The + two small tokenizer assets are prepared explicitly; the temporary module + factories only initialize the architecture that the FineLAP checkpoint + immediately fills. + """ + from transformers import AutoConfig, RobertaConfig, RobertaModel + from transformers import RobertaTokenizer + + module = sys.modules[model_class.__module__] + + class OfflineRobertaModel: + @classmethod + def from_pretrained(cls, *_args: Any, **_kwargs: Any) -> Any: + return RobertaModel( + RobertaConfig.from_json_file(config_path), + add_pooling_layer=False, + ) + + class OfflineRobertaTokenizer: + @classmethod + def from_pretrained(cls, *_args: Any, **_kwargs: Any) -> Any: + return RobertaTokenizer( + vocab_file=vocab_path, + merges_file=merges_path, + model_max_length=512, + ) + + module.RobertaModel = OfflineRobertaModel + module.RobertaTokenizer = OfflineRobertaTokenizer + config = AutoConfig.from_pretrained( + snapshot, + trust_remote_code=True, + local_files_only=True, + ) + return model_class.from_pretrained( + snapshot, + config=config, + local_files_only=True, + ) + + +def get_sound_model( + runtime: ModelRuntimePort, + *, + download: bool = False, + progress: Callable[[dict[str, Any]], None] | None = None, +) -> FineLAPProvider: + device = runtime.device_for("sound") + key = FINELAP_MODEL.key(device) + + def load() -> FineLAPProvider: + snapshot = runtime.resolve_model( + FINELAP_MODEL, + download=download, + progress=progress, + ) + config_path = runtime.resolve_artifact( + ROBERTA_CONFIG, + download=download, + progress=progress, + ) + vocab_path = runtime.resolve_artifact( + ROBERTA_VOCAB, + download=download, + progress=progress, + ) + merges_path = runtime.resolve_artifact( + ROBERTA_MERGES, + download=download, + progress=progress, + ) + report_preparation( + progress, + "loading_model", + f"Loading {FINELAP_MODEL.model_id}.", + ) + model_class = _load_finelap_class( + str(snapshot), + str(runtime.model_cache / "transformers_modules"), + ) + model = _load_finelap_model( + model_class, + str(snapshot), + config_path=str(config_path), + vocab_path=str(vocab_path), + merges_path=str(merges_path), + ).to(device) + model.eval() + runtime.record_compute_precision( + FINELAP_MODEL.capability, + loaded_compute_precision( + model, + fallback=FINELAP_MODEL.weights_precision, + ), + ) + return FineLAPProvider(model=model, device=device) + + return runtime.get_or_load(key, load) diff --git a/src/vidxp/capabilities/sound/operations.py b/src/vidxp/capabilities/sound/operations.py new file mode 100644 index 00000000..89d10e24 --- /dev/null +++ b/src/vidxp/capabilities/sound/operations.py @@ -0,0 +1,110 @@ +from __future__ import annotations + +from typing import Any, Mapping + +from vidxp.capabilities.contracts import ( + CapabilityContext, + CapabilityIndexResult, +) +from vidxp.capabilities.registry import CapabilityRegistry +from vidxp.capabilities.schemas import SearchInput, SearchResult +from vidxp.capabilities.search import search_embeddings +from vidxp.capabilities.sound.indexing import index_sound +from vidxp.capabilities.sound.models import get_sound_model +from vidxp.core.contracts import ( + CancellationToken, + IndexConfig, + VideoSource, +) +from vidxp.core.indexing_common import ProgressCallback +from vidxp.ports import IndexStore, ModelRuntimePort + + +REQUIRED_METADATA = frozenset( + { + "dataset", + "split", + "run_id", + "video_id", + "source_id", + "start", + "end", + "representation", + "window_index", + "modality", + } +) + + +def index_capability( + source: VideoSource, + *, + config: IndexConfig, + storage: IndexStore, + cancellation: CancellationToken, + registry: CapabilityRegistry, + runtime: ModelRuntimePort, + progress: ProgressCallback | None = None, + modalities: tuple[str, ...] = ("sound",), +) -> CapabilityIndexResult: + if modalities != ("sound",): + raise ValueError("The sound indexer only accepts sound.") + return CapabilityIndexResult( + summary=index_sound( + source, + config=config, + storage=storage, + cancellation=cancellation, + runtime=runtime, + progress=progress, + ) + ) + + +def sound_embedding(query: str, runtime: ModelRuntimePort) -> list[float]: + return get_sound_model(runtime).encode_text(query) + + +def search_sound( + query: str, + *, + config: IndexConfig, + runtime: ModelRuntimePort, + top_k: int = 10, + video_id: str | None = None, + query_id: str | None = None, + filters: Mapping[str, Any] | None = None, + storage: IndexStore, +) -> SearchResult: + cleaned = query.strip() + if not cleaned: + raise ValueError("Search query must not be empty.") + if top_k <= 0: + raise ValueError("top_k must be greater than zero.") + return search_embeddings( + cleaned, + "sound", + sound_embedding(cleaned, runtime), + config=config, + required_metadata=REQUIRED_METADATA, + top_k=top_k, + video_id=video_id, + query_id=query_id, + filters=filters, + storage=storage, + ) + + +def search_operation( + context: CapabilityContext, + request: SearchInput, +) -> SearchResult: + config = context.require_config() + return search_sound( + request.query, + config=config, + top_k=request.top_k, + video_id=request.media_id or config.video_id, + runtime=context.runtime, + storage=context.require_storage(), + ) diff --git a/src/vidxp/capabilities/sound/requirements.txt b/src/vidxp/capabilities/sound/requirements.txt new file mode 100644 index 00000000..fb1a650b --- /dev/null +++ b/src/vidxp/capabilities/sound/requirements.txt @@ -0,0 +1,8 @@ +av>=18,<19 +numpy>=2.3,<3 +torch>=2.13,<3 +torchaudio>=2.11,<2.12 +timm>=1.0.20,<2 +transformers>=5.14.1,<6 +huggingface-hub>=1.25.1,<2 +matplotlib>=3.10,<4 diff --git a/src/vidxp/capabilities/sound/specs.py b/src/vidxp/capabilities/sound/specs.py new file mode 100644 index 00000000..421ea6e9 --- /dev/null +++ b/src/vidxp/capabilities/sound/specs.py @@ -0,0 +1,76 @@ +from vidxp.model_contracts import ArtifactSpec, ModelSpec + + +FINELAP_MODEL = ModelSpec( + capability="sound.embedding", + provider="transformers", + model_id="AndreasXi/FineLAP", + revision="b419aa22947d29907a5567f21b81bf3b39a40449", + download_size_bytes=980_404_741, + weights_file="model.safetensors", + weights_sha256=( + "13b9646c9f9d48513c0145bed75e654179e83f0fd8d49ed4ffc5d6b8f3353fb4" + ), + license="MIT (model card)", + weights_precision="float32", +) + +ROBERTA_REVISION = "e2da8e2f811d1448a5b465c236feacd80ffbac7b" +ROBERTA_CONFIG = ArtifactSpec( + capability="sound.text_encoder.config", + provider="finelap-tokenizer", + model_id="FacebookAI/roberta-base config", + revision=ROBERTA_REVISION, + download_size_bytes=481, + url=( + "https://huggingface.co/FacebookAI/roberta-base/resolve/" + f"{ROBERTA_REVISION}/config.json" + ), + filename="config.json", + sha256=( + "ef0185e2aae6e06c5f105a285006952c340e20c7dbf43c86ec82601b13fc45e9" + ), + license="MIT", + weights_precision="not applicable", +) +ROBERTA_VOCAB = ArtifactSpec( + capability="sound.tokenizer.vocab", + provider="finelap-tokenizer", + model_id="FacebookAI/roberta-base vocab", + revision=ROBERTA_REVISION, + download_size_bytes=898_823, + url=( + "https://huggingface.co/FacebookAI/roberta-base/resolve/" + f"{ROBERTA_REVISION}/vocab.json" + ), + filename="vocab.json", + sha256=( + "9e7f63c2d15d666b52e21d250d2e513b87c9b713cfa6987a82ed89e5e6e50655" + ), + license="MIT", + weights_precision="not applicable", +) +ROBERTA_MERGES = ArtifactSpec( + capability="sound.tokenizer.merges", + provider="finelap-tokenizer", + model_id="FacebookAI/roberta-base merges", + revision=ROBERTA_REVISION, + download_size_bytes=456_318, + url=( + "https://huggingface.co/FacebookAI/roberta-base/resolve/" + f"{ROBERTA_REVISION}/merges.txt" + ), + filename="merges.txt", + sha256=( + "1ce1664773c50f3e0cc8842619a93edc4624525b728b188a9e0be33b7726adc5" + ), + license="MIT", + weights_precision="not applicable", +) + +SOUND_MODEL_SPECS = ( + FINELAP_MODEL, + ROBERTA_CONFIG, + ROBERTA_VOCAB, + ROBERTA_MERGES, +) diff --git a/src/vidxp/frontend.py b/src/vidxp/frontend.py index a9fa7f20..ae3d4b04 100644 --- a/src/vidxp/frontend.py +++ b/src/vidxp/frontend.py @@ -133,6 +133,7 @@ def _settings_from_arguments( "dialogue": "Dialogue search", "natural-language": "Ask a question", "scene": "Scene search", + "sound": "Sound event search (FineLAP)", "videoprism": "Temporal action search (VideoPrism)", } @@ -907,7 +908,7 @@ def run(): service = _configured_service() st.title("VidXP") st.caption( - "Index and search video by dialogue, scene, temporal clips, and actor." + "Index and search video by dialogue, sound, scenes, actions, and actor." ) st.caption(f"Index repository: {service.layout.root}") if notice := st.session_state.pop(MEDIA_NOTICE_KEY, None): diff --git a/tests/test_capabilities.py b/tests/test_capabilities.py index 9edd0d72..dc7a10cc 100644 --- a/tests/test_capabilities.py +++ b/tests/test_capabilities.py @@ -25,6 +25,7 @@ ) from vidxp.capability_service import CapabilityService from vidxp.capabilities.scene.config import SceneConfig +from vidxp.capabilities.sound.config import SoundConfig from vidxp.capabilities.videoprism.config import VideoPrismConfig from vidxp.core.contracts import IndexConfig from vidxp.core.runner import _index_groups @@ -63,17 +64,18 @@ def test_module_import_checks_run_in_an_isolated_process(self): def test_registry_drives_capability_metadata(self): self.assertEqual( self.registry.names(), - ("dialogue", "scene", "actor", "videoprism"), + ("dialogue", "sound", "scene", "actor", "videoprism"), ) self.assertEqual(self.registry.index_names(), self.registry.names()) self.assertEqual( self.registry.preparable_names(), - ("dialogue", "scene", "actor", "videoprism"), + ("dialogue", "sound", "scene", "actor", "videoprism"), ) self.assertEqual( self.registry.collection_names(), { "dialogue": "dialogue", + "sound": "sound", "scene": "scene", "actor": "actor", "videoprism": "videoprism", @@ -142,6 +144,7 @@ def test_built_in_settings_are_owned_and_validated(self): ) self.assertIs(self.registry.get("scene").config_model, SceneConfig) self.assertIs(self.registry.get("actor").config_model, ActorConfig) + self.assertIs(self.registry.get("sound").config_model, SoundConfig) self.assertIs( self.registry.get("videoprism").config_model, VideoPrismConfig, @@ -213,10 +216,14 @@ def test_operation_only_capability_needs_no_index_metadata(self): def test_visual_execution_group_is_explicit(self): self.assertEqual( _index_groups( - ("dialogue", "scene", "actor", "videoprism"), + ("dialogue", "sound", "scene", "actor", "videoprism"), self.registry, ), - (("dialogue",), ("scene", "actor", "videoprism")), + ( + ("dialogue",), + ("sound",), + ("scene", "actor", "videoprism"), + ), ) self.assertIsNotNone( self.registry.executor("scene").index_processor @@ -227,6 +234,7 @@ def test_visual_execution_group_is_explicit(self): self.assertIsNone( self.registry.executor("dialogue").index_processor ) + self.assertIsNone(self.registry.executor("sound").index_processor) def test_core_config_has_no_provider_specific_fields(self): fields = IndexConfig.__dataclass_fields__ diff --git a/tests/test_frontend.py b/tests/test_frontend.py index 464384a3..d03329a0 100644 --- a/tests/test_frontend.py +++ b/tests/test_frontend.py @@ -90,12 +90,12 @@ def test_query_modalities_use_real_capability_service_contracts(self): return_value=service, ): available = frontend._available_query_modalities( - ("dialogue", "scene", "actor", "videoprism"), + ("dialogue", "sound", "scene", "actor", "videoprism"), ) self.assertEqual( available, - ("dialogue", "scene", "actor", "videoprism"), + ("dialogue", "sound", "scene", "actor", "videoprism"), ) def tearDown(self): @@ -339,7 +339,10 @@ def check(command): ): available = frontend._available_index_modalities() - self.assertEqual(available, ("dialogue", "scene", "videoprism")) + self.assertEqual( + available, + ("dialogue", "sound", "scene", "videoprism"), + ) self.assertTrue( all( not call.args[0].include_runtime_checks diff --git a/tests/test_frontend_app.py b/tests/test_frontend_app.py index 0b74bcd7..bb300601 100644 --- a/tests/test_frontend_app.py +++ b/tests/test_frontend_app.py @@ -161,7 +161,7 @@ def ready_status() -> IndexStatus: snapshot_id=SNAPSHOT_ID, media_count=1, media_ids=(MEDIA_ID,), - modalities=("dialogue", "scene", "actor", "videoprism"), + modalities=("dialogue", "sound", "scene", "actor", "videoprism"), ), ) diff --git a/tests/test_local_probe.py b/tests/test_local_probe.py index 2832c404..8b23d90f 100644 --- a/tests/test_local_probe.py +++ b/tests/test_local_probe.py @@ -247,7 +247,7 @@ def test_non_windows_launcher_resolution_does_not_add_executable_suffix(self): def test_desktop_model_catalog_is_derived_from_canonical_specs(self): catalog = desktop_model_cache_catalog() - self.assertEqual(len(catalog), 6) + self.assertEqual(len(catalog), 10) self.assertEqual( {item["id"] for item in catalog}, { @@ -255,6 +255,10 @@ def test_desktop_model_catalog_is_derived_from_canonical_specs(self): "google/videoprism-lvt-base-f16r288", "Qwen/Qwen3-Embedding-0.6B", "dropbox-dash/faster-whisper-large-v3-turbo", + "AndreasXi/FineLAP", + "FacebookAI/roberta-base config", + "FacebookAI/roberta-base vocab", + "FacebookAI/roberta-base merges", "yunet", "sface", }, diff --git a/tests/test_mcp.py b/tests/test_mcp.py index 3f44ec3d..d480138d 100644 --- a/tests/test_mcp.py +++ b/tests/test_mcp.py @@ -2734,7 +2734,7 @@ async def test_stdio_entrypoint_serves_the_filesystem_aware_surface(self): ) self.assertEqual( [item["name"] for item in result.structured_content["items"]], - ["dialogue", "scene", "actor", "videoprism"], + ["dialogue", "sound", "scene", "actor", "videoprism"], ) async def test_streamable_http_works_with_the_official_remote_client(self): diff --git a/tests/test_sound.py b/tests/test_sound.py new file mode 100644 index 00000000..165f1a66 --- /dev/null +++ b/tests/test_sound.py @@ -0,0 +1,234 @@ +from pathlib import Path +from tempfile import TemporaryDirectory +import unittest +from unittest.mock import Mock, patch +import wave + +from vidxp.capabilities.sound.config import SoundConfig +from vidxp.capabilities.sound.indexing import ( + AudioWindow, + index_sound, + iter_audio_windows, + sound_records, +) +from vidxp.capabilities.sound.operations import search_sound +from vidxp.capabilities.sound.specs import ( + FINELAP_MODEL, + ROBERTA_CONFIG, + ROBERTA_MERGES, + ROBERTA_VOCAB, +) +from vidxp.core.contracts import CancellationToken, IndexConfig, VideoSource + + +MEDIA_ID = "123456781234423481234567890abcde" +GENERATION_ID = "223456781234423481234567890abcde" + + +class Vector(list): + def tolist(self): + return list(self) + + +class SoundTests(unittest.TestCase): + def config(self, **options): + return IndexConfig.local( + video_id=MEDIA_ID, + enabled_modalities=("sound",), + generation_id=GENERATION_ID, + capability_options={"sound": options}, + ) + + def test_config_limits_windows_to_finelap_input_length(self): + self.assertEqual(SoundConfig().window_seconds, 10.0) + with self.assertRaises(ValueError): + SoundConfig(window_seconds=10.1) + + def test_specs_pin_model_and_explicit_tokenizer_assets(self): + self.assertEqual(FINELAP_MODEL.model_id, "AndreasXi/FineLAP") + self.assertEqual(len(FINELAP_MODEL.revision), 40) + self.assertEqual(ROBERTA_VOCAB.revision, ROBERTA_MERGES.revision) + self.assertEqual(ROBERTA_CONFIG.revision, ROBERTA_VOCAB.revision) + self.assertIn(ROBERTA_CONFIG.revision, ROBERTA_CONFIG.url) + self.assertIn(ROBERTA_VOCAB.revision, ROBERTA_VOCAB.url) + self.assertIn(ROBERTA_MERGES.revision, ROBERTA_MERGES.url) + + def test_records_include_window_and_dense_activation_intervals(self): + config = self.config() + windows = ( + AudioWindow(0, 0.0, 10.0, b""), + AudioWindow(1, 10.0, 12.0, b""), + ) + global_embeddings = (Vector([1.0, 0.0]), Vector([0.5, 0.5])) + dense_embeddings = ( + [Vector([1.0, 0.0]) for _ in range(64)], + [Vector([0.5, 0.5]) for _ in range(64)], + ) + + records = sound_records( + windows, + global_embeddings, + dense_embeddings, + config, + ) + + self.assertEqual(len(records), 78) + self.assertEqual(records[0].metadata["representation"], "window") + self.assertEqual(records[1].metadata["representation"], "activation") + self.assertEqual(records[1].metadata["start"], 0.0) + self.assertEqual(records[1].metadata["end"], 0.16) + self.assertAlmostEqual(records[-1].metadata["start"], 11.92) + self.assertEqual(records[-1].metadata["end"], 12.0) + self.assertTrue( + all(record.metadata["generation_id"] == GENERATION_ID for record in records) + ) + + def test_audio_decode_resamples_and_preserves_source_duration(self): + with TemporaryDirectory() as directory: + path = Path(directory) / "sample.wav" + with wave.open(str(path), "wb") as output: + output.setnchannels(1) + output.setsampwidth(2) + output.setframerate(8_000) + output.writeframes(b"\0\0" * 4_000) + + windows = list( + iter_audio_windows( + path, + window_seconds=10.0, + cancellation=CancellationToken(), + ) + ) + + self.assertEqual(len(windows), 1) + self.assertEqual(windows[0].start, 0.0) + self.assertEqual(windows[0].end, 0.5) + self.assertEqual(len(windows[0].pcm), 16_000) + + def test_index_stores_global_and_dense_records_in_one_collection(self): + config = self.config() + windows = ( + AudioWindow(0, 0.0, 10.0, b"\0\0" * 16), + AudioWindow(1, 10.0, 12.0, b"\0\0" * 16), + ) + provider = Mock() + provider.encode_audio.return_value = ( + (Vector([1.0]), Vector([2.0])), + ( + [Vector([1.0]) for _ in range(64)], + [Vector([2.0]) for _ in range(64)], + ), + ) + storage = Mock() + storage.upsert.return_value = 78 + + with ( + TemporaryDirectory() as directory, + patch( + "vidxp.capabilities.sound.indexing.iter_audio_windows", + return_value=iter(windows), + ), + patch( + "vidxp.capabilities.sound.indexing.get_sound_model", + return_value=provider, + ), + ): + summary = index_sound( + VideoSource(path=Path(directory) / "video.mp4"), + config=config, + storage=storage, + cancellation=CancellationToken(), + runtime=Mock(), + ) + + self.assertEqual( + summary, + {"sound_windows": 2, "sound_activations": 76}, + ) + self.assertEqual(storage.upsert.call_count, 2) + self.assertTrue( + all(call.args[0] == "sound" for call in storage.upsert.call_args_list) + ) + self.assertEqual( + sum(len(call.args[1]) for call in storage.upsert.call_args_list), + 78, + ) + + def test_index_skips_media_without_audio_before_loading_model(self): + with ( + patch( + "vidxp.capabilities.sound.indexing.iter_audio_windows", + return_value=iter(()), + ), + patch( + "vidxp.capabilities.sound.indexing.get_sound_model", + ) as get_model, + ): + summary = index_sound( + VideoSource(path="silent.mp4"), + config=self.config(), + storage=Mock(), + cancellation=CancellationToken(), + runtime=Mock(), + ) + + self.assertEqual( + summary, + {"sound_windows": 0, "sound_activations": 0}, + ) + get_model.assert_not_called() + + def test_sound_search_uses_shared_search_contract_and_public_metadata(self): + config = self.config() + storage = Mock() + storage.query.return_value = [ + { + "source_id": "sound:1", + "raw_distance": 0.2, + "metadata": { + **config.record_identity("sound", "sound:1"), + "generation_id": GENERATION_ID, + "representation": "activation", + "window_index": 3, + "activation_index": 9, + "start": 31.4, + "end": 31.6, + "private": "hidden", + }, + } + ] + provider = Mock() + provider.encode_text.return_value = [0.1, 0.2] + + with patch( + "vidxp.capabilities.sound.operations.get_sound_model", + return_value=provider, + ): + result = search_sound( + "dog barking", + config=config, + runtime=Mock(), + storage=storage, + ) + + self.assertEqual(result.modality, "sound") + self.assertEqual(result.hits[0].start, 31.4) + self.assertEqual( + result.hits[0].metadata, + { + "representation": "activation", + "window_index": 3, + "activation_index": 9, + }, + ) + storage.query.assert_called_once_with( + "sound", + [0.1, 0.2], + top_k=10, + video_id=None, + filters=None, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/uv.lock b/uv.lock index f2dcccdb..f1867cd7 100644 --- a/uv.lock +++ b/uv.lock @@ -600,6 +600,89 @@ 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 = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, + { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, + { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, + { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, + { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, + { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, + { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, + { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, + { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, + { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, +] + [[package]] name = "cryptography" version = "50.0.0" @@ -694,6 +777,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c0/03/126e963fc3237a416f3085b8a663ebd8ab449ed6c37195b4e0b49597ba0c/ctranslate2-4.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:dc9f1abef55579cc02cdc74b3a55df38491ec56d177d6e6039609d61d09ed30e", size = 19499597, upload-time = "2026-07-03T12:40:01.68Z" }, ] +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + [[package]] name = "dbos" version = "2.29.0" @@ -778,6 +870,55 @@ 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 = "fonttools" +version = "4.63.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/69/c97f2c18e0db87d2c7b15da1974dace76ae938f1cfa22e2727a648b7ed43/fonttools-4.63.0.tar.gz", hash = "sha256:caeb583deeb5168e694b65cda8b4ee62abedfa66cf88488734466f2366b9c4e0", size = 3597189, upload-time = "2026-05-14T12:04:30.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/2b/a7f1545bdf5da69c4bda0cea2a5781f0ad2a6623e0277267672db43c5fe6/fonttools-4.63.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b8ae05d9eacf6081414d759c0a352769ac28ce31280d6bb8e77b03f9e3c449f", size = 2881793, upload-time = "2026-05-14T12:02:56.645Z" }, + { url = "https://files.pythonhosted.org/packages/49/50/965308c703f085f225db2886813b27e015b8b3438c350b22dd65b52c2a2c/fonttools-4.63.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79cdc9f567aec74a72918fd060283911406750cbc9fd28c1316023deb6ce31a9", size = 2428130, upload-time = "2026-05-14T12:02:58.891Z" }, + { url = "https://files.pythonhosted.org/packages/d8/38/6937fbd7f2dc3a6b48725851bc2c15ec949b9af14d9bbcb5fe83cdf9bdf9/fonttools-4.63.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c14b4fd138c4bafcca294765c547914e1aa431ae1ca94ab99d8db08c958bd3b", size = 5111952, upload-time = "2026-05-14T12:03:01.263Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/a81f20050a3115b57d62c8e781446949512eac36690dc384ccea65ff4cc1/fonttools-4.63.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76ac49f929aecaf82d83250b8347e099d7aecba0f4726c1d9b6df3b8bb5fe18", size = 5082308, upload-time = "2026-05-14T12:03:03.211Z" }, + { url = "https://files.pythonhosted.org/packages/67/00/cdd9d4944ca6ae280d01e69cc37bde3bf663630b837a6fc6d2cd65d80e0e/fonttools-4.63.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dcf076a4474fe0d7367e5bbf5b052c7284fa1feca729c04176ce513521afd8a0", size = 5087932, upload-time = "2026-05-14T12:03:05.147Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f1/0aa0dbea778c75adbef223c42019fd47d22262b905974d62d829545d485f/fonttools-4.63.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7dd683fef0663e9f0f45cf541d788d24caa3ec9db50796b588e1757d8b3bc007", size = 5213271, upload-time = "2026-05-14T12:03:07.238Z" }, + { url = "https://files.pythonhosted.org/packages/a8/99/253e4056e1f0e67b9390125a154b73b5eb73ad521bece95c004858fdeec2/fonttools-4.63.0-cp311-cp311-win32.whl", hash = "sha256:afefc1ed0a59785a7fb06ea7e1678e849c193e1e387db783579bc7b3056fcfcb", size = 2304473, upload-time = "2026-05-14T12:03:09.271Z" }, + { url = "https://files.pythonhosted.org/packages/08/60/defa5e69641db890a63be281f41345f4c33b157824eaf0b9fad3e08b0dcb/fonttools-4.63.0-cp311-cp311-win_amd64.whl", hash = "sha256:063e08bd17bd5a90127a14123de0d6a952dbc847695fd98b63c043d58057f90c", size = 2356389, upload-time = "2026-05-14T12:03:11.53Z" }, + { url = "https://files.pythonhosted.org/packages/08/ef/b3c6b9b5be2f82416d73fe2ed2e96e2793cd80e7510bd6a17ca79cdd88ec/fonttools-4.63.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02", size = 2881131, upload-time = "2026-05-14T12:03:13.386Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/c815bea63117fa63e4e1c01f8a1110d2112fa003f838e6467094ec2432ce/fonttools-4.63.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a9faff9e0c1f76f9fd55899d2ce785832efebab37eb8ae13995853aef178bef0", size = 2426704, upload-time = "2026-05-14T12:03:15.801Z" }, + { url = "https://files.pythonhosted.org/packages/44/04/0b91d8e916e92ad1fac9e4624760baf0fd5ff2ead614c2f68fb21373f03f/fonttools-4.63.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef3048ef05dbb552b89817713d9cac912e00d0fde4a3105c00d29e52e10c89af", size = 5044298, upload-time = "2026-05-14T12:03:18.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/c7/2342da9830e3e9d4870305ca5d2091d2a83284f2953079b7bdd3b5e029d8/fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58dc6bb86a78d782f00f9190ca02c119cf5bbe2807536e361e18d42019f877d8", size = 4999800, upload-time = "2026-05-14T12:03:20.161Z" }, + { url = "https://files.pythonhosted.org/packages/e6/6d/67fe16c48d7ce050979b33f47e0d28a318f02da030602e944c34f7a16ef3/fonttools-4.63.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee08ebfa58f6e1aeff5697ab9582105bb620008c1caafb681e4c557e7483027b", size = 4982666, upload-time = "2026-05-14T12:03:22.87Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/3bbab338c07c71fa56269953845e92c951a61457bbbb0f1022551ea266d9/fonttools-4.63.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:27fdc65af8da6f88b9c6121c47a464cbe359fcfff7ff6fc2d37a1f395d755b78", size = 5133598, upload-time = "2026-05-14T12:03:25.168Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/aa27c7f98db5b064883dadcc5283947e81e034de42e22a33675878d98b54/fonttools-4.63.0-cp312-cp312-win32.whl", hash = "sha256:af2fd1664d00a397d75f806985ddb36282091c2131a73a6485c23b4a34722263", size = 2292575, upload-time = "2026-05-14T12:03:27.496Z" }, + { url = "https://files.pythonhosted.org/packages/87/36/cccb9bc2a6ab63d1b2980374f0dca72ce95ae267c9b4cfe77455bb70d0d4/fonttools-4.63.0-cp312-cp312-win_amd64.whl", hash = "sha256:59ac449f8cca9b4ffa08d2e7bbadad87ce710d69d1eda5c3c1ce579baa987272", size = 2343211, upload-time = "2026-05-14T12:03:30.057Z" }, + { url = "https://files.pythonhosted.org/packages/0f/8d/d8fec3dcde2963f8c908fb315e5ff2cd0ac34f82394bbbf73a2aa5145ce3/fonttools-4.63.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd7e9857e5e63738b9d9fd707bc1f59c8b09e5177726d23664db393c59bb08bd", size = 2876062, upload-time = "2026-05-14T12:03:32.554Z" }, + { url = "https://files.pythonhosted.org/packages/ef/71/d935dc54e4ff121bfdd11e08702db63a7e6f25af21d8a3d7b7212df53641/fonttools-4.63.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c2a2a42198b696a6f48fad91709afb55176e66a5e566131219dba372fb7f8c59", size = 2424594, upload-time = "2026-05-14T12:03:34.86Z" }, + { url = "https://files.pythonhosted.org/packages/8e/40/e76320afa1df918e146155ef239b1719ee266092e96f5423bfd075affba1/fonttools-4.63.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e874792a8212b44583ea02189d9e693906b2f78b261f372f95d6c563210ac1d", size = 5024840, upload-time = "2026-05-14T12:03:36.745Z" }, + { url = "https://files.pythonhosted.org/packages/ce/36/0b805d8c485f872f65a509cbe3b58a5d0d17bee855333b54a150c79d3061/fonttools-4.63.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22135da48a348785c5e2d5d2d9d6bec5ed44adacbaeb9db12d9493bf6c6bfa68", size = 4975801, upload-time = "2026-05-14T12:03:38.833Z" }, + { url = "https://files.pythonhosted.org/packages/c8/26/2cee03d0aa083ab022da5c07aff9ed3f689da1defb81ad6917c9627896da/fonttools-4.63.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ccf41f2efdf56994d22d73bef4ced1052161958169428d06ba9724ea9e9a64be", size = 4965009, upload-time = "2026-05-14T12:03:41.494Z" }, + { url = "https://files.pythonhosted.org/packages/7e/48/cc4b66d9058c0d0982c833fad10127c4b0e9324606aafa41382295ca4102/fonttools-4.63.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9ced0bd02ac751dd6319b0da88aaef24414e3b0dbc32bb4f24944821a3741a27", size = 5105892, upload-time = "2026-05-14T12:03:43.525Z" }, + { url = "https://files.pythonhosted.org/packages/d8/1f/a98a30a814b9ddef3a2e706025f90b9e0bc94890e6cb15254bc86547d11a/fonttools-4.63.0-cp313-cp313-win32.whl", hash = "sha256:85be818f5506e8a7753153def2c9550178f0ecae6a47b5e0e8dbb23f7cc90380", size = 2291313, upload-time = "2026-05-14T12:03:45.594Z" }, + { url = "https://files.pythonhosted.org/packages/92/46/5177b01f3b4abfdd4409f31cca4ab279c9343a26efbe9ec78c97fc612e02/fonttools-4.63.0-cp313-cp313-win_amd64.whl", hash = "sha256:ba04cb5891d4c0c21b6da95eda8d7b090021508a294fff33464fc7d241e0856b", size = 2342299, upload-time = "2026-05-14T12:03:47.414Z" }, + { url = "https://files.pythonhosted.org/packages/27/d2/23d25e3f247b328be58d04a4c9f894178a0d1eda7d42867cfb388adaf416/fonttools-4.63.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fd1e3094f42d806d3d7c79162fc59e5910fcbe3a7360c385b8da969bc4493745", size = 2875338, upload-time = "2026-05-14T12:03:50.052Z" }, + { url = "https://files.pythonhosted.org/packages/cd/58/7dfa0c761cb3b2964e2a84c4dc986c926a87de0cb9fb60d5b28ded3f2914/fonttools-4.63.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6e528da43bc3791085f8cb6141b1d13e459226790240340fcbb4625649238b03", size = 2422661, upload-time = "2026-05-14T12:03:52.154Z" }, + { url = "https://files.pythonhosted.org/packages/dd/87/64cfa18a7a1621d17b7f4502b2b0ed8a135a90c3db51ea590ee99043e76b/fonttools-4.63.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b2248c5decb223562f7902ff6325077a073f608ee8e33e88ad88db734eb9f49", size = 5010526, upload-time = "2026-05-14T12:03:54.647Z" }, + { url = "https://files.pythonhosted.org/packages/36/e1/a8933a72c45a87177fbde2696e0d0755c8c9062f8c077a961c6215fa27b1/fonttools-4.63.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:308f957cdeaf8abe4e5f2f124902ef405448af92c90f80e302a3b771c2e6116b", size = 4923946, upload-time = "2026-05-14T12:03:56.984Z" }, + { url = "https://files.pythonhosted.org/packages/27/60/872e6e233b8c5e8b41413796ff18b7fe479661bd40147e071b450dfad7a1/fonttools-4.63.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bf00f21eb5fb721dbaf73d1e9da6d02a1af7768f2ebcf9798be98beab8ba90f6", size = 4962489, upload-time = "2026-05-14T12:03:59.443Z" }, + { url = "https://files.pythonhosted.org/packages/30/c4/83c24f2ec38b90cfda84bf4b1a1f49df80e84a1db4e7ac6e0d41bf23bc39/fonttools-4.63.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c1aaa4b9c75798400ac043ce04d74e7830376c85095a5a6ed7cba2f17a266bf4", size = 5071870, upload-time = "2026-05-14T12:04:02.122Z" }, + { url = "https://files.pythonhosted.org/packages/de/40/3ae22b60ff1d41ce0bd044b31238cdc72cef99f28b976f1e128ebd618c9b/fonttools-4.63.0-cp314-cp314-win32.whl", hash = "sha256:22693918177bd9ceabec4736d338045f357769416fc6b0b2508eefef75b08616", size = 2295026, upload-time = "2026-05-14T12:04:04.47Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d4/98078064ccc76b45cb0f6c002452011e93c4bd26f6850344f0951cc1fe89/fonttools-4.63.0-cp314-cp314-win_amd64.whl", hash = "sha256:7d782fac32985914c351556f68ac0855391572bcd87de50e05970d3cd4c96fc5", size = 2347454, upload-time = "2026-05-14T12:04:06.752Z" }, + { url = "https://files.pythonhosted.org/packages/49/4e/652d1580c5f4e39f7d103b0c793e4773129ad633dce4addd0cf4dfebde02/fonttools-4.63.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6db5140a60a5d731d21ec076745b40a310607731b0a565b50776393188649001", size = 2958152, upload-time = "2026-05-14T12:04:08.706Z" }, + { url = "https://files.pythonhosted.org/packages/0e/55/ad864c9a9b219f552eb46b32cd7906c466e5a578ba0c3abfcc0fe7413eb6/fonttools-4.63.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d76edbff9014094dbf03bd2d074709dfa6ec7aba13d838c937a2b33d2d6a86e", size = 2460809, upload-time = "2026-05-14T12:04:10.783Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/0aa8db70f18cf52e49b4ed5ecec68547f981160bf5ded3b5aed6faa0a6f9/fonttools-4.63.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0eac00b9118c3c2f87d272e45341871c5b3066baa3c86897fa634a7c3fb59096", size = 5148649, upload-time = "2026-05-14T12:04:12.747Z" }, + { url = "https://files.pythonhosted.org/packages/7f/63/18e4369c25043096f1048e0c9915951adc4f842bd81c6b18155824d6fa99/fonttools-4.63.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51394295f1a51de8b5f30bdb1e1b9a4231536c7064ef5c6e211eec19fa36036f", size = 4932147, upload-time = "2026-05-14T12:04:14.806Z" }, + { url = "https://files.pythonhosted.org/packages/a1/3f/67f3eac2ffd8a98446c5022f8ed3864eac878a5ff7af8df4c8286dba16cc/fonttools-4.63.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9e12f105d2b6342c559c298afb674006bb2893afc7102dcf8a1b55b0486b4e40", size = 5027237, upload-time = "2026-05-14T12:04:17.675Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ba/4e6214cb38a7b04779e97bb7636de9a5c7f20af7018d03dee0b64c08510a/fonttools-4.63.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:796f27556dbe094c4824f75ca85267e4df776c79036c8441469a4df37038c196", size = 5053933, upload-time = "2026-05-14T12:04:20.818Z" }, + { url = "https://files.pythonhosted.org/packages/34/3b/214dcc19ee31d3d38fb5ad2755c11ef0514e5dc300bbaf41c0b69f393799/fonttools-4.63.0-cp314-cp314t-win32.whl", hash = "sha256:948428a275741f0b64b113c955425a953314f4b9ab9997f73a72c83e68e569c8", size = 2359326, upload-time = "2026-05-14T12:04:24.22Z" }, + { url = "https://files.pythonhosted.org/packages/dd/1e/3ff1a9b523058c2eeb6a9d50f5574e2a738200d0d94107d5bc4105e8da3f/fonttools-4.63.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6d4741eb179121cab9eea4cb2393d24492373a260d7945006358c08cfbf45419", size = 2425829, upload-time = "2026-05-14T12:04:26.829Z" }, + { url = "https://files.pythonhosted.org/packages/2c/47/c99d5268f354002ce80f8d029cd9d7d872969da1de8b93d32de4dc56d6f4/fonttools-4.63.0-py3-none-any.whl", hash = "sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d", size = 1164562, upload-time = "2026-05-14T12:04:29.092Z" }, +] + [[package]] name = "frozenlist" version = "1.8.0" @@ -1381,6 +1522,112 @@ 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 = "kiwisolver" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/dd/a495a9c104be1c476f0386e714252caf2b7eca883915422a64c50b88c6f5/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9eed0f7edbb274413b6ee781cca50541c8c0facd3d6fd289779e494340a2b85c", size = 122798, upload-time = "2026-03-09T13:12:58.963Z" }, + { url = "https://files.pythonhosted.org/packages/11/60/37b4047a2af0cf5ef6d8b4b26e91829ae6fc6a2d1f74524bcb0e7cd28a32/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb", size = 66216, upload-time = "2026-03-09T13:13:00.155Z" }, + { url = "https://files.pythonhosted.org/packages/0a/aa/510dc933d87767584abfe03efa445889996c70c2990f6f87c3ebaa0a18c5/kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac", size = 63911, upload-time = "2026-03-09T13:13:01.671Z" }, + { url = "https://files.pythonhosted.org/packages/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27", size = 1438209, upload-time = "2026-03-09T13:13:03.385Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d6/76621246f5165e5372f02f5e6f3f48ea336a8f9e96e43997d45b240ed8cd/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff710414307fefa903e0d9bdf300972f892c23477829f49504e59834f4195398", size = 1248888, upload-time = "2026-03-09T13:13:05.231Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c1/31559ec6fb39a5b48035ce29bb63ade628f321785f38c384dee3e2c08bc1/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6176c1811d9d5a04fa391c490cc44f451e240697a16977f11c6f722efb9041db", size = 1266304, upload-time = "2026-03-09T13:13:06.743Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ef/1cb8276f2d29cc6a41e0a042f27946ca347d3a4a75acf85d0a16aa6dcc82/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50847dca5d197fcbd389c805aa1a1cf32f25d2e7273dc47ab181a517666b68cc", size = 1319650, upload-time = "2026-03-09T13:13:08.607Z" }, + { url = "https://files.pythonhosted.org/packages/4c/e4/5ba3cecd7ce6236ae4a80f67e5d5531287337d0e1f076ca87a5abe4cd5d0/kiwisolver-1.5.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:01808c6d15f4c3e8559595d6d1fe6411c68e4a3822b4b9972b44473b24f4e679", size = 970949, upload-time = "2026-03-09T13:13:10.299Z" }, + { url = "https://files.pythonhosted.org/packages/5a/69/dc61f7ae9a2f071f26004ced87f078235b5507ab6e5acd78f40365655034/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f9f4121ec58628c96baa3de1a55a4e3a333c5102c8e94b64e23bf7b2083309", size = 2199125, upload-time = "2026-03-09T13:13:11.841Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7b/abbe0f1b5afa85f8d084b73e90e5f801c0939eba16ac2e49af7c61a6c28d/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b7d335370ae48a780c6e6a6bbfa97342f563744c39c35562f3f367665f5c1de2", size = 2293783, upload-time = "2026-03-09T13:13:14.399Z" }, + { url = "https://files.pythonhosted.org/packages/8a/80/5908ae149d96d81580d604c7f8aefd0e98f4fd728cf172f477e9f2a81744/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:800ee55980c18545af444d93fdd60c56b580db5cc54867d8cbf8a1dc0829938c", size = 1960726, upload-time = "2026-03-09T13:13:16.047Z" }, + { url = "https://files.pythonhosted.org/packages/84/08/a78cb776f8c085b7143142ce479859cfec086bd09ee638a317040b6ef420/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c438f6ca858697c9ab67eb28246c92508af972e114cac34e57a6d4ba17a3ac08", size = 2464738, upload-time = "2026-03-09T13:13:17.897Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e1/65584da5356ed6cb12c63791a10b208860ac40a83de165cb6a6751a686e3/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c63c91f95173f9c2a67c7c526b2cea976828a0e7fced9cdcead2802dc10f8a4", size = 2270718, upload-time = "2026-03-09T13:13:19.421Z" }, + { url = "https://files.pythonhosted.org/packages/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b", size = 73480, upload-time = "2026-03-09T13:13:20.818Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0e/2ee5debc4f77a625778fec5501ff3e8036fe361b7ee28ae402a485bb9694/kiwisolver-1.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad4ae4ffd1ee9cd11357b4c66b612da9888f4f4daf2f36995eda64bd45370cac", size = 64930, upload-time = "2026-03-09T13:13:21.997Z" }, + { url = "https://files.pythonhosted.org/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158, upload-time = "2026-03-09T13:13:23.127Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588", size = 66388, upload-time = "2026-03-09T13:13:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819", size = 64068, upload-time = "2026-03-09T13:13:25.878Z" }, + { url = "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f", size = 1477934, upload-time = "2026-03-09T13:13:27.166Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2f/cebfcdb60fd6a9b0f6b47a9337198bcbad6fbe15e68189b7011fd914911f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf", size = 1278537, upload-time = "2026-03-09T13:13:28.707Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0d/9b782923aada3fafb1d6b84e13121954515c669b18af0c26e7d21f579855/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d", size = 1296685, upload-time = "2026-03-09T13:13:30.528Z" }, + { url = "https://files.pythonhosted.org/packages/27/70/83241b6634b04fe44e892688d5208332bde130f38e610c0418f9ede47ded/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083", size = 1346024, upload-time = "2026-03-09T13:13:32.818Z" }, + { url = "https://files.pythonhosted.org/packages/e4/db/30ed226fb271ae1a6431fc0fe0edffb2efe23cadb01e798caeb9f2ceae8f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6", size = 987241, upload-time = "2026-03-09T13:13:34.435Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bd/c314595208e4c9587652d50959ead9e461995389664e490f4dce7ff0f782/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1", size = 2227742, upload-time = "2026-03-09T13:13:36.4Z" }, + { url = "https://files.pythonhosted.org/packages/c1/43/0499cec932d935229b5543d073c2b87c9c22846aab48881e9d8d6e742a2d/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0", size = 2323966, upload-time = "2026-03-09T13:13:38.204Z" }, + { url = "https://files.pythonhosted.org/packages/3d/6f/79b0d760907965acfd9d61826a3d41f8f093c538f55cd2633d3f0db269f6/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15", size = 1977417, upload-time = "2026-03-09T13:13:39.966Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/01d0537c41cb75a551a438c3c7a80d0c60d60b81f694dac83dd436aec0d0/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314", size = 2491238, upload-time = "2026-03-09T13:13:41.698Z" }, + { url = "https://files.pythonhosted.org/packages/e4/34/8aefdd0be9cfd00a44509251ba864f5caf2991e36772e61c408007e7f417/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9", size = 2294947, upload-time = "2026-03-09T13:13:43.343Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384", size = 73569, upload-time = "2026-03-09T13:13:45.792Z" }, + { url = "https://files.pythonhosted.org/packages/28/26/192b26196e2316e2bd29deef67e37cdf9870d9af8e085e521afff0fed526/kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7", size = 64997, upload-time = "2026-03-09T13:13:46.878Z" }, + { url = "https://files.pythonhosted.org/packages/9d/69/024d6711d5ba575aa65d5538042e99964104e97fa153a9f10bc369182bc2/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09", size = 123166, upload-time = "2026-03-09T13:13:48.032Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/adbb40df306f587054a348831220812b9b1d787aff714cfbc8556e38fccd/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3", size = 66395, upload-time = "2026-03-09T13:13:49.365Z" }, + { url = "https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd", size = 64065, upload-time = "2026-03-09T13:13:50.562Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3", size = 1477903, upload-time = "2026-03-09T13:13:52.084Z" }, + { url = "https://files.pythonhosted.org/packages/18/d8/55638d89ffd27799d5cc3d8aa28e12f4ce7a64d67b285114dbedc8ea4136/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96", size = 1278751, upload-time = "2026-03-09T13:13:54.673Z" }, + { url = "https://files.pythonhosted.org/packages/b8/97/b4c8d0d18421ecceba20ad8701358453b88e32414e6f6950b5a4bad54e65/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099", size = 1296793, upload-time = "2026-03-09T13:13:56.287Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/f862f94b6389d8957448ec9df59450b81bec4abb318805375c401a1e6892/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8", size = 1346041, upload-time = "2026-03-09T13:13:58.269Z" }, + { url = "https://files.pythonhosted.org/packages/a3/6a/f1650af35821eaf09de398ec0bc2aefc8f211f0cda50204c9f1673741ba9/kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87", size = 987292, upload-time = "2026-03-09T13:13:59.871Z" }, + { url = "https://files.pythonhosted.org/packages/de/19/d7fb82984b9238115fe629c915007be608ebd23dc8629703d917dbfaffd4/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23", size = 2227865, upload-time = "2026-03-09T13:14:01.401Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b9/46b7f386589fd222dac9e9de9c956ce5bcefe2ee73b4e79891381dda8654/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859", size = 2324369, upload-time = "2026-03-09T13:14:02.972Z" }, + { url = "https://files.pythonhosted.org/packages/92/8b/95e237cf3d9c642960153c769ddcbe278f182c8affb20cecc1cc983e7cc5/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902", size = 1977989, upload-time = "2026-03-09T13:14:04.503Z" }, + { url = "https://files.pythonhosted.org/packages/1b/95/980c9df53501892784997820136c01f62bc1865e31b82b9560f980c0e649/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167", size = 2491645, upload-time = "2026-03-09T13:14:06.106Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/900647fd0840abebe1561792c6b31e6a7c0e278fc3973d30572a965ca14c/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0", size = 2295237, upload-time = "2026-03-09T13:14:08.891Z" }, + { url = "https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276", size = 73573, upload-time = "2026-03-09T13:14:12.327Z" }, + { url = "https://files.pythonhosted.org/packages/4d/d2/64be2e429eb4fca7f7e1c52a91b12663aeaf25de3895e5cca0f47ef2a8d0/kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c", size = 64998, upload-time = "2026-03-09T13:14:13.469Z" }, + { url = "https://files.pythonhosted.org/packages/b0/69/ce68dd0c85755ae2de490bf015b62f2cea5f6b14ff00a463f9d0774449ff/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1", size = 125700, upload-time = "2026-03-09T13:14:14.636Z" }, + { url = "https://files.pythonhosted.org/packages/74/aa/937aac021cf9d4349990d47eb319309a51355ed1dbdc9c077cdc9224cb11/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e", size = 67537, upload-time = "2026-03-09T13:14:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/ee/20/3a87fbece2c40ad0f6f0aefa93542559159c5f99831d596050e8afae7a9f/kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7", size = 65514, upload-time = "2026-03-09T13:14:18.035Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7f/f943879cda9007c45e1f7dba216d705c3a18d6b35830e488b6c6a4e7cdf0/kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c", size = 1584848, upload-time = "2026-03-09T13:14:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/37/f8/4d4f85cc1870c127c88d950913370dd76138482161cd07eabbc450deff01/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368", size = 1391542, upload-time = "2026-03-09T13:14:21.54Z" }, + { url = "https://files.pythonhosted.org/packages/04/0b/65dd2916c84d252b244bd405303220f729e7c17c9d7d33dca6feeff9ffc4/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489", size = 1404447, upload-time = "2026-03-09T13:14:23.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/2606a373247babce9b1d056c03a04b65f3cf5290a8eac5d7bdead0a17e21/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1", size = 1455918, upload-time = "2026-03-09T13:14:24.74Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d1/c6078b5756670658e9192a2ef11e939c92918833d2745f85cd14a6004bdf/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3", size = 1072856, upload-time = "2026-03-09T13:14:26.597Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c8/7def6ddf16eb2b3741d8b172bdaa9af882b03c78e9b0772975408801fa63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18", size = 2333580, upload-time = "2026-03-09T13:14:28.237Z" }, + { url = "https://files.pythonhosted.org/packages/9e/87/2ac1fce0eb1e616fcd3c35caa23e665e9b1948bb984f4764790924594128/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021", size = 2423018, upload-time = "2026-03-09T13:14:30.018Z" }, + { url = "https://files.pythonhosted.org/packages/67/13/c6700ccc6cc218716bfcda4935e4b2997039869b4ad8a94f364c5a3b8e63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310", size = 2062804, upload-time = "2026-03-09T13:14:32.888Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bd/877056304626943ff0f1f44c08f584300c199b887cb3176cd7e34f1515f1/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3", size = 2597482, upload-time = "2026-03-09T13:14:34.971Z" }, + { url = "https://files.pythonhosted.org/packages/75/19/c60626c47bf0f8ac5dcf72c6c98e266d714f2fbbfd50cf6dab5ede3aaa50/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2", size = 2394328, upload-time = "2026-03-09T13:14:36.816Z" }, + { url = "https://files.pythonhosted.org/packages/47/84/6a6d5e5bb8273756c27b7d810d47f7ef2f1f9b9fd23c9ee9a3f8c75c9cef/kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53", size = 68410, upload-time = "2026-03-09T13:14:38.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/060f45052f2a01ad5762c8fdecd6d7a752b43400dc29ff75cd47225a40fd/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615", size = 123231, upload-time = "2026-03-09T13:14:41.323Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/78da680eadd06ff35edef6ef68a1ad273bad3e2a0936c9a885103230aece/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02", size = 66489, upload-time = "2026-03-09T13:14:42.534Z" }, + { url = "https://files.pythonhosted.org/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e", size = 64063, upload-time = "2026-03-09T13:14:44.759Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac", size = 1475913, upload-time = "2026-03-09T13:14:46.247Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05", size = 1282782, upload-time = "2026-03-09T13:14:48.458Z" }, + { url = "https://files.pythonhosted.org/packages/e2/9f/1de7aad00697325f05238a5f2eafbd487fb637cc27a558b5367a5f37fb7f/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd", size = 1300815, upload-time = "2026-03-09T13:14:50.721Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c2/297f25141d2e468e0ce7f7a7b92e0cf8918143a0cbd3422c1ad627e85a06/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a", size = 1347925, upload-time = "2026-03-09T13:14:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d3/f4c73a02eb41520c47610207b21afa8cdd18fdbf64ffd94674ae21c4812d/kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554", size = 991322, upload-time = "2026-03-09T13:14:54.637Z" }, + { url = "https://files.pythonhosted.org/packages/7b/46/d3f2efef7732fcda98d22bf4ad5d3d71d545167a852ca710a494f4c15343/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581", size = 2232857, upload-time = "2026-03-09T13:14:56.471Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ec/2d9756bf2b6d26ae4349b8d3662fb3993f16d80c1f971c179ce862b9dbae/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303", size = 2329376, upload-time = "2026-03-09T13:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/8f/9f/876a0a0f2260f1bde92e002b3019a5fabc35e0939c7d945e0fa66185eb20/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9", size = 1982549, upload-time = "2026-03-09T13:14:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/ba3624dfac23a64d54ac4179832860cb537c1b0af06024936e82ca4154a0/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79", size = 2494680, upload-time = "2026-03-09T13:15:01.364Z" }, + { url = "https://files.pythonhosted.org/packages/39/b7/97716b190ab98911b20d10bf92eca469121ec483b8ce0edd314f51bc85af/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796", size = 2297905, upload-time = "2026-03-09T13:15:03.925Z" }, + { url = "https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e", size = 75086, upload-time = "2026-03-09T13:15:07.775Z" }, + { url = "https://files.pythonhosted.org/packages/70/15/9b90f7df0e31a003c71649cf66ef61c3c1b862f48c81007fa2383c8bd8d7/kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df", size = 66577, upload-time = "2026-03-09T13:15:09.139Z" }, + { url = "https://files.pythonhosted.org/packages/17/01/7dc8c5443ff42b38e72731643ed7cf1ed9bf01691ae5cdca98501999ed83/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e", size = 125794, upload-time = "2026-03-09T13:15:10.525Z" }, + { url = "https://files.pythonhosted.org/packages/46/8a/b4ebe46ebaac6a303417fab10c2e165c557ddaff558f9699d302b256bc53/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4", size = 67646, upload-time = "2026-03-09T13:15:12.016Z" }, + { url = "https://files.pythonhosted.org/packages/60/35/10a844afc5f19d6f567359bf4789e26661755a2f36200d5d1ed8ad0126e5/kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028", size = 65511, upload-time = "2026-03-09T13:15:13.311Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8a/685b297052dd041dcebce8e8787b58923b6e78acc6115a0dc9189011c44b/kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657", size = 1584858, upload-time = "2026-03-09T13:15:15.103Z" }, + { url = "https://files.pythonhosted.org/packages/9e/80/04865e3d4638ac5bddec28908916df4a3075b8c6cc101786a96803188b96/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920", size = 1392539, upload-time = "2026-03-09T13:15:16.661Z" }, + { url = "https://files.pythonhosted.org/packages/ba/01/77a19cacc0893fa13fafa46d1bba06fb4dc2360b3292baf4b56d8e067b24/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9", size = 1405310, upload-time = "2026-03-09T13:15:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/53/39/bcaf5d0cca50e604cfa9b4e3ae1d64b50ca1ae5b754122396084599ef903/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d", size = 1456244, upload-time = "2026-03-09T13:15:20.444Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7a/72c187abc6975f6978c3e39b7cf67aeb8b3c0a8f9790aa7fd412855e9e1f/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65", size = 1073154, upload-time = "2026-03-09T13:15:22.039Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ca/cf5b25783ebbd59143b4371ed0c8428a278abe68d6d0104b01865b1bbd0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa", size = 2334377, upload-time = "2026-03-09T13:15:23.741Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e5/b1f492adc516796e88751282276745340e2a72dcd0d36cf7173e0daf3210/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0", size = 2425288, upload-time = "2026-03-09T13:15:25.789Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e5/9b21fbe91a61b8f409d74a26498706e97a48008bfcd1864373d32a6ba31c/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9", size = 2063158, upload-time = "2026-03-09T13:15:27.63Z" }, + { url = "https://files.pythonhosted.org/packages/b1/02/83f47986138310f95ea95531f851b2a62227c11cbc3e690ae1374fe49f0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f", size = 2597260, upload-time = "2026-03-09T13:15:29.421Z" }, + { url = "https://files.pythonhosted.org/packages/07/18/43a5f24608d8c313dd189cf838c8e68d75b115567c6279de7796197cfb6a/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646", size = 2394403, upload-time = "2026-03-09T13:15:31.517Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b5/98222136d839b8afabcaa943b09bd05888c2d36355b7e448550211d1fca4/kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681", size = 79687, upload-time = "2026-03-09T13:15:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032, upload-time = "2026-03-09T13:15:34.411Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fa/2910df836372d8761bb6eff7d8bdcb1613b5c2e03f260efe7abe34d388a7/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797", size = 130262, upload-time = "2026-03-09T13:15:35.629Z" }, + { url = "https://files.pythonhosted.org/packages/0f/41/c5f71f9f00aabcc71fee8b7475e3f64747282580c2fe748961ba29b18385/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203", size = 138036, upload-time = "2026-03-09T13:15:36.894Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/7399a607f434119c6e1fdc8ec89a8d51ccccadf3341dee4ead6bd14caaf5/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7", size = 194295, upload-time = "2026-03-09T13:15:38.22Z" }, + { url = "https://files.pythonhosted.org/packages/b5/91/53255615acd2a1eaca307ede3c90eb550bae9c94581f8c00081b6b1c8f44/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57", size = 75987, upload-time = "2026-03-09T13:15:39.65Z" }, + { url = "https://files.pythonhosted.org/packages/e9/eb/5fcbbbf9a0e2c3a35effb88831a483345326bbc3a030a3b5b69aee647f84/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ec4c85dc4b687c7f7f15f553ff26a98bfe8c58f5f7f0ac8905f0ba4c7be60232", size = 59532, upload-time = "2026-03-09T13:15:47.047Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9b/e17104555bb4db148fd52327feea1e96be4b88e8e008b029002c281a21ab/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:12e91c215a96e39f57989c8912ae761286ac5a9584d04030ceb3368a357f017a", size = 57420, upload-time = "2026-03-09T13:15:48.199Z" }, + { url = "https://files.pythonhosted.org/packages/48/44/2b5b95b7aa39fb2d8d9d956e0f3d5d45aef2ae1d942d4c3ffac2f9cfed1a/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be4a51a55833dc29ab5d7503e7bcb3b3af3402d266018137127450005cdfe737", size = 79892, upload-time = "2026-03-09T13:15:49.694Z" }, + { url = "https://files.pythonhosted.org/packages/52/7d/7157f9bba6b455cfb4632ed411e199fc8b8977642c2b12082e1bd9e6d173/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daae526907e262de627d8f70058a0f64acc9e2641c164c99c8f594b34a799a16", size = 77603, upload-time = "2026-03-09T13:15:50.945Z" }, + { url = "https://files.pythonhosted.org/packages/0a/dd/8050c947d435c8d4bc94e3252f4d8bb8a76cfb424f043a8680be637a57f1/kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1", size = 73558, upload-time = "2026-03-09T13:15:52.112Z" }, +] + [[package]] name = "kubernetes" version = "36.0.3" @@ -1509,6 +1756,71 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] +[[package]] +name = "matplotlib" +version = "3.11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/64/f9a391af28f518b11ad45a8a712353c94a0aefce09d3703200e5c54b610a/matplotlib-3.11.1.tar.gz", hash = "sha256:69647db5746941c793d6e445a4cd349323ffb87d9cc958c2ad84a659b4832d30", size = 32612045, upload-time = "2026-07-18T03:39:46.63Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/d0/791aa183dd88491555cf7d4be0b52b0bcf6c3c2a2c22c815a2e819bf53e2/matplotlib-3.11.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b7cf158e7add54a8d51ac9b5a84abd6d4e13ed4951b4f25f1c5139f41c2addb2", size = 9440302, upload-time = "2026-07-18T03:38:03.844Z" }, + { url = "https://files.pythonhosted.org/packages/35/74/82bbdf683a301f4478384c8aaba6903631a2ca18294b2d7655c9a542bffb/matplotlib-3.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d2ace7273b9a5061a3b420918a16fae1f2dc5dfee1abcc13aba71b5d94b1820c", size = 9268549, upload-time = "2026-07-18T03:38:06.144Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f0/9b4298911303f74e6d83e64a81d996c0616405ec95046fac7f17e4258b9e/matplotlib-3.11.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee55e9041211bf84302ab55ec3965df18dd90ae19f8b58332a7feaf208bfe83", size = 10024922, upload-time = "2026-07-18T03:38:08.236Z" }, + { url = "https://files.pythonhosted.org/packages/84/6f/0bc3c3d05b021db44c14bc379a7c0df7d57302aa15380c16fd4e63fd6a9b/matplotlib-3.11.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f4bdeea33a8d15a071dbfe6d119451b1d719c733ac666d65357082901a9099", size = 10832170, upload-time = "2026-07-18T03:38:10.276Z" }, + { url = "https://files.pythonhosted.org/packages/db/4d/e375f39acdb2af5a9342730618608e39790ec842e6f1b392863028781459/matplotlib-3.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b4c78ceb2f11bcac7389d305cda17aeb1f4586a857854ab5780bd3dd8dbfc407", size = 10916701, upload-time = "2026-07-18T03:38:12.512Z" }, + { url = "https://files.pythonhosted.org/packages/bc/be/fa26ed085b41298f64a8f9b7592c671bbf1acc8b0df124c1c5de96b859f8/matplotlib-3.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:7f33a781e12b1e53b278deb2f5373c2e55ec4f10727be3440c0cfb5cda9f944f", size = 9315331, upload-time = "2026-07-18T03:38:14.949Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f3/eb5bdf3b6e191b200db298b08bbc1638b7f3c82cdc8680f9d88bf72559ae/matplotlib-3.11.1-cp311-cp311-win_arm64.whl", hash = "sha256:67e4c3cd578c65ebd81bdc09a1b6592ceafee6dfafe116dc85dfcb647b5bbb18", size = 9003475, upload-time = "2026-07-18T03:38:17.205Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6c/7ef7ebcb2bd9739b2b66b18b076e077f44bb46fdbe28ca0506edb3c62c79/matplotlib-3.11.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e15ef41507f3d525f46154ac9e3ae785dacde9f20e593a25de8986267892ef74", size = 9453849, upload-time = "2026-07-18T03:38:19.593Z" }, + { url = "https://files.pythonhosted.org/packages/eb/f8/6d0c312c8d9738e7d9677f09fe5c986b3239e651a7b73a2deb38b65e4a71/matplotlib-3.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:21a67b961a6d597bca54fae826cd20695ba4a6e4d05424a08da6e13e3176fd6b", size = 9283113, upload-time = "2026-07-18T03:38:21.95Z" }, + { url = "https://files.pythonhosted.org/packages/c9/cf/b4ad2cc81b6672ea29ea04e64e350a9f9b493b0908ccd884c67eeff8f7b2/matplotlib-3.11.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ba8f811b8ddfac493734d6af0b2dff96919d0c28ca0d641858dab4262777c6ea", size = 10035615, upload-time = "2026-07-18T03:38:24.315Z" }, + { url = "https://files.pythonhosted.org/packages/88/90/4e10e033d9b66589d8ed98b84c95cdbb57033d57c1f41339d7393dbd2f2e/matplotlib-3.11.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c52f7ad20ef476806ed212380b1d54d20310c8b86bdc2c9a68b51f0024a44472", size = 10842559, upload-time = "2026-07-18T03:38:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/88/eb/799612d0f8cd3e816a10fec59329fca52cd2353264df80378dfc541ae855/matplotlib-3.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8b14eb22961fe865efb0e4ff167e333e428908b00115a8d800ccb65ee108e481", size = 10927532, upload-time = "2026-07-18T03:38:28.532Z" }, + { url = "https://files.pythonhosted.org/packages/88/89/56649bbaa2fd12e20f3be03dbcc135b0c8676d88bac17977599e3eb442a0/matplotlib-3.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:88a2a27dd9691ae448dfae4b26f59036be90c3c28757edd3553a29559d00859f", size = 9333886, upload-time = "2026-07-18T03:38:30.477Z" }, + { url = "https://files.pythonhosted.org/packages/c1/11/4d124efbbad677b7b7552f6f85a3bd432d4232f95400cea98fcd2ae36ef3/matplotlib-3.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:480194afceca4df2f137c2721227d3cba67121fbf4397b69cee7f83714b0a58a", size = 9007545, upload-time = "2026-07-18T03:38:32.833Z" }, + { url = "https://files.pythonhosted.org/packages/04/6c/4798363b7fb5644e309fe1fac30216e9146c9f70859d80d588c18caf5317/matplotlib-3.11.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6771b0cd7838c6a857a7209814158c0ad09bfef878db3033dd82d70ad101f191", size = 9454341, upload-time = "2026-07-18T03:38:35.001Z" }, + { url = "https://files.pythonhosted.org/packages/59/98/6acadbe7f98df19d274bc107ac58bb439fa75df82c33dc110d71a4a8501f/matplotlib-3.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2abdee5ffa2fe11b2d19f7a5c63b785fb7c28cc46c7bc1814156341d9d1a33e1", size = 9283627, upload-time = "2026-07-18T03:38:37.061Z" }, + { url = "https://files.pythonhosted.org/packages/24/ea/65cec46fe241390ccea1b1754207ee28eb71c5ab866bd5f22fe47e538fa4/matplotlib-3.11.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0a19dcf73406d3746d25a5ed42d713604c9a3e024d129b102852b0d941cb9f3", size = 10035860, upload-time = "2026-07-18T03:38:39.663Z" }, + { url = "https://files.pythonhosted.org/packages/c7/10/63fdccccbabe002fb0960876baabc5e3f24d9c1bb4cfb25651457f74b3a0/matplotlib-3.11.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7389b77ed2ab0552f46d9a90b81b7b8e6dfcdc42adc36c37a0865799843e0e3e", size = 10843594, upload-time = "2026-07-18T03:38:42.144Z" }, + { url = "https://files.pythonhosted.org/packages/98/51/a1155945bff7b91381875022ac1522c5dfdac0d006be8e7df389b3134eae/matplotlib-3.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c90be0b73568da4f662afac580956a76e308437e641b4a45aa08925eeb67d95f", size = 10927962, upload-time = "2026-07-18T03:38:44.302Z" }, + { url = "https://files.pythonhosted.org/packages/0d/3a/3d5e1f42dc761bf53401a62a83ff93389b37de9d2c093b2a3aa49ac34f1b/matplotlib-3.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:68408341f2312836fbbdf6b3c78047f65b2d8752f5fd221c3e72d348f5b34f8b", size = 9334074, upload-time = "2026-07-18T03:38:46.616Z" }, + { url = "https://files.pythonhosted.org/packages/e2/db/3f5ea5a5b64060ef5e1ff60a19170423e41ce21b8497a6fe15a36e0b43e3/matplotlib-3.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:0c1f44890d435c1b4ef52f701ad5828cb450ea97bcc83918fda6be74965d6cd2", size = 9007662, upload-time = "2026-07-18T03:38:49.112Z" }, + { url = "https://files.pythonhosted.org/packages/98/6e/c7ae5e0531425b69c0826b00ebbc264c85cab853f1cd6e096c9983c2cdc1/matplotlib-3.11.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:5e510088c27a89d53580a752f959146893563e63c330e161d159b0fee652af6f", size = 9503790, upload-time = "2026-07-18T03:38:51.527Z" }, + { url = "https://files.pythonhosted.org/packages/92/79/15be162e0a2ed546939674e2e97d0e33ec2447d86d4d4e611fa295bb178c/matplotlib-3.11.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:1524e2bdd48a93557aa47ddcfe9c225dfdd57d5a01a5c49128c20f0632980ee1", size = 9336148, upload-time = "2026-07-18T03:38:53.564Z" }, + { url = "https://files.pythonhosted.org/packages/6a/7f/36ffe144fc4aacfe0e3ed2318f72b6755d1e73b041d619b4d393e60f5a66/matplotlib-3.11.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:11664c551345553db92e61cae6cf1376f138f8c47cafdf13b64b18f3e3e9e464", size = 10049244, upload-time = "2026-07-18T03:38:55.911Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5f/55812d68c0a840d3a463638f48c00ab1fe338518ec49a640cb6473b444af/matplotlib-3.11.1-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e1f8922ba31959cf6a9dfb51be64b7f7bc582801a3957dc0c2f3afcd3537adf", size = 10860798, upload-time = "2026-07-18T03:38:58.282Z" }, + { url = "https://files.pythonhosted.org/packages/7a/64/cca444b4eb5e6c768c44fc5e1f0b5211f20ca2b282778051996e996a2bdf/matplotlib-3.11.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83235693abde86e5e0129998f80ee39fc7f58e6d56a88fafb28a9278833e9d5f", size = 10943282, upload-time = "2026-07-18T03:39:00.465Z" }, + { url = "https://files.pythonhosted.org/packages/e5/0f/a49c329d394f2e9ef38506982107e8b04ecf94dd41a9d8423ff82cc737c7/matplotlib-3.11.1-cp313-cp313t-win_amd64.whl", hash = "sha256:9a076f4fc5cdc43fdf510f5981418d25c2db4973418d9f22d8bb3dc8045ada78", size = 9383532, upload-time = "2026-07-18T03:39:02.468Z" }, + { url = "https://files.pythonhosted.org/packages/e4/50/103e86afb806d8f64d04ede14e4cfc09dbfc25f512421ff85fdd6ebd59cf/matplotlib-3.11.1-cp313-cp313t-win_arm64.whl", hash = "sha256:216fbb93a74add02ddb4cb38ef5348f59ac00b3e84567eaf16598772d40e150a", size = 9059665, upload-time = "2026-07-18T03:39:04.607Z" }, + { url = "https://files.pythonhosted.org/packages/35/04/3079499fa8cb661ea66d13d6439d5a3ae6710a7afd5c7f72e08914f275f8/matplotlib-3.11.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:30c492d4ba9448595b6fd8708c6725963f8148e25c0d8842948da5b05f0ee8d3", size = 9456022, upload-time = "2026-07-18T03:39:07.041Z" }, + { url = "https://files.pythonhosted.org/packages/53/a2/69acfe84ec1f32930e801a5782a07fc5c79c8c6599a507b806d859d5da8e/matplotlib-3.11.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ac104be2768ffdd8655db9e71b768cbb45f2b9aa7b450cf1595e8f65d3822319", size = 9285475, upload-time = "2026-07-18T03:39:09.562Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b3/31b15a2ca56d4ddd6aaa1c884c2f51cf9a61cfaf5ca6f6fbd6343d38e6df/matplotlib-3.11.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be943cb68bc6660ead58c55b3aa6366cba2ef7feb06460fbcce32360376f19f", size = 10847102, upload-time = "2026-07-18T03:39:11.532Z" }, + { url = "https://files.pythonhosted.org/packages/64/0d/a17e966e620545c1548125af0b29ac812dd17b197a18a7462ac12fa859ee/matplotlib-3.11.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5af0dcda57d471440a7b5b623e70e0a61003518443d9098f211a96ecfbbc25be", size = 11131087, upload-time = "2026-07-18T03:39:13.764Z" }, + { url = "https://files.pythonhosted.org/packages/97/c5/5e100efdd67abb7de20befaa333612ef9bfc63417fb71398f904f25d083c/matplotlib-3.11.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3d3fd84082b1afbd9398466c81309e20045be20d48fe0fb18c43504d164cbbb2", size = 10929036, upload-time = "2026-07-18T03:39:16.888Z" }, + { url = "https://files.pythonhosted.org/packages/ce/04/d719a0a36930ecc8dfc801ff340f9dcfc4223f8ca5d39d06b4020032fff8/matplotlib-3.11.1-cp314-cp314-win_amd64.whl", hash = "sha256:9601a1e90be21e4884c53b4f3dc3ee0544654946f9975258d691f1c2e2f119c6", size = 9489571, upload-time = "2026-07-18T03:39:19.449Z" }, + { url = "https://files.pythonhosted.org/packages/48/65/facabdc2f1f6caba7e856db64dfedddca25f7608df07d96a1c8fd114fd3b/matplotlib-3.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:ae30c6109848ac0f9fa36c5d6270938487614c47ba31860bd5361266dabc5685", size = 9164486, upload-time = "2026-07-18T03:39:21.424Z" }, + { url = "https://files.pythonhosted.org/packages/88/dd/18da6cd01cf96354534f98c468a25380c68ce582a2c9dd0cae12b04af4f2/matplotlib-3.11.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:dadfe80797174e2984aae3be0b77594a3c72d2c0a40fbd4a0de48d2728caf3ae", size = 9504876, upload-time = "2026-07-18T03:39:23.633Z" }, + { url = "https://files.pythonhosted.org/packages/79/b0/f0b63555a18b79d038c81fd6126f35fc4dfce0eaff48d96103348c7cf935/matplotlib-3.11.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:89b193b255f4f6f7948dbcee3691f4f341ab05d9a8874a67b45ddb4182922eda", size = 9336120, upload-time = "2026-07-18T03:39:25.797Z" }, + { url = "https://files.pythonhosted.org/packages/c6/dd/f210ec7c4a6f198d5567237048a93d0811fb5a1f1691f13320e592f95b41/matplotlib-3.11.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191163532cdefcb1571ca38a6d7e6474baccde64495783e6ba47aa07ec4b9bbb", size = 10858033, upload-time = "2026-07-18T03:39:27.999Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d2/d6d5324507c5fbb316db48e258c09c2807f3de03d9af47017e120070926f/matplotlib-3.11.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9fdf1c818ab05d0e74002091ddaf414478a3a449ec9d51c8976d45be7e3a01e2", size = 11141827, upload-time = "2026-07-18T03:39:30.092Z" }, + { url = "https://files.pythonhosted.org/packages/0f/68/3c22e9320bdce2c4d2f1320643ef706db7a24cb7420eea28b97a2d67f5a8/matplotlib-3.11.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b937b9dba5f5f6c1e31c47abe2186c865c0914fd18f2ce0dfc39c9adcef5951d", size = 10943061, upload-time = "2026-07-18T03:39:32.356Z" }, + { url = "https://files.pythonhosted.org/packages/f6/4a/907ed190ee81a9df581e0ed5456134fc0f7cb55ffcfda2f9e54ca900761c/matplotlib-3.11.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f2912f647f3fbe1ccf085f91e213936f9101bead81a5e670565b1f1b3712f4fb", size = 9540074, upload-time = "2026-07-18T03:39:34.789Z" }, + { url = "https://files.pythonhosted.org/packages/23/d4/97c19b77e0a6e3b48581185bb65088f431cd20186076cc0f650a1757ea46/matplotlib-3.11.1-cp314-cp314t-win_arm64.whl", hash = "sha256:54d47b8ae8b579633a3902ca5b4ad6c1e132a5626d64447b2e22a66394e79987", size = 9213472, upload-time = "2026-07-18T03:39:37.141Z" }, + { url = "https://files.pythonhosted.org/packages/ee/38/ceb1d637c4db6d06141f3739e93af3321e7caaabe69b57ae48ffe3ee95b1/matplotlib-3.11.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:427258425f9a3fc4ed79a91f9e9b9aaf5a82cb6571e85dc14063cc6fbb993741", size = 9438045, upload-time = "2026-07-18T03:39:39.491Z" }, + { url = "https://files.pythonhosted.org/packages/89/25/72ad8b58602d3a6ef1dfc4b65ecd01634ab65a2bdf494c9fe0e966dbf081/matplotlib-3.11.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:1ac697e591c11b6ad04679a73c2d2f9980fe9d9f0311fb414a2e329706343dfb", size = 9266127, upload-time = "2026-07-18T03:39:41.597Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6d/69552382fcc8e93d1f2763ef2665980a900a48b7f3a4c57ed290726d1cbc/matplotlib-3.11.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e4b9ac2f1f607ecda2af90a5232beee2af7582fce1cc30c4b6a1b012dc21ee99", size = 10019439, upload-time = "2026-07-18T03:39:43.78Z" }, +] + [[package]] name = "mcp" version = "2.0.0" @@ -2981,6 +3293,15 @@ crypto = [ { name = "cryptography" }, ] +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + [[package]] name = "pypika" version = "0.51.1" @@ -3848,6 +4169,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/16/5b/f2aa703a4fc5d2dff73460a7d46cc2f3f44aa0f3dd8eeb20d2a0ecf68862/tiktoken-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:85b78cc3a2c3d48723ca751fa981f1fedccd54194ca0471b957364353a898b07", size = 918110, upload-time = "2026-05-15T04:51:17.237Z" }, ] +[[package]] +name = "timm" +version = "1.0.28" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "pyyaml" }, + { name = "safetensors" }, + { name = "torch", version = "2.13.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, + { name = "torch", version = "2.13.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "torchvision", version = "0.28.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, + { name = "torchvision", version = "0.28.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/35/03/e41389ac641747bfec48d016fde8be1eade1901e6f2c1aedcb0c8cb4b5d9/timm-1.0.28.tar.gz", hash = "sha256:3789d313fdd5541a327b60180d70dbb4bdec73db8ff0655e413db3c3d134a9a4", size = 2451413, upload-time = "2026-07-11T17:24:32.615Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/76/de1bfac17d183c49c6d0887903d3064ced51cf1d9ba7a8d611c1a8808c4f/timm-1.0.28-py3-none-any.whl", hash = "sha256:e577b88da96b3a722ea5e2f042455ce6f715d398304d8e63b17d126ed7d89968", size = 2597944, upload-time = "2026-07-11T17:24:30.869Z" }, +] + [[package]] name = "tokenizers" version = "0.22.2" @@ -3955,6 +4294,56 @@ wheels = [ { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.13.0%2Bcpu-cp314-cp314t-win_amd64.whl", hash = "sha256:a43376bd094124ef626bfdd3d4c2c62eacb0b5ddc99776f4a32d4fd16f1f3420", upload-time = "2026-07-08T19:31:48Z" }, ] +[[package]] +name = "torchaudio" +version = "2.11.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform != 'linux' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform != 'linux' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform != 'linux' and sys_platform != 'win32'", + "python_full_version < '3.12' and sys_platform != 'linux' and sys_platform != 'win32'", +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/77/0eec7f175d88f312296bd5b11c23bd58da37c1021f53da3db4df449ce3ee/torchaudio-2.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:492dd64645e9d0bb843e94f1d9a4d1e31426262ffc594fafecc1697df9df5eb9", size = 684142, upload-time = "2026-03-23T18:13:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b1/77658817acacd01a72b714440c62f419efc4d90170e704e8e7a2c0918988/torchaudio-2.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a1cf1acc883bee9cb906a933572fed6a8a933f86ef34e9ea7d803f72317e8c1b", size = 684226, upload-time = "2026-03-23T18:13:40.023Z" }, + { url = "https://files.pythonhosted.org/packages/fb/9e/f76fcd9877c8c78f258ee34e0fb8291fdb91e6218d582d9ca66b1e4bd4ae/torchaudio-2.11.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:e3f9696a9ef1d49acc452159b052370c636406d072e9d8f10895fda87b591ea9", size = 679904, upload-time = "2026-03-23T18:13:28.329Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ce/52c652d30af7d6e96c8f1735d26131e94708e3f38d852b8fa97958804dd8/torchaudio-2.11.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:bda09ea630ae7207384fb0f28c35e4f8c0d82dd6eba020b6b335ad0caa9fed49", size = 680814, upload-time = "2026-03-23T18:13:17.08Z" }, + { url = "https://files.pythonhosted.org/packages/39/fe/ffa618b4f0d9732d7df7a2fa2bd48657d896599bc224e5af3c70d46c546b/torchaudio-2.11.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:cc09cd1f6015b8549e7fe255fb1be5346b57e7fee06541d3f3dbb012d8c4715f", size = 679901, upload-time = "2026-03-23T18:13:25.472Z" }, + { url = "https://files.pythonhosted.org/packages/60/84/1c792b0b700eac9a96772cfd9f96c097b17bca3234a2fde3c64b8063660d/torchaudio-2.11.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:da2725e250866da42a12934c9a6552f65a18b7187fd7a6221387f0e605fb3b96", size = 679926, upload-time = "2026-03-23T18:13:24.452Z" }, +] + +[[package]] +name = "torchaudio" +version = "2.11.0+cpu" +source = { registry = "https://download.pytorch.org/whl/cpu" } +resolution-markers = [ + "(python_full_version >= '3.14' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform == 'win32')", + "(python_full_version == '3.13.*' and sys_platform == 'linux') or (python_full_version == '3.13.*' and sys_platform == 'win32')", + "(python_full_version == '3.12.*' and sys_platform == 'linux') or (python_full_version == '3.12.*' and sys_platform == 'win32')", + "(python_full_version < '3.12' and sys_platform == 'linux') or (python_full_version < '3.12' and sys_platform == 'win32')", +] +wheels = [ + { url = "https://download-r2.pytorch.org/whl/cpu/torchaudio-2.11.0%2Bcpu-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:7a5ecdd20fb26d8eb1d62135ddc6db0f806b17667c10e82df913fd509d1f5cfe", upload-time = "2026-03-23T15:50:10Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchaudio-2.11.0%2Bcpu-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:2ddcb49458aa9783121222c535c0eb9cd54445ec44bb8dd22c9e52cdb730dbc2", upload-time = "2026-03-23T15:50:10Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchaudio-2.11.0%2Bcpu-cp311-cp311-win_amd64.whl", hash = "sha256:abbff04127caf6e16e3a7c3e1ca2739b68899e14926d7cbec4bd8b40f72375d5", upload-time = "2026-03-23T15:50:10Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchaudio-2.11.0%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:b9dd2c6ac144001dc6dac38b564c1de73ac26ef0c195d5037c4a94990b0e2b5a", upload-time = "2026-03-23T15:50:10Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchaudio-2.11.0%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:2354248848d06a9ae1e7a12165f800f0dda7df60ecac9fca892322b722b922c0", upload-time = "2026-03-23T15:50:10Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchaudio-2.11.0%2Bcpu-cp312-cp312-win_amd64.whl", hash = "sha256:95d517bd1a0a28dacd1c37550ced95cab64f3a7a4ef9b8219b41049388a71163", upload-time = "2026-03-23T15:50:10Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchaudio-2.11.0%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:2faa8d8f251d1fa44813765b00791048b617e9dc06e6cd9222aba81023929119", upload-time = "2026-03-23T15:50:10Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchaudio-2.11.0%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3c0175d0ed054bf0dc3b154a744b1a127c94291b3f3b7bdd0639b4b238c89445", upload-time = "2026-03-23T15:50:10Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchaudio-2.11.0%2Bcpu-cp313-cp313-win_amd64.whl", hash = "sha256:fa0c22f58f60c3537b7d28ed2501e0995acb2a65d9af2708f21edaad67186cd8", upload-time = "2026-03-23T15:50:10Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchaudio-2.11.0%2Bcpu-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:5d665234def325e22c15518c581b0107a651c9f843176e3192360b092ebcb656", upload-time = "2026-03-23T15:50:10Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchaudio-2.11.0%2Bcpu-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:a3df9c41706a22de0f43fe2734f54db31cb5a7314cc92ecc84657a8492b3ff8d", upload-time = "2026-03-23T15:50:10Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchaudio-2.11.0%2Bcpu-cp313-cp313t-win_amd64.whl", hash = "sha256:18818a326b779abc7bfd5cfb9fe88501916dde144cb1944fc9e7b4fb6208dfc7", upload-time = "2026-03-23T15:50:10Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchaudio-2.11.0%2Bcpu-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:591ed8279f0170ef28933873f65e9f5f8c439287088ef6285cda65988b0ba614", upload-time = "2026-03-23T15:50:10Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchaudio-2.11.0%2Bcpu-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:34c5dcd704e17a2b01c097b4fe3b5f83c5cdbc42b9f2abd095e026c588f873d4", upload-time = "2026-03-23T15:50:10Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchaudio-2.11.0%2Bcpu-cp314-cp314-win_amd64.whl", hash = "sha256:be19f467eb7a173264653369426e7ecc4745e28d15f9c52eea2ad5316ec685eb", upload-time = "2026-03-23T15:50:10Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchaudio-2.11.0%2Bcpu-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:ad3e6141c1078c5c9c8ae7c3cdf6c80eb35612c99c8cf3f76fe295845f45bb9d", upload-time = "2026-03-23T15:50:10Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchaudio-2.11.0%2Bcpu-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:69db7c3d86af1bc8224f7053395ace3e2bd8c56b0c3922bc7b798114440c88e5", upload-time = "2026-03-23T15:50:10Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchaudio-2.11.0%2Bcpu-cp314-cp314t-win_amd64.whl", hash = "sha256:b6bfca873bd67d02fbbaa254312283be48dc1ca5532013152527210db432ef8c", upload-time = "2026-03-23T15:50:10Z" }, +] + [[package]] name = "torchvision" version = "0.28.0" @@ -4197,9 +4586,11 @@ actor = [ { name = "psutil" }, ] all = [ + { name = "av" }, { name = "chromadb" }, { name = "faster-whisper" }, { name = "huggingface-hub" }, + { name = "matplotlib" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "opencv-python-headless" }, @@ -4207,8 +4598,11 @@ all = [ { name = "pooch" }, { name = "psutil" }, { name = "sentence-transformers" }, + { name = "timm" }, { name = "torch", version = "2.13.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, { name = "torch", version = "2.13.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "torchaudio", version = "2.11.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, + { name = "torchaudio", version = "2.11.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "torchvision", version = "0.28.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, { name = "torchvision", version = "0.28.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "transformers" }, @@ -4227,9 +4621,11 @@ frontend = [ { name = "streamlit" }, ] local-worker = [ + { name = "av" }, { name = "chromadb" }, { name = "faster-whisper" }, { name = "huggingface-hub" }, + { name = "matplotlib" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "opencv-python-headless" }, @@ -4238,8 +4634,11 @@ local-worker = [ { name = "psutil" }, { name = "pydantic-ai-slim", extra = ["openai"] }, { name = "sentence-transformers" }, + { name = "timm" }, { name = "torch", version = "2.13.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, { name = "torch", version = "2.13.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "torchaudio", version = "2.11.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, + { name = "torchaudio", version = "2.11.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "torchvision", version = "0.28.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, { name = "torchvision", version = "0.28.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "transformers" }, @@ -4273,10 +4672,12 @@ server = [ server-worker = [ { name = "alembic" }, { name = "asgi-correlation-id" }, + { name = "av" }, { name = "chromadb-client" }, { name = "fastapi" }, { name = "faster-whisper" }, { name = "huggingface-hub" }, + { name = "matplotlib" }, { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "opencv-python-headless" }, @@ -4288,8 +4689,11 @@ server-worker = [ { name = "pyjwt", extra = ["crypto"] }, { name = "python-multipart" }, { name = "sentence-transformers" }, + { name = "timm" }, { name = "torch", version = "2.13.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, { name = "torch", version = "2.13.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "torchaudio", version = "2.11.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, + { name = "torchaudio", version = "2.11.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "torchvision", version = "0.28.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, { name = "torchvision", version = "0.28.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "transformers" }, @@ -4298,6 +4702,21 @@ server-worker = [ slm = [ { name = "pydantic-ai-slim", extra = ["openai"] }, ] +sound = [ + { name = "av" }, + { name = "chromadb" }, + { name = "huggingface-hub" }, + { name = "matplotlib" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "psutil" }, + { name = "timm" }, + { name = "torch", version = "2.13.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, + { name = "torch", version = "2.13.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "torchaudio", version = "2.11.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'linux' and sys_platform != 'win32'" }, + { name = "torchaudio", version = "2.11.0+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, + { name = "transformers" }, +] storage = [ { name = "chromadb" }, { name = "psutil" }, @@ -4326,11 +4745,16 @@ requires-dist = [ { name = "alembic", marker = "extra == 'server-worker'", specifier = ">=1.18.5,<2" }, { name = "asgi-correlation-id", marker = "extra == 'server'", specifier = ">=5.0.1,<6" }, { name = "asgi-correlation-id", marker = "extra == 'server-worker'", specifier = ">=5.0.1,<6" }, + { name = "av", marker = "extra == 'all'", specifier = ">=18,<19" }, + { name = "av", marker = "extra == 'local-worker'", specifier = ">=18,<19" }, + { name = "av", marker = "extra == 'server-worker'", specifier = ">=18,<19" }, + { name = "av", marker = "extra == 'sound'", specifier = ">=18,<19" }, { name = "chromadb", marker = "extra == 'actor'", specifier = ">=1.5.9,<2" }, { name = "chromadb", marker = "extra == 'all'", specifier = ">=1.5.9,<2" }, { name = "chromadb", marker = "extra == 'dialogue'", specifier = ">=1.5.9,<2" }, { name = "chromadb", marker = "extra == 'local-worker'", specifier = ">=1.5.9,<2" }, { name = "chromadb", marker = "extra == 'scene'", specifier = ">=1.5.9,<2" }, + { name = "chromadb", marker = "extra == 'sound'", specifier = ">=1.5.9,<2" }, { name = "chromadb", marker = "extra == 'storage'", specifier = ">=1.5.9,<2" }, { name = "chromadb", marker = "extra == 'videoprism'", specifier = ">=1.5.9,<2" }, { name = "chromadb-client", marker = "extra == 'server-worker'", specifier = ">=1.5.9,<2" }, @@ -4348,7 +4772,12 @@ requires-dist = [ { name = "huggingface-hub", marker = "extra == 'local-worker'", specifier = ">=1.25.1,<2" }, { name = "huggingface-hub", marker = "extra == 'scene'", specifier = ">=1.25.1,<2" }, { name = "huggingface-hub", marker = "extra == 'server-worker'", specifier = ">=1.25.1,<2" }, + { name = "huggingface-hub", marker = "extra == 'sound'", specifier = ">=1.25.1,<2" }, { name = "huggingface-hub", marker = "extra == 'videoprism'", specifier = ">=1.25.1,<2" }, + { name = "matplotlib", marker = "extra == 'all'", specifier = ">=3.10,<4" }, + { name = "matplotlib", marker = "extra == 'local-worker'", specifier = ">=3.10,<4" }, + { name = "matplotlib", marker = "extra == 'server-worker'", specifier = ">=3.10,<4" }, + { name = "matplotlib", marker = "extra == 'sound'", specifier = ">=3.10,<4" }, { name = "mcp", marker = "extra == 'mcp'", specifier = ">=2.0,<3" }, { name = "mcp", marker = "extra == 'server'", specifier = ">=2.0,<3" }, { name = "numpy", marker = "extra == 'actor'", specifier = ">=2.3,<3" }, @@ -4356,6 +4785,7 @@ requires-dist = [ { name = "numpy", marker = "extra == 'local-worker'", specifier = ">=2.3,<3" }, { name = "numpy", marker = "extra == 'scene'", specifier = ">=2.3,<3" }, { name = "numpy", marker = "extra == 'server-worker'", specifier = ">=2.3,<3" }, + { name = "numpy", marker = "extra == 'sound'", specifier = ">=2.3,<3" }, { name = "numpy", marker = "extra == 'videoprism'", specifier = ">=2.3,<3" }, { name = "opencv-python-headless", marker = "extra == 'actor'", specifier = ">=5.0.0.93,<6" }, { name = "opencv-python-headless", marker = "extra == 'all'", specifier = ">=5.0.0.93,<6" }, @@ -4380,6 +4810,7 @@ requires-dist = [ { name = "psutil", marker = "extra == 'scene'", specifier = ">=7.2.2,<8" }, { name = "psutil", marker = "extra == 'server'", specifier = ">=7.2.2,<8" }, { name = "psutil", marker = "extra == 'server-worker'", specifier = ">=7.2.2,<8" }, + { name = "psutil", marker = "extra == 'sound'", specifier = ">=7.2.2,<8" }, { name = "psutil", marker = "extra == 'storage'", specifier = ">=7.2.2,<8" }, { name = "psutil", marker = "extra == 'videoprism'", specifier = ">=7.2.2,<8" }, { name = "psycopg", extras = ["binary"], marker = "extra == 'server'", specifier = ">=3.3.4,<4" }, @@ -4403,16 +4834,30 @@ requires-dist = [ { name = "sqlalchemy", specifier = ">=2.0.51,<2.1" }, { name = "srt", marker = "extra == 'benchmarks'", specifier = ">=3.5,<4" }, { name = "streamlit", marker = "extra == 'frontend'", specifier = ">=1.60,<2" }, + { name = "timm", marker = "extra == 'all'", specifier = ">=1.0.20,<2" }, + { name = "timm", marker = "extra == 'local-worker'", specifier = ">=1.0.20,<2" }, + { name = "timm", marker = "extra == 'server-worker'", specifier = ">=1.0.20,<2" }, + { name = "timm", marker = "extra == 'sound'", specifier = ">=1.0.20,<2" }, { name = "torch", marker = "(sys_platform == 'linux' and extra == 'all') or (sys_platform == 'win32' and extra == 'all')", specifier = ">=2.13,<3", index = "https://download.pytorch.org/whl/cpu" }, { name = "torch", marker = "(sys_platform == 'linux' and extra == 'local-worker') or (sys_platform == 'win32' and extra == 'local-worker')", specifier = ">=2.13,<3", index = "https://download.pytorch.org/whl/cpu" }, { name = "torch", marker = "(sys_platform == 'linux' and extra == 'scene') or (sys_platform == 'win32' and extra == 'scene')", specifier = ">=2.13,<3", index = "https://download.pytorch.org/whl/cpu" }, { name = "torch", marker = "(sys_platform == 'linux' and extra == 'server-worker') or (sys_platform == 'win32' and extra == 'server-worker')", specifier = ">=2.13,<3", index = "https://download.pytorch.org/whl/cpu" }, + { name = "torch", marker = "(sys_platform == 'linux' and extra == 'sound') or (sys_platform == 'win32' and extra == 'sound')", specifier = ">=2.13,<3", index = "https://download.pytorch.org/whl/cpu" }, { name = "torch", marker = "(sys_platform == 'linux' and extra == 'videoprism') or (sys_platform == 'win32' and extra == 'videoprism')", specifier = ">=2.13,<3", index = "https://download.pytorch.org/whl/cpu" }, { name = "torch", marker = "sys_platform != 'linux' and sys_platform != 'win32' and extra == 'all'", specifier = ">=2.13,<3" }, { name = "torch", marker = "sys_platform != 'linux' and sys_platform != 'win32' and extra == 'local-worker'", specifier = ">=2.13,<3" }, { name = "torch", marker = "sys_platform != 'linux' and sys_platform != 'win32' and extra == 'scene'", specifier = ">=2.13,<3" }, { name = "torch", marker = "sys_platform != 'linux' and sys_platform != 'win32' and extra == 'server-worker'", specifier = ">=2.13,<3" }, + { name = "torch", marker = "sys_platform != 'linux' and sys_platform != 'win32' and extra == 'sound'", specifier = ">=2.13,<3" }, { name = "torch", marker = "sys_platform != 'linux' and sys_platform != 'win32' and extra == 'videoprism'", specifier = ">=2.13,<3" }, + { name = "torchaudio", marker = "(sys_platform == 'linux' and extra == 'all') or (sys_platform == 'win32' and extra == 'all')", specifier = ">=2.11,<2.12", index = "https://download.pytorch.org/whl/cpu" }, + { name = "torchaudio", marker = "(sys_platform == 'linux' and extra == 'local-worker') or (sys_platform == 'win32' and extra == 'local-worker')", specifier = ">=2.11,<2.12", index = "https://download.pytorch.org/whl/cpu" }, + { name = "torchaudio", marker = "(sys_platform == 'linux' and extra == 'server-worker') or (sys_platform == 'win32' and extra == 'server-worker')", specifier = ">=2.11,<2.12", index = "https://download.pytorch.org/whl/cpu" }, + { name = "torchaudio", marker = "(sys_platform == 'linux' and extra == 'sound') or (sys_platform == 'win32' and extra == 'sound')", specifier = ">=2.11,<2.12", index = "https://download.pytorch.org/whl/cpu" }, + { name = "torchaudio", marker = "sys_platform != 'linux' and sys_platform != 'win32' and extra == 'all'", specifier = ">=2.11,<2.12" }, + { name = "torchaudio", marker = "sys_platform != 'linux' and sys_platform != 'win32' and extra == 'local-worker'", specifier = ">=2.11,<2.12" }, + { name = "torchaudio", marker = "sys_platform != 'linux' and sys_platform != 'win32' and extra == 'server-worker'", specifier = ">=2.11,<2.12" }, + { name = "torchaudio", marker = "sys_platform != 'linux' and sys_platform != 'win32' and extra == 'sound'", specifier = ">=2.11,<2.12" }, { name = "torchvision", marker = "(sys_platform == 'linux' and extra == 'all') or (sys_platform == 'win32' and extra == 'all')", specifier = ">=0.28,<1", index = "https://download.pytorch.org/whl/cpu" }, { name = "torchvision", marker = "(sys_platform == 'linux' and extra == 'local-worker') or (sys_platform == 'win32' and extra == 'local-worker')", specifier = ">=0.28,<1", index = "https://download.pytorch.org/whl/cpu" }, { name = "torchvision", marker = "(sys_platform == 'linux' and extra == 'server-worker') or (sys_platform == 'win32' and extra == 'server-worker')", specifier = ">=0.28,<1", index = "https://download.pytorch.org/whl/cpu" }, @@ -4425,12 +4870,13 @@ requires-dist = [ { name = "transformers", marker = "extra == 'local-worker'", specifier = ">=5.14.1,<6" }, { name = "transformers", marker = "extra == 'scene'", specifier = ">=5.14.1,<6" }, { name = "transformers", marker = "extra == 'server-worker'", specifier = ">=5.14.1,<6" }, + { name = "transformers", marker = "extra == 'sound'", specifier = ">=5.14.1,<6" }, { name = "transformers", marker = "extra == 'videoprism'", specifier = ">=5.14.1,<6" }, { name = "typer", specifier = ">=0.27,<1" }, { name = "uvicorn", extras = ["standard"], marker = "extra == 'server'", specifier = ">=0.51,<0.52" }, { name = "uvicorn", extras = ["standard"], marker = "extra == 'server-worker'", specifier = ">=0.51,<0.52" }, ] -provides-extras = ["storage", "dialogue", "scene", "actor", "videoprism", "all", "local-worker", "mcp", "slm", "server", "server-worker", "test", "frontend", "benchmarks"] +provides-extras = ["storage", "dialogue", "scene", "actor", "videoprism", "sound", "all", "local-worker", "mcp", "slm", "server", "server-worker", "test", "frontend", "benchmarks"] [[package]] name = "watchdog" From ae2babd5f42f7a509d0be6017f626472f4e50241 Mon Sep 17 00:00:00 2001 From: Talha Amjad Date: Sat, 29 Aug 2026 22:58:24 +0500 Subject: [PATCH 3/9] feat(premiere): add installable media search for CEP and UXP (#131) * feat(premiere): add project media indexing and search * feat(premiere): add Desktop-managed CEP and UXP installation * fix(desktop): expose sound in guided setup * fix(desktop): make Premiere setup own dependencies * fix(desktop): recover stalled Windows media setup * fix(desktop): derive setup metadata from capabilities * build(premiere): adopt Bolt UXP tooling * build(premiere): complete Bolt UXP adoption --- .github/workflows/desktop.yml | 35 + .gitignore | 5 + .vscode/launch.json | 11 + INSTALLATION_GUIDE.md | 84 +- README.md | 1 + desktop/capability-catalog.json | 85 + desktop/runtime-manifest.json | 25 +- desktop/scripts/model-catalog.py | 32 +- desktop/src-tauri/build.rs | 15 + desktop/src-tauri/src/lib.rs | 253 +- desktop/src-tauri/src/media_setup.rs | 110 + desktop/src-tauri/src/premiere_integration.rs | 373 + desktop/src-tauri/tauri.conf.json | 10 +- desktop/src/App.test.tsx | 108 +- desktop/src/App.tsx | 23 +- desktop/src/components/ManagedSetup.tsx | 153 +- .../src/components/PremiereIntegration.tsx | 112 + desktop/src/components/TargetSummary.tsx | 35 +- desktop/src/desktopPackagingContracts.test.ts | 10 +- desktop/src/tauri.test.ts | 9 + desktop/src/tauri.ts | 42 +- docs/adding-a-capability.md | 12 +- docs/desktop.md | 31 +- docs/integrations/premiere-pro.md | 106 + docs/local-api.md | 18 + premiere/README.md | 102 + premiere/cep/CSXS/manifest.xml | 55 + premiere/cep/index.html | 13 + premiere/cep/index.tsx | 19 + premiere/cep/jsx/host.jsx | 121 + premiere/docs/MANUAL_TEST_CHECKLIST.md | 92 + premiere/eslint.config.mjs | 30 + premiere/index.html | 12 + premiere/index.tsx | 42 + premiere/package-lock.json | 6764 +++++++++++++++++ premiere/package.json | 52 + premiere/scripts/package-extensions.mjs | 55 + premiere/scripts/prepare-cep.mjs | 12 + premiere/src/premiere/adapter.ts | 104 + premiere/src/premiere/cep-adapter.ts | 43 + premiere/src/premiere/library.ts | 69 + premiere/src/premiere/types.ts | 28 + premiere/src/services/vidxp/cep-fetch.ts | 75 + premiere/src/services/vidxp/client.ts | 226 + premiere/src/services/vidxp/types.ts | 112 + premiere/src/ui/App.tsx | 650 ++ premiere/src/ui/components/Spectrum.tsx | 334 + premiere/src/ui/spectrum-elements.d.ts | 29 + premiere/src/ui/styles.css | 502 ++ premiere/src/ui/theme.ts | 126 + premiere/src/vite-env.d.ts | 1 + premiere/tests/cep-adapter.test.ts | 41 + premiere/tests/library.test.ts | 72 + premiere/tests/theme.test.ts | 61 + premiere/tests/uxp-config.test.ts | 21 + premiere/tests/vidxp-client.test.ts | 124 + premiere/tsconfig.json | 32 + premiere/uxp.config.ts | 68 + premiere/vite.config.ts | 60 + src/vidxp/api_routes/media.py | 74 + src/vidxp/application_models.py | 1 + src/vidxp/capabilities/actor/definition.py | 1 + src/vidxp/capabilities/contracts.py | 7 + src/vidxp/capabilities/dialogue/definition.py | 1 + src/vidxp/capabilities/scene/definition.py | 1 + src/vidxp/capabilities/sound/definition.py | 1 + .../capabilities/videoprism/definition.py | 1 + src/vidxp/capability_service.py | 1 + src/vidxp/control_plane.py | 28 + src/vidxp/local_probe.py | 48 +- src/vidxp/upload_service.py | 2 +- tests/test_api.py | 143 +- tests/test_capabilities.py | 13 + tests/test_control_plane.py | 24 + tests/test_local_probe.py | 22 +- tests/test_packaging.py | 9 +- 76 files changed, 12114 insertions(+), 108 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 desktop/capability-catalog.json create mode 100644 desktop/src-tauri/src/premiere_integration.rs create mode 100644 desktop/src/components/PremiereIntegration.tsx create mode 100644 docs/integrations/premiere-pro.md create mode 100644 premiere/README.md create mode 100644 premiere/cep/CSXS/manifest.xml create mode 100644 premiere/cep/index.html create mode 100644 premiere/cep/index.tsx create mode 100644 premiere/cep/jsx/host.jsx create mode 100644 premiere/docs/MANUAL_TEST_CHECKLIST.md create mode 100644 premiere/eslint.config.mjs create mode 100644 premiere/index.html create mode 100644 premiere/index.tsx create mode 100644 premiere/package-lock.json create mode 100644 premiere/package.json create mode 100644 premiere/scripts/package-extensions.mjs create mode 100644 premiere/scripts/prepare-cep.mjs create mode 100644 premiere/src/premiere/adapter.ts create mode 100644 premiere/src/premiere/cep-adapter.ts create mode 100644 premiere/src/premiere/library.ts create mode 100644 premiere/src/premiere/types.ts create mode 100644 premiere/src/services/vidxp/cep-fetch.ts create mode 100644 premiere/src/services/vidxp/client.ts create mode 100644 premiere/src/services/vidxp/types.ts create mode 100644 premiere/src/ui/App.tsx create mode 100644 premiere/src/ui/components/Spectrum.tsx create mode 100644 premiere/src/ui/spectrum-elements.d.ts create mode 100644 premiere/src/ui/styles.css create mode 100644 premiere/src/ui/theme.ts create mode 100644 premiere/src/vite-env.d.ts create mode 100644 premiere/tests/cep-adapter.test.ts create mode 100644 premiere/tests/library.test.ts create mode 100644 premiere/tests/theme.test.ts create mode 100644 premiere/tests/uxp-config.test.ts create mode 100644 premiere/tests/vidxp-client.test.ts create mode 100644 premiere/tsconfig.json create mode 100644 premiere/uxp.config.ts create mode 100644 premiere/vite.config.ts diff --git a/.github/workflows/desktop.yml b/.github/workflows/desktop.yml index 33f465df..50eb03ee 100644 --- a/.github/workflows/desktop.yml +++ b/.github/workflows/desktop.yml @@ -62,9 +62,39 @@ concurrency: cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: + premiere-packages: + name: Premiere extension packages + runs-on: windows-2025 + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ inputs.checkout_ref || github.event.inputs.checkout_ref || github.sha }} + + - uses: actions/setup-node@v7 + with: + node-version: "22" + cache: npm + cache-dependency-path: premiere/package-lock.json + + - name: Build and package both Premiere host generations + run: | + npm ci + npm run check + npm run package + working-directory: premiere + + - name: Preserve the Premiere packages + uses: actions/upload-artifact@v7 + with: + name: vidxp-premiere-packages + path: premiere/packages/* + if-no-files-found: error + retention-days: ${{ inputs.artifact_retention_days || 14 }} + build: name: ${{ matrix.target == 'windows' && 'Windows x86-64 NSIS' || matrix.target == 'macos' && 'macOS Apple Silicon DMG' || 'Linux x86-64 AppImage' }} runs-on: ${{ matrix.target == 'windows' && 'windows-2025' || matrix.target == 'macos' && 'macos-15' || 'ubuntu-24.04' }} + needs: premiere-packages strategy: fail-fast: false matrix: @@ -75,6 +105,11 @@ jobs: with: ref: ${{ inputs.checkout_ref || github.event.inputs.checkout_ref || github.sha }} + - uses: actions/download-artifact@v8 + with: + name: vidxp-premiere-packages + path: premiere/packages/ + - uses: actions/download-artifact@v8 if: inputs.package_artifact_name != '' with: diff --git a/.gitignore b/.gitignore index 1e944329..5cb56467 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,11 @@ pnpm-debug.log* /target /desktop/node_modules/ /desktop/dist/ +/premiere/dist/ +/premiere/ccx/ +/premiere/coverage/ +/premiere/packages/ +/premiere/.rnd /desktop/src-tauri/target/ /desktop/src-tauri/gen/ /desktop/src-tauri/binaries/uv-* diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..c18fc217 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "uxp", + "request": "attach", + "name": "Attach to VidXP Premiere UXP", + "manifestPath": "${workspaceFolder}/premiere/dist/manifest.json" + } + ] +} diff --git a/INSTALLATION_GUIDE.md b/INSTALLATION_GUIDE.md index 9288c417..7ae4036b 100644 --- a/INSTALLATION_GUIDE.md +++ b/INSTALLATION_GUIDE.md @@ -33,14 +33,16 @@ Approximate model downloads are: | Feature | Download | |---|---:| -| Dialogue search | 2.64 GiB | -| Sound event search | 0.94 GiB | +| Dialogue search | 2.63 GiB | +| Sound event search | 0.91 GiB | | Scene search | 1.43 GiB | | Action search | 0.93 GiB | | Actor matching | 37 MiB | The Desktop-managed runtime can use about 3 GiB in addition to selected models. -A complete local setup uses about 8.1 GiB before adding videos and indexes. +All five model sets total about 5.95 GiB, so a complete local setup uses about +8.95 GiB before temporary installation space, videos, and indexes. Desktop +calculates the storage plan from the features currently selected during setup. ## Desktop app @@ -78,6 +80,13 @@ setup leaves the previous working setup available. - **AI assistant integration** lets a local MCP-compatible assistant use VidXP. - **App integration service** lets other local applications use the HTTP API or Streamable HTTP MCP. +- **Premiere Pro extension** installs the matching Adobe panel and automatically + includes local video processing plus the private app integration service. + +The [Premiere Pro extension](docs/integrations/premiere-pro.md) searches media +that is already loaded in an editing project. Desktop includes both supported +Adobe extension packages; users do not need the source repository or a +JavaScript toolchain. Choose where models should be stored, then decide whether to download them during setup. VidXP displays the required downloads before starting them. @@ -94,6 +103,37 @@ After setup finishes: Browser and API sharing are off by default. If you enable sharing, use it only on a trusted local network and follow the warning shown by Desktop. +### Install the Premiere Pro extension + +On Windows, VidXP supports Premiere Pro 23.0 or newer with two packaged host +variants: + +- Premiere Pro 23.0 through 25.5 uses the CEP extension; +- Premiere Pro 25.6 or newer uses the UXP extension. + +In Desktop setup, select **Premiere Pro extension** and the search features you +want. Desktop automatically includes local video processing and its private app +service, then detects standard Premiere installations and installs the matching +package through Adobe Creative Cloud's plugin installer. If Adobe requires +confirmation or elevation, finish the Creative Cloud prompt. Restart Premiere +after installation. + +For an existing Desktop-managed VidXP installation, select **Set up Premiere** +on the summary screen. Desktop opens the same setup flow with the Premiere +requirements already selected. **Install for Premiere** remains available there +for reinstalling or retrying only the Adobe package. + +For Premiere Pro 23.2, open the panel from **Window > Extensions (Legacy) > +VidXP Search**. For Premiere Pro 25.6 or newer, use **Window > UXP Plugins > +VidXP Search**. If both Premiere generations are installed, Desktop installs +both packages; their host ranges do not overlap. + +Plain loopback HTTP is blocked by Premiere UXP on macOS. Desktop therefore does +not claim the 25.6+ macOS panel as usable until the integration has a trusted +local HTTPS transport. The CEP package can use its native local transport on +supported macOS Premiere versions, but still requires host validation before a +release claims support. + ## Command line Command-line installation uses @@ -410,6 +450,44 @@ vidxp doctor Review the suggested package-manager command before approving it. +### Windows Desktop is stuck while checking FFmpeg + +Select **Cancel setup**. VidXP stops the package-manager process and keeps the +previous installation and your setup choices. If an older Desktop release does +not show that button, use **Quit VidXP** from the system tray. If it does not +respond, end **VidXP Desktop** in Task Manager. End `winget.exe` there too if it +continues running after VidXP has closed. + +Open a new PowerShell window so it receives any PATH changes made by WinGet, +then check whether FFmpeg finished installing: + +```powershell +winget list --id Gyan.FFmpeg --exact +where.exe ffmpeg +where.exe ffprobe +``` + +If WinGet lists `Gyan.FFmpeg` and both executables are found, reopen VidXP +Desktop and retry setup. If the package is missing, or WinGet lists it but the +executables are still not found, repair the package: + +```powershell +winget install --id Gyan.FFmpeg --exact --source winget --force --silent ` + --disable-interactivity --accept-package-agreements ` + --accept-source-agreements +``` + +Open another new PowerShell window and verify the result: + +```powershell +ffmpeg -hide_banner -version +ffprobe -hide_banner -version +ffmpeg -hide_banner -encoders | findstr /i "libx264 aac" +``` + +The final command should list both `libx264` and `aac`. Reopen VidXP Desktop +and retry setup after all three commands succeed. + ### Linux or Windows starts downloading NVIDIA packages Reinstall with the supported CPU dependency set: diff --git a/README.md b/README.md index 7f08f1b3..3c199746 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,7 @@ ordinary MCP tools. - [Python, HTTP, and MCP installation](INSTALLATION_GUIDE.md) - [Local HTTP API and MCP server](docs/local-api.md) +- [Premiere Pro extension](docs/integrations/premiere-pro.md) - [ChatGPT and Codex plugin integration](docs/integrations/openai-plugin.md) - [Optional capability packages](INSTALLATION_GUIDE.md#optional-dependency-extras) - [Coolify server setup](docs/deployment/coolify.md) diff --git a/desktop/capability-catalog.json b/desktop/capability-catalog.json new file mode 100644 index 00000000..543a5b9d --- /dev/null +++ b/desktop/capability-catalog.json @@ -0,0 +1,85 @@ +{ + "schema_version": 1, + "capabilities": { + "dialogue": { + "extra": "dialogue", + "modality": "dialogue", + "label": "Dialogue search", + "description": "Index and search spoken dialogue.", + "models": [ + { + "cache_key": "models--Qwen--Qwen3-Embedding-0.6B/snapshots/97b0c614be4d77ee51c0cef4e5f07c00f9eb65b3/model.safetensors", + "download_size_bytes": 1207489041 + }, + { + "cache_key": "models--dropbox-dash--faster-whisper-large-v3-turbo/snapshots/0a363e9161cbc7ed1431c9597a8ceaf0c4f78fcf/model.bin", + "download_size_bytes": 1621668947 + } + ] + }, + "sound": { + "extra": "sound", + "modality": "sound", + "label": "Sound event search", + "description": "Index and search music, environmental sounds, and audio events.", + "models": [ + { + "cache_key": "finelap-tokenizer/config.json", + "download_size_bytes": 481 + }, + { + "cache_key": "finelap-tokenizer/merges.txt", + "download_size_bytes": 456318 + }, + { + "cache_key": "finelap-tokenizer/vocab.json", + "download_size_bytes": 898823 + }, + { + "cache_key": "models--AndreasXi--FineLAP/snapshots/b419aa22947d29907a5567f21b81bf3b39a40449/model.safetensors", + "download_size_bytes": 980404741 + } + ] + }, + "scene": { + "extra": "scene", + "modality": "scene", + "label": "Visual scene search", + "description": "Index and search visual scenes.", + "models": [ + { + "cache_key": "models--google--siglip2-base-patch16-224/snapshots/75de2d55ec2d0b4efc50b3e9ad70dba96a7b2fa2/model.safetensors", + "download_size_bytes": 1539458338 + } + ] + }, + "actor": { + "extra": "actor", + "modality": "actor", + "label": "Actor recognition", + "description": "Index, inspect, and render actor clusters.", + "models": [ + { + "cache_key": "opencv-zoo/face_detection_yunet_2026may.onnx", + "download_size_bytes": 229738 + }, + { + "cache_key": "opencv-zoo/face_recognition_sface_2021dec.onnx", + "download_size_bytes": 38696353 + } + ] + }, + "videoprism": { + "extra": "videoprism", + "modality": "videoprism", + "label": "Temporal video search", + "description": "Index and search temporal video clips with VideoPrism.", + "models": [ + { + "cache_key": "models--google--videoprism-lvt-base-f16r288/snapshots/fb6de9f0eb7bc285be86bdca1cf7daa3e3ef51ff/model.safetensors", + "download_size_bytes": 993993146 + } + ] + } + } +} diff --git a/desktop/runtime-manifest.json b/desktop/runtime-manifest.json index ba16d94f..6a2b7583 100644 --- a/desktop/runtime-manifest.json +++ b/desktop/runtime-manifest.json @@ -6,6 +6,7 @@ "dependency_index": "https://pypi.org/simple", "python_version": "3.14.6", "uv_version": "0.12.0", + "managed_runtime_estimated_size_bytes": 3221225472, "surfaces": { "worker": { "extra": "local-worker", @@ -28,32 +29,10 @@ "server": { "extra": "server", "label": "App integration service", - "description": "Run an API and network-style MCP connection for other software. It is private by default and can be shared on your local network with bearer-token authentication.", + "description": "Connect other apps through VidXP's private local API. Local-network sharing is optional and requires bearer-token authentication.", "default": false } }, - "capabilities": { - "dialogue": { - "extra": "dialogue", - "modality": "dialogue", - "label": "Dialogue search" - }, - "scene": { - "extra": "scene", - "modality": "scene", - "label": "Visual scene search" - }, - "actor": { - "extra": "actor", - "modality": "actor", - "label": "Actor recognition" - }, - "videoprism": { - "extra": "videoprism", - "modality": "videoprism", - "label": "Temporal video search" - } - }, "media_runtime": { "strategy": "system", "executables": [ diff --git a/desktop/scripts/model-catalog.py b/desktop/scripts/model-catalog.py index be30acc7..e48ad832 100644 --- a/desktop/scripts/model-catalog.py +++ b/desktop/scripts/model-catalog.py @@ -9,15 +9,21 @@ ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT / "src")) -from vidxp.local_probe import desktop_model_cache_catalog # noqa: E402 +from vidxp.local_probe import ( # noqa: E402 + desktop_capability_catalog, + desktop_model_cache_catalog, +) -CATALOG_PATH = ROOT / "desktop" / "model-cache-catalog.json" +CATALOGS = { + ROOT / "desktop" / "capability-catalog.json": desktop_capability_catalog, + ROOT / "desktop" / "model-cache-catalog.json": desktop_model_cache_catalog, +} -def rendered_catalog() -> str: +def rendered_catalog(value: object) -> str: return json.dumps( - desktop_model_cache_catalog(), + value, indent=2, ensure_ascii=False, ) + "\n" @@ -29,13 +35,23 @@ def main() -> int: mode.add_argument("--write", action="store_true") mode.add_argument("--check", action="store_true") arguments = parser.parse_args() - derived = rendered_catalog() if arguments.write: - CATALOG_PATH.write_text(derived, encoding="utf-8", newline="\n") + for path, derive in CATALOGS.items(): + path.write_text( + rendered_catalog(derive()), + encoding="utf-8", + newline="\n", + ) return 0 - if CATALOG_PATH.read_text(encoding="utf-8") != derived: + stale = [ + path.relative_to(ROOT).as_posix() + for path, derive in CATALOGS.items() + if not path.exists() + or path.read_text(encoding="utf-8") != rendered_catalog(derive()) + ] + if stale: raise SystemExit( - "desktop/model-cache-catalog.json is stale; run npm run " + f"{', '.join(stale)} is stale; run npm run " "model-catalog:write from desktop/." ) return 0 diff --git a/desktop/src-tauri/build.rs b/desktop/src-tauri/build.rs index 37090520..174f6707 100644 --- a/desktop/src-tauri/build.rs +++ b/desktop/src-tauri/build.rs @@ -6,6 +6,20 @@ fn main() { let mut manifest: serde_json::Value = serde_json::from_slice(include_bytes!("../runtime-manifest.json")) .expect("desktop/runtime-manifest.json must be valid JSON"); + let capability_catalog: serde_json::Value = + serde_json::from_slice(include_bytes!("../capability-catalog.json")) + .expect("desktop/capability-catalog.json must be valid JSON"); + assert_eq!( + capability_catalog["schema_version"].as_u64(), + Some(1), + "desktop capability catalog uses an unsupported schema version" + ); + let capabilities = capability_catalog["capabilities"] + .as_object() + .filter(|capabilities| !capabilities.is_empty()) + .expect("desktop capability catalog must contain capabilities") + .clone(); + manifest["capabilities"] = serde_json::Value::Object(capabilities); let expected = manifest["uv_version"] .as_str() .expect("runtime manifest must contain uv_version"); @@ -152,6 +166,7 @@ fn main() { println!("cargo:rerun-if-changed=../../uv.lock"); println!("cargo:rerun-if-changed=../../dist"); println!("cargo:rerun-if-changed=../runtime-manifest.json"); + println!("cargo:rerun-if-changed=../capability-catalog.json"); let attributes = tauri_build::Attributes::new(); #[cfg(windows)] diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index d699e420..fca2be96 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -31,6 +31,7 @@ mod background_process; mod browser_readiness; mod lifecycle; mod media_setup; +mod premiere_integration; mod target_profiles; use activation::{ActivationRecovery, ActivationStage, activation_recovery}; @@ -56,13 +57,22 @@ const CODEX_PLUGIN_MARKETPLACE_REF: Option<&str> = option_env!("VIDXP_PLUGIN_MAR const PRODUCT_DATA_DIRECTORY_NAME: &str = "VidXP"; const RUNTIME_CONSTRAINTS_FILE_NAME: &str = "runtime-constraints.txt"; const MAX_SETUP_OUTPUT_BYTES: usize = 4 * 1024 * 1024; +const MEDIA_RUNTIME_INSTALL_TIMEOUT: Duration = Duration::from_secs(15 * 60); static READINESS_SEQUENCE: AtomicU64 = AtomicU64::new(0); +#[derive(Clone, Deserialize, Serialize)] +struct ModelDownloadSpec { + cache_key: String, + download_size_bytes: u64, +} + #[derive(Clone, Deserialize, Serialize)] struct CapabilitySpec { extra: String, modality: String, label: String, + description: String, + models: Vec, } #[derive(Clone, Deserialize, Serialize)] @@ -90,6 +100,7 @@ struct RuntimeManifest { dependency_constraints_sha256: String, python_version: String, uv_version: String, + managed_runtime_estimated_size_bytes: u64, surfaces: BTreeMap, capabilities: BTreeMap, media_runtime: MediaRuntimeSpec, @@ -1374,6 +1385,13 @@ fn executable_candidates(name: &str) -> Vec { .join("Links"), ); } + if cfg!(windows) { + for variable in ["PROGRAMFILES", "PROGRAMFILES(X86)", "ProgramW6432"] { + if let Some(program_files) = env::var_os(variable) { + directories.push(PathBuf::from(program_files).join("WinGet").join("Links")); + } + } + } if cfg!(target_os = "macos") { directories.extend([ PathBuf::from("/opt/homebrew/bin"), @@ -1400,10 +1418,18 @@ fn executable_candidates(name: &str) -> Vec { } fn resolve_system_executable(name: &str) -> Option { - executable_candidates(name) + let resolved = executable_candidates(name) .into_iter() .find(|candidate| candidate.is_file()) - .and_then(|candidate| fs::canonicalize(&candidate).ok().or(Some(candidate))) + .and_then(|candidate| fs::canonicalize(&candidate).ok().or(Some(candidate))); + if resolved.is_some() { + return resolved; + } + #[cfg(windows)] + if matches!(name, "ffmpeg" | "ffprobe") { + return media_setup::resolve_winget_ffmpeg_executable(&format!("{name}.exe")); + } + None } fn combined_output(output: &background_process::BackgroundOutput) -> String { @@ -2061,11 +2087,26 @@ async fn supervised_output( command: Command, cancellation: background_process::CancellationToken, operation: &str, +) -> Result { + supervised_output_with_timeout( + command, + cancellation, + operation, + Duration::from_secs(30 * 60), + ) + .await +} + +async fn supervised_output_with_timeout( + command: Command, + cancellation: background_process::CancellationToken, + operation: &str, + timeout: Duration, ) -> Result { let output = background_process::run_async( command, background_process::BackgroundPolicy { - timeout: Duration::from_secs(30 * 60), + timeout, max_output_bytes: MAX_SETUP_OUTPUT_BYTES, }, cancellation, @@ -2546,6 +2587,54 @@ async fn cancel_managed_setup( })? } +#[tauri::command] +fn cancel_managed_setup_operation( + state: tauri::State<'_, DesktopState>, + draft_id: String, +) -> Result<(), String> { + cancel_managed_setup_operation_inner(&state, &draft_id) +} + +fn cancel_managed_setup_operation_inner( + state: &DesktopState, + draft_id: &str, +) -> Result<(), String> { + { + let transition = state + .transition + .lock() + .map_err(|_| "The managed setup operation is unavailable.".to_string())?; + let record = transition + .draft + .as_ref() + .ok_or_else(|| "This managed setup draft has expired.".to_string())?; + if record.draft.id != draft_id { + return Err("A stale managed setup screen cannot stop the current operation.".into()); + } + if record.phase != DraftPhase::Applying + || !transition.active.is_some_and(|active| { + matches!( + active.kind, + TransitionKind::InstallMedia + | TransitionKind::InstallRuntime + | TransitionKind::PrepareModels + ) + }) + { + return Err("No cancellable managed setup operation is active.".into()); + } + } + let active = state + .operation_cancellation + .lock() + .map_err(|_| "The setup cancellation supervisor is unavailable.".to_string())?; + let cancellation = active + .as_ref() + .ok_or_else(|| "The managed setup operation has already settled.".to_string())?; + cancellation.cancel(); + Ok(()) +} + #[tauri::command] fn choose_model_directory( app: AppHandle, @@ -2571,8 +2660,10 @@ async fn install_media_runtime( app: AppHandle, state: tauri::State<'_, DesktopState>, draft_id: String, + total_steps: u8, ) -> Result { let cancellation = OperationCancellationGuard::register(&state)?; + let total_steps = total_steps.max(1); let _transition = TargetTransitionCoordinator::begin_apply(&state, &draft_id, TransitionKind::InstallMedia) .map_err(|error| error.to_string())?; @@ -2618,20 +2709,47 @@ async fn install_media_runtime( if !approved { return Err("FFmpeg setup was deferred.".into()); } + emit_managed_setup_progress( + &app, + &draft_id, + 1, + total_steps, + "video-tools", + &format!("Installing FFmpeg with {}", plan.manager), + ); let command = app .shell() .command(plan.command[0].clone()) .args(&plan.command[1..]); let command: Command = command.into(); - supervised_output( + supervised_output_with_timeout( command, cancellation.token(), &format!("{} FFmpeg installation", plan.manager), + MEDIA_RUNTIME_INSTALL_TIMEOUT, ) .await?; - let status = tauri::async_runtime::spawn_blocking(inspect_media_runtime) - .await - .map_err(|error| format!("Media runtime verification stopped unexpectedly: {error}"))?; + emit_managed_setup_progress( + &app, + &draft_id, + 1, + total_steps, + "video-tools", + "Verifying FFmpeg and required video codecs", + ); + let status = tauri::async_runtime::spawn_blocking(|| { + let mut status = inspect_media_runtime(); + for _ in 0..4 { + if status.ready { + break; + } + thread::sleep(Duration::from_secs(1)); + status = inspect_media_runtime(); + } + status + }) + .await + .map_err(|error| format!("Media runtime verification stopped unexpectedly: {error}"))?; if !status.ready { return Err(format!( "FFmpeg installation finished but verification failed: {}", @@ -3728,6 +3846,60 @@ async fn install_codex_plugin( .map_err(|error| format!("Codex plugin setup stopped unexpectedly: {error}"))? } +#[tauri::command] +fn premiere_integration_state( + app: AppHandle, +) -> Result { + let resource_dir = app + .path() + .resource_dir() + .map_err(|error| format!("Could not locate VidXP Desktop resources: {error}"))?; + Ok(premiere_integration::state(&resource_dir)) +} + +#[tauri::command] +async fn install_premiere_extensions( + app: AppHandle, + state: tauri::State<'_, DesktopState>, +) -> Result { + let _active = state.active_operations.register()?; + let (profile, _) = selected_target_context(&app)?; + if !profile.surfaces.iter().any(|surface| surface == "server") { + return Err("Enable the App integration service in Setup options before installing the Premiere extension.".into()); + } + let worker_app = app.clone(); + let (result, packages) = tauri::async_runtime::spawn_blocking(move || { + let resource_dir = worker_app + .path() + .resource_dir() + .map_err(|error| format!("Could not locate VidXP Desktop resources: {error}"))?; + premiere_integration::install(&resource_dir) + }) + .await + .map_err(|error| format!("Premiere setup stopped unexpectedly: {error}"))??; + for package in packages { + app.opener() + .open_path(package.display().to_string(), None::<&str>) + .map_err(|error| { + format!( + "Could not open {} with Adobe Creative Cloud: {error}", + package.display() + ) + })?; + } + Ok(result) +} + +#[tauri::command] +async fn uninstall_premiere_extensions( + state: tauri::State<'_, DesktopState>, +) -> Result<(), String> { + let _active = state.active_operations.register()?; + tauri::async_runtime::spawn_blocking(premiere_integration::uninstall) + .await + .map_err(|error| format!("Premiere removal stopped unexpectedly: {error}"))? +} + fn execute_worker_action(app: &AppHandle, action: &str) -> Result { let (profile, paths) = selected_target_context(app)?; if !profile.surfaces.iter().any(|surface| surface == "worker") { @@ -4748,6 +4920,7 @@ pub fn run() { confirm_forget_target, begin_managed_setup, cancel_managed_setup, + cancel_managed_setup_operation, choose_model_directory, install_media_runtime, runtime_status, @@ -4759,6 +4932,9 @@ pub fn run() { configure_external_installation, mcp_client_config, install_codex_plugin, + premiere_integration_state, + install_premiere_extensions, + uninstall_premiere_extensions, local_worker_status, start_local_worker, stop_local_worker, @@ -5038,6 +5214,44 @@ mod tests { ); } + #[test] + fn active_managed_operation_can_be_cancelled_without_cancelling_its_draft() { + let state = DesktopState::default(); + state.transition.lock().expect("transition").draft = Some(DraftRecord { + draft: ManagedSetupDraft { + id: "draft-current".into(), + previous_profile_id: None, + }, + phase: DraftPhase::Draft, + }); + let operation = super::OperationCancellationGuard::register(&state).expect("operation"); + let token = operation.token(); + let applying = TargetTransitionCoordinator::begin_apply( + &state, + "draft-current", + TransitionKind::InstallMedia, + ) + .expect("apply"); + + super::cancel_managed_setup_operation_inner(&state, "draft-current") + .expect("cancel operation"); + + assert!(token.is_cancelled()); + assert_eq!( + state + .transition + .lock() + .expect("transition") + .draft + .as_ref() + .expect("draft") + .phase, + DraftPhase::Applying + ); + drop(applying); + drop(operation); + } + fn worker_supervisor_fixture() -> ( Arc, Arc, @@ -5362,6 +5576,31 @@ mod tests { assert!(selected_capabilities(&manifest, &["other".into()]).is_err()); } + #[test] + fn runtime_manifest_exposes_sound_setup_and_dependency() { + let manifest = manifest().expect("manifest"); + let sound = manifest + .capabilities + .get("sound") + .expect("sound setup capability"); + + assert_eq!(sound.extra, "sound"); + assert_eq!(sound.modality, "sound"); + assert_eq!(sound.label, "Sound event search"); + assert_eq!( + sound + .models + .iter() + .map(|model| model.download_size_bytes) + .sum::(), + 981_760_363 + ); + assert_eq!( + package_specification(&manifest, &["sound".into()], &[]), + format!("vidxp[sound]=={}", manifest.package_version) + ); + } + #[test] fn runtime_constraints_digest_is_independent_of_checkout_line_endings() { let canonical = normalized_runtime_constraints(); diff --git a/desktop/src-tauri/src/media_setup.rs b/desktop/src-tauri/src/media_setup.rs index 192ed68c..7ac27e0d 100644 --- a/desktop/src-tauri/src/media_setup.rs +++ b/desktop/src-tauri/src/media_setup.rs @@ -1,5 +1,8 @@ use std::path::PathBuf; +#[cfg(windows)] +use std::{env, fs, path::Path}; + pub(crate) struct SystemInstallPlan { pub(crate) manager: String, pub(crate) command: Vec, @@ -21,6 +24,8 @@ pub(crate) fn system_install_plan( "--exact".into(), "--source".into(), "winget".into(), + "--silent".into(), + "--disable-interactivity".into(), "--accept-package-agreements".into(), "--accept-source-agreements".into(), ], @@ -86,3 +91,108 @@ pub(crate) fn required_encoder_missing(output: &str, encoder: &str) -> bool { .flat_map(|line| line.split_whitespace()) .any(|token| token == encoder) } + +#[cfg(windows)] +fn find_winget_package_executable_in(root: &Path, name: &str) -> Option { + fn visit(directory: &Path, name: &str, remaining_depth: u8, matches: &mut Vec) { + if remaining_depth == 0 { + return; + } + let Ok(entries) = fs::read_dir(directory) else { + return; + }; + for entry in entries.flatten() { + let path = entry.path(); + if path.is_file() + && path + .file_name() + .is_some_and(|candidate| candidate.to_string_lossy().eq_ignore_ascii_case(name)) + { + matches.push(path); + } else if path.is_dir() { + visit(&path, name, remaining_depth - 1, matches); + } + } + } + + let entries = fs::read_dir(root).ok()?; + let mut matches = Vec::new(); + for entry in entries.flatten() { + let package = entry.path(); + if package.is_dir() + && package + .file_name() + .is_some_and(|candidate| candidate.to_string_lossy().starts_with("Gyan.FFmpeg_")) + { + visit(&package, name, 6, &mut matches); + } + } + matches.sort(); + matches.into_iter().next() +} + +#[cfg(windows)] +pub(crate) fn resolve_winget_ffmpeg_executable(name: &str) -> Option { + let mut roots = Vec::new(); + if let Some(local) = env::var_os("LOCALAPPDATA") { + roots.push( + PathBuf::from(local) + .join("Microsoft") + .join("WinGet") + .join("Packages"), + ); + } + for variable in ["PROGRAMFILES", "PROGRAMFILES(X86)", "ProgramW6432"] { + if let Some(program_files) = env::var_os(variable) { + roots.push(PathBuf::from(program_files).join("WinGet").join("Packages")); + } + } + roots.into_iter().find_map(|root| { + find_winget_package_executable_in(&root, name) + .and_then(|candidate| fs::canonicalize(&candidate).ok().or(Some(candidate))) + }) +} + +#[cfg(all(test, windows))] +mod tests { + use super::*; + + #[test] + fn resolves_ffmpeg_from_a_winget_package_when_its_alias_is_missing() { + let root = std::env::temp_dir().join(format!( + "vidxp-winget-ffmpeg-{}-{}", + std::process::id(), + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("clock") + .as_nanos() + )); + let executable = root + .join("Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe") + .join("ffmpeg-build") + .join("bin") + .join("ffmpeg.exe"); + fs::create_dir_all(executable.parent().expect("executable parent")).expect("package"); + fs::write(&executable, b"fixture").expect("executable"); + + assert_eq!( + find_winget_package_executable_in(&root, "ffmpeg.exe"), + Some(executable) + ); + let _ = fs::remove_dir_all(root); + } + + #[test] + fn ffmpeg_install_disables_hidden_package_manager_prompts() { + let plan = + system_install_plan(|name| (name == "winget").then(|| PathBuf::from("winget.exe"))) + .expect("install plan"); + + assert!(plan.command.iter().any(|argument| argument == "--silent")); + assert!( + plan.command + .iter() + .any(|argument| argument == "--disable-interactivity") + ); + } +} diff --git a/desktop/src-tauri/src/premiere_integration.rs b/desktop/src-tauri/src/premiere_integration.rs new file mode 100644 index 00000000..da3d6862 --- /dev/null +++ b/desktop/src-tauri/src/premiere_integration.rs @@ -0,0 +1,373 @@ +use std::{ + collections::BTreeSet, + path::{Path, PathBuf}, + process::Command, +}; + +#[cfg(windows)] +use std::env; +#[cfg(target_os = "macos")] +use std::fs; + +use serde::{Deserialize, Serialize}; + +const CEP_ID: &str = "org.grayhat.vidxp-premiere.cep.search"; +const UXP_ID: &str = "org.grayhat.vidxp-premiere"; +const CEP_PACKAGE: &str = "vidxp-premiere-cep.zxp"; +const UXP_PACKAGE: &str = "vidxp-premiere-uxp.ccx"; + +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum PremiereHostKind { + Cep, + Uxp, + Unsupported, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct PremiereInstallation { + pub display_name: String, + pub version: String, + pub executable: String, + pub host_kind: PremiereHostKind, + pub compatible: bool, +} + +#[derive(Clone, Debug, Serialize)] +pub struct PremiereIntegrationState { + pub installations: Vec, + pub platform_supported: bool, + pub installer_available: bool, + pub cep_package_available: bool, + pub uxp_package_available: bool, + pub cep_installed: bool, + pub uxp_installed: bool, + pub detail: String, +} + +#[derive(Clone, Debug, Serialize)] +pub struct PremiereInstallResult { + pub installed_hosts: Vec, + pub opened_packages: Vec, + pub detail: String, +} + +#[derive(Deserialize)] +struct WindowsInstallation { + #[serde(rename = "Name")] + name: String, + #[serde(rename = "Version")] + version: String, + #[serde(rename = "Executable")] + executable: String, +} + +pub fn state(resource_dir: &Path) -> PremiereIntegrationState { + let installations = discover_installations(); + let installer = unified_plugin_installer(); + let installed = installer + .as_ref() + .and_then(|path| installer_list(path).ok()) + .unwrap_or_default(); + let cep_package_available = package_path(resource_dir, PremiereHostKind::Cep).is_file(); + let uxp_package_available = package_path(resource_dir, PremiereHostKind::Uxp).is_file(); + let compatible_count = installations.iter().filter(|item| item.compatible).count(); + let detail = if compatible_count == 0 { + "No compatible Premiere installation was found in its standard application folder. Both packages remain available for custom Adobe installations.".into() + } else { + format!( + "Found {compatible_count} compatible Premiere installation{}.", + if compatible_count == 1 { "" } else { "s" } + ) + }; + PremiereIntegrationState { + installations, + platform_supported: cfg!(any(windows, target_os = "macos")), + installer_available: installer.is_some(), + cep_package_available, + uxp_package_available, + cep_installed: installed.contains(CEP_ID), + uxp_installed: installed.contains(UXP_ID), + detail, + } +} + +pub fn install(resource_dir: &Path) -> Result<(PremiereInstallResult, Vec), String> { + if !cfg!(any(windows, target_os = "macos")) { + return Err( + "Premiere extension installation is available on Windows and macOS only.".into(), + ); + } + let installations = discover_installations(); + let mut kinds = installations + .iter() + .filter(|item| item.compatible) + .map(|item| item.host_kind.clone()) + .collect::>(); + if kinds.is_empty() { + if !installations.is_empty() { + return Err("The detected Premiere installations are not supported by this VidXP extension build.".into()); + } + kinds.insert(PremiereHostKind::Cep); + if cfg!(windows) { + kinds.insert(PremiereHostKind::Uxp); + } + } + let installer = unified_plugin_installer(); + let mut installed_hosts = Vec::new(); + let mut opened = Vec::new(); + for kind in kinds { + let package = package_path(resource_dir, kind.clone()); + if !package.is_file() { + return Err(format!( + "The bundled {} package is missing. Reinstall or update VidXP Desktop.", + package.display() + )); + } + if installer + .as_ref() + .is_some_and(|path| installer_install(path, &package).is_ok()) + { + installed_hosts.push(kind); + } else { + opened.push(package); + } + } + let result = PremiereInstallResult { + installed_hosts, + opened_packages: opened + .iter() + .map(|path| path.display().to_string()) + .collect(), + detail: if opened.is_empty() { + "The VidXP extension was installed. Restart Premiere, then open VidXP Search from the Window menu.".into() + } else { + "Adobe's background installer was unavailable or required interaction. Complete the Creative Cloud installation window, then restart Premiere.".into() + }, + }; + Ok((result, opened)) +} + +pub fn uninstall() -> Result<(), String> { + let installer = unified_plugin_installer() + .ok_or_else(|| "Adobe Creative Cloud's plugin installer was not found.".to_string())?; + let mut failures = Vec::new(); + for id in [CEP_ID, UXP_ID] { + if let Err(error) = installer_remove(&installer, id) { + failures.push(error); + } + } + if failures.len() == 2 { + Err(failures.join(" ")) + } else { + Ok(()) + } +} + +fn package_path(resource_dir: &Path, kind: PremiereHostKind) -> PathBuf { + let name = match kind { + PremiereHostKind::Cep => CEP_PACKAGE, + PremiereHostKind::Uxp => UXP_PACKAGE, + PremiereHostKind::Unsupported => unreachable!("unsupported hosts do not have packages"), + }; + resource_dir.join("premiere").join(name) +} + +fn host_kind(version: &str) -> PremiereHostKind { + let mut parts = version + .split('.') + .filter_map(|part| part.parse::().ok()); + let major = parts.next().unwrap_or_default(); + let minor = parts.next().unwrap_or_default(); + if major > 25 || (major == 25 && minor >= 6) { + PremiereHostKind::Uxp + } else if major >= 23 { + PremiereHostKind::Cep + } else { + PremiereHostKind::Unsupported + } +} + +fn discover_installations() -> Vec { + #[cfg(windows)] + return discover_windows_installations(); + #[cfg(target_os = "macos")] + return discover_macos_installations(); + #[cfg(not(any(windows, target_os = "macos")))] + Vec::new() +} + +#[cfg(windows)] +fn discover_windows_installations() -> Vec { + let script = r#" +$items = @() +$roots = @($env:ProgramFiles, ${env:ProgramFiles(x86)}) | Where-Object { $_ } | Select-Object -Unique +foreach ($root in $roots) { + $adobe = Join-Path $root 'Adobe' + if (-not (Test-Path -LiteralPath $adobe)) { continue } + Get-ChildItem -LiteralPath $adobe -Directory -Filter 'Adobe Premiere Pro*' -ErrorAction SilentlyContinue | ForEach-Object { + $exe = Join-Path $_.FullName 'Adobe Premiere Pro.exe' + if (Test-Path -LiteralPath $exe) { + $info = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($exe) + $items += [pscustomobject]@{ Name = $_.Name; Version = $info.ProductVersion; Executable = $exe } + } + } +} +$items | ConvertTo-Json -Compress +"#; + let output = Command::new("powershell.exe") + .args(["-NoProfile", "-NonInteractive", "-Command", script]) + .output(); + let Ok(output) = output else { + return Vec::new(); + }; + if !output.status.success() { + return Vec::new(); + } + parse_windows_installations(&output.stdout) +} + +#[cfg(windows)] +fn parse_windows_installations(payload: &[u8]) -> Vec { + let value: serde_json::Value = match serde_json::from_slice(payload) { + Ok(value) => value, + Err(_) => return Vec::new(), + }; + let values = match value { + serde_json::Value::Array(values) => values, + serde_json::Value::Object(_) => vec![value], + _ => Vec::new(), + }; + values + .into_iter() + .filter_map(|value| { + let item: WindowsInstallation = serde_json::from_value(value).ok()?; + let kind = host_kind(&item.version); + let compatible = kind != PremiereHostKind::Unsupported + && !(cfg!(target_os = "macos") && kind == PremiereHostKind::Uxp); + Some(PremiereInstallation { + display_name: item.name, + version: item.version, + executable: item.executable, + compatible, + host_kind: kind, + }) + }) + .collect() +} + +#[cfg(target_os = "macos")] +fn discover_macos_installations() -> Vec { + let Ok(entries) = fs::read_dir("/Applications") else { + return Vec::new(); + }; + entries + .flatten() + .filter_map(|entry| { + let path = entry.path(); + let name = path.file_name()?.to_str()?; + if !name.starts_with("Adobe Premiere Pro") || path.extension()?.to_str()? != "app" { + return None; + } + let plist = fs::read_to_string(path.join("Contents/Info.plist")).ok()?; + let version = plist_value(&plist, "CFBundleShortVersionString")?; + let kind = host_kind(&version); + let compatible = kind != PremiereHostKind::Unsupported + && !(cfg!(target_os = "macos") && kind == PremiereHostKind::Uxp); + Some(PremiereInstallation { + display_name: name.trim_end_matches(".app").into(), + version, + executable: path.display().to_string(), + compatible, + host_kind: kind, + }) + }) + .collect() +} + +#[cfg(target_os = "macos")] +fn plist_value(plist: &str, key: &str) -> Option { + let marker = format!("{key}"); + let remainder = plist.split_once(&marker)?.1; + let value = remainder + .split_once("")? + .1 + .split_once("")? + .0; + Some(value.trim().into()) +} + +fn unified_plugin_installer() -> Option { + #[cfg(windows)] + { + let program_files = env::var_os("ProgramFiles")?; + let path = PathBuf::from(program_files).join("Common Files/Adobe/Adobe Desktop Common/RemoteComponents/UPI/UnifiedPluginInstallerAgent/UnifiedPluginInstallerAgent.exe"); + path.is_file().then_some(path) + } + #[cfg(target_os = "macos")] + { + let path = PathBuf::from( + "/Library/Application Support/Adobe/Adobe Desktop Common/RemoteComponents/UPI/UnifiedPluginInstallerAgent/UnifiedPluginInstallerAgent.app/Contents/MacOS/UnifiedPluginInstallerAgent", + ); + return path.is_file().then_some(path); + } + #[cfg(not(any(windows, target_os = "macos")))] + None +} + +fn installer_list(installer: &Path) -> Result { + let argument = if cfg!(windows) { "/list" } else { "--list" }; + checked_installer(installer, [argument, "all"]) +} + +fn installer_install(installer: &Path, package: &Path) -> Result { + let argument = if cfg!(windows) { + "/install" + } else { + "--install" + }; + checked_installer(installer, [argument, package.to_string_lossy().as_ref()]) +} + +fn installer_remove(installer: &Path, id: &str) -> Result { + let argument = if cfg!(windows) { "/remove" } else { "--remove" }; + checked_installer(installer, [argument, id]) +} + +fn checked_installer<'a>( + installer: &Path, + arguments: impl IntoIterator, +) -> Result { + let output = Command::new(installer) + .args(arguments) + .output() + .map_err(|error| format!("Could not start Adobe's plugin installer: {error}"))?; + let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string(); + let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); + if output.status.success() { + Ok(stdout) + } else { + Err(format!( + "Adobe's plugin installer failed{}.", + if stderr.is_empty() { + String::new() + } else { + format!(": {stderr}") + } + )) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn chooses_non_overlapping_host_generations() { + assert_eq!(host_kind("23.2.0.69"), PremiereHostKind::Cep); + assert_eq!(host_kind("25.5.0"), PremiereHostKind::Cep); + assert_eq!(host_kind("25.6.0"), PremiereHostKind::Uxp); + assert_eq!(host_kind("26.3.0"), PremiereHostKind::Uxp); + assert_eq!(host_kind("22.6.0"), PremiereHostKind::Unsupported); + } +} diff --git a/desktop/src-tauri/tauri.conf.json b/desktop/src-tauri/tauri.conf.json index fe905f2d..94f7fd70 100644 --- a/desktop/src-tauri/tauri.conf.json +++ b/desktop/src-tauri/tauri.conf.json @@ -36,10 +36,12 @@ "targets": "all", "publisher": "Grayhat Developers PVT Ltd", "copyright": "Copyright © 2026 Grayhat Developers PVT Ltd", - "resources": [ - "../THIRD_PARTY_NOTICES.txt", - "../../LICENSE" - ], + "resources": { + "../THIRD_PARTY_NOTICES.txt": "THIRD_PARTY_NOTICES.txt", + "../../LICENSE": "LICENSE", + "../../premiere/packages/vidxp-premiere-cep.zxp": "premiere/vidxp-premiere-cep.zxp", + "../../premiere/packages/vidxp-premiere-uxp.ccx": "premiere/vidxp-premiere-uxp.ccx" + }, "externalBin": [ "binaries/uv" ], diff --git a/desktop/src/App.test.tsx b/desktop/src/App.test.tsx index 608c6785..bdc08ec8 100644 --- a/desktop/src/App.test.tsx +++ b/desktop/src/App.test.tsx @@ -8,12 +8,13 @@ const mocks = vi.hoisted(() => ({ targetSetupState: vi.fn(), recheckTargetState: vi.fn(), discoverLocalTargets: vi.fn(), chooseLocalExecutable: vi.fn(), inspectLocalTarget: vi.fn(), activateLocalTarget: vi.fn(), selectTargetProfile: vi.fn(), deleteTargetProfile: vi.fn(), confirmForgetTarget: vi.fn(), beginManagedSetup: vi.fn(), - cancelManagedSetup: vi.fn(), installMediaRuntime: vi.fn(), installRuntime: vi.fn(), + cancelManagedSetup: vi.fn(), cancelManagedSetupOperation: vi.fn(), installMediaRuntime: vi.fn(), installRuntime: vi.fn(), prepareManagedModels: vi.fn(), onManagedSetupProgress: vi.fn(), runtimeManifest: vi.fn(), runtimeStatus: vi.fn(), launchUi: vi.fn(), chooseModelDirectory: vi.fn(), modelDirectoryInventory: vi.fn(), targetDoctor: vi.fn(), mcpClientConfig: vi.fn(), installCodexPlugin: vi.fn(), localServerStatus: vi.fn(), localWorkerStatus: vi.fn(), browserServiceStatus: vi.fn(), startLocalServer: vi.fn(), startSharedServer: vi.fn(), stopLocalServer: vi.fn(), startSharedBrowser: vi.fn(), stopBrowserService: vi.fn(), startLocalWorker: vi.fn(), stopLocalWorker: vi.fn(), configureExternalInstallation: vi.fn(), + premiereIntegrationState: vi.fn(), installPremiereExtensions: vi.fn(), uninstallPremiereExtensions: vi.fn(), })); const windowMocks = vi.hoisted(() => ({ @@ -88,8 +89,9 @@ describe('desktop target lifecycle', () => { }); mocks.beginManagedSetup.mockResolvedValue({ id: 'draft-1', previous_profile_id: null }); mocks.cancelManagedSetup.mockResolvedValue(emptyState); + mocks.cancelManagedSetupOperation.mockResolvedValue(undefined); mocks.confirmForgetTarget.mockResolvedValue(true); - mocks.runtimeManifest.mockResolvedValue({ package_version: '0.4.0', capabilities: { scene: { extra: 'scene', label: 'Visual scene search' } }, surfaces: { + mocks.runtimeManifest.mockResolvedValue({ package_version: '0.4.0', managed_runtime_estimated_size_bytes: 3 * 1024 ** 3, capabilities: { scene: { extra: 'scene', label: 'Visual scene search', description: 'Index and search visual scenes.', models: [{ cache_key: 'scene', download_size_bytes: 1539458338 }] } }, surfaces: { worker: { extra: 'local-worker', label: 'Process videos on this computer', description: 'Run video work locally.', default: true }, browser: { extra: 'frontend', label: 'Browser interface', description: 'Open VidXP in your browser.', default: true }, mcp: { extra: 'mcp', label: 'AI assistant integration', description: 'Connect a compatible AI app.', default: false }, @@ -113,6 +115,13 @@ describe('desktop target lifecycle', () => { installed_path: 'C:\\Users\\test\\.codex\\plugins\\vidxp', detail: 'VidXP is installed in Codex with its MCP server and skills. Start a new Codex chat to use the updated plugin.', }); + mocks.premiereIntegrationState.mockResolvedValue({ + installations: [], platform_supported: false, installer_available: false, + cep_package_available: true, uxp_package_available: true, + cep_installed: false, uxp_installed: false, detail: 'Unavailable.', + }); + mocks.installPremiereExtensions.mockResolvedValue({ installed_hosts: [], opened_packages: [], detail: 'Installed.' }); + mocks.uninstallPremiereExtensions.mockResolvedValue(undefined); mocks.browserServiceStatus.mockResolvedValue({ state: 'stopped', running: false, shared: false, port: null, local_url: null, network_url: null, detail: 'Stopped.' }); mocks.startSharedBrowser.mockResolvedValue({ state: 'ready', running: true, shared: true, port: 8501, local_url: 'http://127.0.0.1:8501', network_url: 'http://192.168.1.20:8501', detail: 'Shared.' }); mocks.stopBrowserService.mockResolvedValue({ state: 'stopped', running: false, shared: false, port: null, local_url: null, network_url: null, detail: 'Stopped.' }); @@ -508,14 +517,91 @@ describe('desktop target lifecycle', () => { })); }); + it('installs Premiere with its processing and private-service dependencies in one setup flow', async () => { + mocks.premiereIntegrationState.mockResolvedValue({ + installations: [{ display_name: 'Adobe Premiere Pro 2023', version: '23.2.0.69', executable: 'C:\\Program Files\\Adobe\\Premiere.exe', host_kind: 'cep', compatible: true }], + platform_supported: true, installer_available: true, + cep_package_available: true, uxp_package_available: true, + cep_installed: false, uxp_installed: false, detail: 'Found Premiere.', + }); + const premiereProfile = { ...managedProfile, surfaces: ['worker', 'browser', 'server'] }; + mocks.installRuntime.mockResolvedValue({ + install: { package_version: '0.4.0', capabilities: ['scene'], surfaces: premiereProfile.surfaces, model_directory: 'C:\\Models', prepared: true }, + setup: { profiles: [premiereProfile], selected_profile_id: premiereProfile.id, issues: [] }, + }); + mocks.installPremiereExtensions.mockResolvedValue({ installed_hosts: ['cep'], opened_packages: [], detail: 'The VidXP extension was installed.' }); + const user = userEvent.setup(); renderApp(); await enterManaged(user); + + await user.click(screen.getByRole('checkbox', { name: /Premiere Pro extension/i })); + expect(screen.getByRole('checkbox', { name: /Process videos on this computer/i })).toBeChecked(); + expect(screen.getByRole('checkbox', { name: /App integration service/i })).toBeChecked(); + await user.click(screen.getByRole('button', { name: 'Install VidXP and Premiere' })); + + await waitFor(() => expect(mocks.installRuntime).toHaveBeenCalledWith(expect.objectContaining({ + surfaces: expect.arrayContaining(['worker', 'server']), + }))); + await waitFor(() => expect(mocks.startLocalServer).toHaveBeenCalledTimes(1)); + expect(mocks.installPremiereExtensions).toHaveBeenCalledTimes(1); + expect(await screen.findByText('VidXP and Premiere are ready')).toBeVisible(); + }); + + it('turns Set up Premiere into a preselected managed update instead of a prerequisite warning', async () => { + const state = { profiles: [managedProfile], selected_profile_id: managedProfile.id, issues: [] }; + mocks.targetSetupState.mockResolvedValue(state); + mocks.recheckTargetState.mockResolvedValue(state); + mocks.runtimeStatus.mockResolvedValue({ state: 'ready', ready: true, runtime_profile: 'runtime-a', package_version: '0.4.0', capabilities: ['scene'], surfaces: [], model_directory: 'C:\\Models', detail: 'Ready.' }); + mocks.premiereIntegrationState.mockResolvedValue({ + installations: [{ display_name: 'Adobe Premiere Pro 2023', version: '23.2.0.69', executable: 'C:\\Program Files\\Adobe\\Premiere.exe', host_kind: 'cep', compatible: true }], + platform_supported: true, installer_available: true, + cep_package_available: true, uxp_package_available: true, + cep_installed: false, uxp_installed: false, detail: 'Found Premiere.', + }); + const user = userEvent.setup(); renderApp(); + + await user.click(await screen.findByRole('button', { name: 'Set up Premiere' })); + + expect(await screen.findByRole('checkbox', { name: /Premiere Pro extension/i })).toBeChecked(); + expect(screen.getByRole('checkbox', { name: /Process videos on this computer/i })).toBeChecked(); + expect(screen.getByRole('checkbox', { name: /App integration service/i })).toBeChecked(); + expect(screen.getByRole('button', { name: 'Apply and install Premiere' })).toBeEnabled(); + }); + it('passes the scoped draft through first-time installation and does not auto-open the browser', async () => { const user = userEvent.setup(); renderApp(); await enterManaged(user); await user.click(await screen.findByRole('button', { name: 'Install VidXP' })); await waitFor(() => expect(mocks.installRuntime).toHaveBeenCalledWith(expect.objectContaining({ draft_id: 'draft-1' }))); - expect(mocks.installMediaRuntime).toHaveBeenCalledWith('draft-1'); + expect(mocks.installMediaRuntime).toHaveBeenCalledWith('draft-1', 8); expect(mocks.launchUi).not.toHaveBeenCalled(); }); + it('derives the storage plan from every selected capability model', async () => { + mocks.runtimeManifest.mockResolvedValue({ + package_version: '0.4.0', + managed_runtime_estimated_size_bytes: 3 * 1024 ** 3, + capabilities: { + scene: { + extra: 'scene', label: 'Visual scene search', description: 'Index and search visual scenes.', + models: [{ cache_key: 'scene-model', download_size_bytes: 1539458338 }], + }, + sound: { + extra: 'sound', label: 'Sound event search', description: 'Index and search sound events.', + models: [ + { cache_key: 'sound-model', download_size_bytes: 980404741 }, + { cache_key: 'sound-vocab', download_size_bytes: 1355622 }, + ], + }, + }, + surfaces: { + worker: { extra: 'local-worker', label: 'Process videos on this computer', description: 'Run video work locally.', default: true }, + }, + }); + const user = userEvent.setup(); renderApp(); await enterManaged(user); + + expect(screen.getByText(/Selected model downloads total up to 2.35 GiB/)).toBeVisible(); + expect(screen.getByText(/Visual scene search: 1.43 GiB · Sound event search: 936.3 MiB/)).toBeVisible(); + expect(screen.getByText(/Plan for approximately 5.35 GiB locally/)).toBeVisible(); + }); + it('keeps a managed installation failure visible in the setup dialog until it is acknowledged', async () => { mocks.installRuntime.mockRejectedValueOnce('The installed runtime failed its compatibility check.'); const user = userEvent.setup(); renderApp(); await enterManaged(user); @@ -544,6 +630,7 @@ describe('desktop target lifecycle', () => { expect(screen.getByRole('dialog', { name: 'Setting up VidXP' })).toBeVisible(); expect(screen.getByText('Step 1 of 8')).toBeVisible(); expect(screen.getByText('Checking FFmpeg and required video codecs')).toBeVisible(); + expect(screen.getByRole('button', { name: 'Cancel setup' })).toBeEnabled(); reportProgress?.({ draft_id: 'draft-1', current: 4, total: 8, stage: 'dependencies', message: 'Installing the selected search features' }); expect(await screen.findByText('Step 4 of 8')).toBeVisible(); expect(screen.getByText('Installing the selected search features')).toBeVisible(); @@ -573,6 +660,21 @@ describe('desktop target lifecycle', () => { media.resolve({ ready: true }); }); + it('cancels a running managed setup without discarding the draft', async () => { + const media = deferred<{ ready: boolean }>(); + mocks.installMediaRuntime.mockReturnValue(media.promise); + const user = userEvent.setup(); renderApp(); await enterManaged(user); + + await user.click(screen.getByRole('button', { name: 'Install VidXP' })); + await user.click(screen.getByRole('button', { name: 'Cancel setup' })); + + expect(mocks.cancelManagedSetupOperation).toHaveBeenCalledWith('draft-1'); + expect(await screen.findByText('Stopping setup safely')).toBeVisible(); + media.reject('Windows Package Manager FFmpeg installation failed: the operation was cancelled'); + expect(await screen.findByRole('dialog', { name: 'Setup could not finish' })).toBeVisible(); + expect(screen.getByRole('alert', { name: 'VidXP was not installed' })).toHaveTextContent('Setup was cancelled'); + }); + it('coalesces duplicate managed Continue actions', async () => { const pending = deferred<{ id: string; previous_profile_id: null }>(); mocks.beginManagedSetup.mockReturnValue(pending.promise); diff --git a/desktop/src/App.tsx b/desktop/src/App.tsx index 578b4bb4..f7e533c6 100644 --- a/desktop/src/App.tsx +++ b/desktop/src/App.tsx @@ -27,12 +27,20 @@ import { useExclusiveOperation } from './useAsyncAction'; type Stage = 'loading' | 'choice' | 'local' | 'managed-confirm' | 'managed' | 'summary'; type AppOperation = 'startup-check' | 'recheck' | 'begin-managed' | 'cancel-managed' | 'select-profile' | 'forget-profile' | 'open-browser'; +interface CompletionNotice { + color: 'teal' | 'yellow'; + title: string; + detail: string; +} + interface LifecycleState { stage: Stage; choice: TargetKind | null; setup: TargetSetupState | null; draft: ManagedSetupDraft | null; failure: string | null; + completionNotice: CompletionNotice | null; + premiereSetupRequested: boolean; operation: AppOperation | null; operationProfile: string | null; } @@ -44,10 +52,11 @@ type Action = | { type: 'loadFailed'; failure: string } | { type: 'operationStarted'; operation: AppOperation; profileId?: string } | { type: 'operationFailed'; failure: string } - | { type: 'operationSettled'; setup?: TargetSetupState; stage?: Stage; draft?: ManagedSetupDraft | null }; + | { type: 'operationSettled'; setup?: TargetSetupState; stage?: Stage; draft?: ManagedSetupDraft | null; completionNotice?: CompletionNotice | null; premiereSetupRequested?: boolean }; const initialState: LifecycleState = { stage: 'loading', choice: null, setup: null, draft: null, failure: null, + completionNotice: null, premiereSetupRequested: false, operation: null, operationProfile: null, }; @@ -71,6 +80,8 @@ function reducer(state: LifecycleState, action: Action): LifecycleState { setup: action.setup ?? state.setup, stage: action.stage ?? state.stage, draft: action.draft === undefined ? state.draft : action.draft, + completionNotice: action.completionNotice === undefined ? state.completionNotice : action.completionNotice, + premiereSetupRequested: action.premiereSetupRequested ?? state.premiereSetupRequested, operation: null, operationProfile: null, failure: null, @@ -131,12 +142,12 @@ export function App() { }; }, [recheck]); - async function beginManaged() { + async function beginManaged(premiereSetupRequested = false) { const current = startOperation('begin-managed'); if (current === null) return; try { const draft = await beginManagedSetup(); - settleOperation(current, { type: 'operationSettled', draft, stage: 'managed' }); + settleOperation(current, { type: 'operationSettled', draft, stage: 'managed', premiereSetupRequested }); } catch (error) { settleOperation(current, { type: 'operationFailed', @@ -154,6 +165,7 @@ export function App() { settleOperation(current, { type: 'operationSettled', setup, draft: null, stage: selectedProfile(setup) ? 'summary' : 'choice', + premiereSetupRequested: false, }); } catch (error) { settleOperation(current, { @@ -213,6 +225,7 @@ export function App() {

+ void openExternalSetup()} + /> +
Health and background services Check whether VidXP is usable and control only the services you enabled. diff --git a/desktop/src/desktopPackagingContracts.test.ts b/desktop/src/desktopPackagingContracts.test.ts index cdbb9b76..70dde383 100644 --- a/desktop/src/desktopPackagingContracts.test.ts +++ b/desktop/src/desktopPackagingContracts.test.ts @@ -13,10 +13,12 @@ function read(path: string) { describe('Desktop packaging and documentation contracts', () => { it('packages the project license and complete third-party notices', () => { const config = JSON.parse(read('desktop/src-tauri/tauri.conf.json')); - expect(config.bundle.resources).toEqual(expect.arrayContaining([ - '../THIRD_PARTY_NOTICES.txt', - '../../LICENSE', - ])); + expect(config.bundle.resources).toMatchObject({ + '../THIRD_PARTY_NOTICES.txt': 'THIRD_PARTY_NOTICES.txt', + '../../LICENSE': 'LICENSE', + '../../premiere/packages/vidxp-premiere-cep.zxp': 'premiere/vidxp-premiere-cep.zxp', + '../../premiere/packages/vidxp-premiere-uxp.ccx': 'premiere/vidxp-premiere-uxp.ccx', + }); const notices = read('desktop/THIRD_PARTY_NOTICES.txt'); expect(notices).toContain('VIDXP PROJECT LICENSE'); diff --git a/desktop/src/tauri.test.ts b/desktop/src/tauri.test.ts index 658dbd6c..422388c3 100644 --- a/desktop/src/tauri.test.ts +++ b/desktop/src/tauri.test.ts @@ -7,6 +7,7 @@ vi.mock('@tauri-apps/api/event', () => ({ listen })); import { beginManagedSetup, + cancelManagedSetupOperation, displayPath, installRuntime, installCodexPlugin, @@ -90,6 +91,14 @@ describe('desktop IPC adapter', () => { expect(invoke).toHaveBeenNthCalledWith(2, 'install_runtime', { request }); }); + it('cancels only the active managed setup draft', async () => { + invoke.mockResolvedValue(undefined); + + await cancelManagedSetupOperation('draft-1'); + + expect(invoke).toHaveBeenCalledWith('cancel_managed_setup_operation', { draftId: 'draft-1' }); + }); + it('maps managed setup progress events to their payload', async () => { const stop = vi.fn(); listen.mockResolvedValue(stop); diff --git a/desktop/src/tauri.ts b/desktop/src/tauri.ts index 78df41ee..591a231a 100644 --- a/desktop/src/tauri.ts +++ b/desktop/src/tauri.ts @@ -134,7 +134,8 @@ export interface LocalTargetInspection extends Omit; surfaces: Record; } @@ -264,6 +266,33 @@ export interface CodexPluginInstallResult { detail: string; } +export type PremiereHostKind = 'cep' | 'uxp' | 'unsupported'; + +export interface PremiereInstallation { + display_name: string; + version: string; + executable: string; + host_kind: PremiereHostKind; + compatible: boolean; +} + +export interface PremiereIntegrationState { + installations: PremiereInstallation[]; + platform_supported: boolean; + installer_available: boolean; + cep_package_available: boolean; + uxp_package_available: boolean; + cep_installed: boolean; + uxp_installed: boolean; + detail: string; +} + +export interface PremiereInstallResult { + installed_hosts: PremiereHostKind[]; + opened_packages: string[]; + detail: string; +} + interface WireInstallTransitionResult { install: InstallRuntimeResult; setup: WireTargetState; @@ -355,14 +384,18 @@ export function cancelManagedSetup(draftId: string): Promise { return invoke('cancel_managed_setup', { draftId }).then(normalizeState); } +export function cancelManagedSetupOperation(draftId: string): Promise { + return invoke('cancel_managed_setup_operation', { draftId }); +} + export function runtimeManifest(): Promise { return invoke('runtime_manifest'); } export function runtimeStatus(): Promise { return invoke('runtime_status'); } export function modelDirectoryInventory(directory?: string): Promise { return invoke('model_directory_inventory', { directory: directory || null }); } export function chooseModelDirectory(): Promise { return invoke('choose_model_directory'); } -export function installMediaRuntime(draftId: string): Promise { - return invoke('install_media_runtime', { draftId }); +export function installMediaRuntime(draftId: string, totalSteps: number): Promise { + return invoke('install_media_runtime', { draftId, totalSteps }); } export function installRuntime(request: InstallRuntimeRequest): Promise { return invoke('install_runtime', { request }).then((result) => ({ @@ -385,6 +418,9 @@ export function configureExternalInstallation(capabilities: string[], surfaces: } export function mcpClientConfig(): Promise { return invoke('mcp_client_config'); } export function installCodexPlugin(): Promise { return invoke('install_codex_plugin'); } +export function premiereIntegrationState(): Promise { return invoke('premiere_integration_state'); } +export function installPremiereExtensions(): Promise { return invoke('install_premiere_extensions'); } +export function uninstallPremiereExtensions(): Promise { return invoke('uninstall_premiere_extensions'); } export function localWorkerStatus(): Promise { return invoke('local_worker_status'); } export function startLocalWorker(): Promise { return invoke('start_local_worker'); } export function stopLocalWorker(): Promise { return invoke('stop_local_worker'); } diff --git a/docs/adding-a-capability.md b/docs/adding-a-capability.md index a2bdf47c..fd541dcf 100644 --- a/docs/adding-a-capability.md +++ b/docs/adding-a-capability.md @@ -47,6 +47,7 @@ Start in `definition.py`. It must export one frozen, Pydantic-validated Every capability declares: - a stable internal `name`; +- a product-facing `label`; - a short human-readable `description`; - the package `extra` that installs its dependencies; - its Pydantic configuration model; and @@ -138,7 +139,8 @@ meaning may make existing indexes invalid. When it does, require a rebuild and keep the previous index active until the replacement passes validation. State the rebuild requirement in both the release note and user documentation. -If a model contract is displayed by Desktop, regenerate the checked-in catalog: +Regenerate the checked-in Desktop catalogs after changing a capability label, +description, package extra, or model contract: ```bash npm --prefix desktop run model-catalog:write @@ -152,7 +154,13 @@ Review the generated diff rather than editing the catalog by hand. After the definition and executor are ready, connect them with a `CapabilityPlugin` named `PLUGIN`. Register that plugin explicitly in `src/vidxp/capabilities/registry.py`. For an ordinary capability, the registry -is the only central runtime file that should change. +is the only central Python runtime file that should change. + +Desktop derives the capability's package extra, modality, product label, +description, and model download plan from the generated capability catalog. +Do not add the capability to `desktop/runtime-manifest.json` or a UI label map. +Add a Desktop test that verifies the generated manifest produces the expected +package specification for the new capability. Generic commands discover capability names and operations from the registry. Most capabilities therefore need no CLI code. Add `cli.py` only when the diff --git a/docs/desktop.md b/docs/desktop.md index 15b7c299..d09af8eb 100644 --- a/docs/desktop.md +++ b/docs/desktop.md @@ -54,7 +54,8 @@ The main Desktop files are: | `desktop/src-tauri/src/` | Tauri commands, setup lifecycle, activation, and process supervision | | `desktop/runtime-manifest.json` | Pinned Python and VidXP runtime versions | | `desktop/sidecars.json` | Pinned `uv` sidecar versions and archive checksums | -| `desktop/model-cache-catalog.json` | Generated catalog of model downloads shown during setup | +| `desktop/capability-catalog.json` | Generated capability labels, installation extras, and model download plans | +| `desktop/model-cache-catalog.json` | Generated model-cache recognition catalog | | `desktop/scripts/` | Sidecar, model-catalog, notice, branding, and package scripts | | `desktop/THIRD_PARTY_NOTICES.txt` | Generated notices shipped with the installers | @@ -126,11 +127,13 @@ Use `npm --prefix desktop run sidecar:windows` instead of `sidecar:unix` on Windows. Report the exact commands you ran and any platform package you could not build or inspect. -Two checked-in files must stay synchronized with their source contracts: +Three checked-in files must stay synchronized with their source contracts: -- After changing capability or model contracts, run +- After changing capability labels, descriptions, extras, or model contracts, + run `npm --prefix desktop run model-catalog:write`, review the diff, and run the - corresponding `:check` command. + corresponding `:check` command. This updates both Desktop catalogs from the + canonical capability registry. - After changing a production dependency or license, run `npm --prefix desktop run notices:write`, review the inventory, and run the corresponding `:check` command. @@ -189,6 +192,12 @@ The user-facing feature choices map to package extras as follows: These package names are implementation details and should not replace the product labels in the interface. +The capability registry owns capability labels, descriptions, installation +extras, and model specifications. Desktop embeds a generated capability +catalog because it must show setup choices before a VidXP runtime exists. The +build merges that generated catalog with Desktop-owned surface and runtime +metadata; React must not maintain a parallel capability list or storage table. + The installer does not bundle FFmpeg. If it is missing, setup may offer the supported WinGet command on Windows or Homebrew command on macOS, but it must wait for user confirmation before running either one. On Linux, setup shows an @@ -215,8 +224,18 @@ VidXP currently publishes: | macOS Apple Silicon | DMG | | Linux x86-64 | AppImage | -The release workflow builds all three packages from the stamped release commit -and attaches them to one GitHub release. +The release workflow builds all three Desktop packages from the stamped +release commit and attaches them to one GitHub release. Before those builds it +also creates the signed CEP `.zxp` and UXP `.ccx` Premiere packages. Every +Desktop installer embeds both packages as resources, so installation happens +on the user's computer without source code or build tooling. + +Desktop detects standard Premiere installations, assigns versions 23.0–25.5 +to CEP and 25.6 or newer to UXP, and calls Adobe's Unified Plugin Installer +Agent. If Adobe requires an interactive confirmation, Desktop opens the +already-built package with Creative Cloud. The package host ranges are +non-overlapping, so both may remain installed on a workstation with multiple +Premiere generations. Beta and stable macOS DMGs are signed with a Developer ID certificate and the hardened runtime. The workflow then notarizes, staples, and verifies each DMG diff --git a/docs/integrations/premiere-pro.md b/docs/integrations/premiere-pro.md new file mode 100644 index 00000000..4acb48a1 --- /dev/null +++ b/docs/integrations/premiere-pro.md @@ -0,0 +1,106 @@ +# Search Premiere Pro media with VidXP + +The VidXP Premiere Pro extension lets an editor select clips or bins from the +open project, index their existing source files, and search the resulting +library without leaving Premiere. It discovers dialogue, sound, scene, actor, +and future search features from the connected VidXP runtime instead of keeping +a fixed capability list in the extension. + +## Install the extension + +Install VidXP Desktop from the official +[GitHub release](https://github.com/grayhatdevelopers/vidxp/releases). No Git +checkout, Node.js installation, local build, Adobe developer mode, or Premiere +upgrade is required for Premiere Pro 23.2. + +1. In Desktop setup, select **Premiere Pro extension** and the search features + you want. Desktop automatically includes local video processing and its + private app connection. +2. Complete an Adobe Creative Cloud confirmation window if one appears. +3. Restart Premiere Pro. + +For an existing Desktop-managed setup, select **Set up Premiere** on its summary +screen. The Premiere requirements are preselected and Desktop installs the Adobe +package after updating VidXP. Use **Install for Premiere** only to reinstall or +retry the Adobe package without changing VidXP features. + +Desktop ships both Adobe extension packages and chooses from the installed +Premiere versions: + +| Premiere version | Extension | Open the panel from | +|---|---|---| +| 23.0–25.5 | CEP 11 | **Window > Extensions (Legacy) > VidXP Search** | +| 25.6 or newer | UXP | **Window > UXP Plugins > VidXP Search** | + +The package ranges do not overlap. A workstation with an older and a current +Premiere installation can keep both packages installed without duplicate +panels in either host. + +Desktop uses Adobe Creative Cloud's official Unified Plugin Installer Agent. +If the background installer is unavailable or needs user interaction, Desktop +opens the bundled `.zxp` or `.ccx` package so Creative Cloud can finish the +installation. It never builds extension code on the user's computer. + +## Connect VidXP + +The extension and VidXP must run on the same computer because Premiere gives +the panel paths to media already present in the project. VidXP indexes those +source files in place; it does not upload or duplicate them. + +Premiere setup starts the private app service. Local video processing starts +when VidXP needs it; if you stop either service later, start it again from the +Desktop summary. Copy the displayed API address into the Premiere panel and +connect. + +Desktop can choose an available local port, so use its displayed address +instead of assuming the default `http://127.0.0.1:32191`. + +## Index Premiere media + +1. Open a Premiere project and connect the panel. +2. Search the project tree or choose **Use current selection** to mirror the + Project panel selection. +3. Select clips or bins. A Premiere bin expands to its file-backed descendants; + it is not treated as a filesystem directory. +4. Choose any indexing features reported by VidXP and start indexing. +5. Keep the panel open while it reports batch progress. A dismissible notice + reports completion and identifies individual failures. + +Offline clips, sequences, generated items without a media path, and duplicate +source paths are not submitted. Large selections are split into durable VidXP +ingestion sessions. + +## Search indexed moments + +Enter a description, choose one indexed video or the complete active library, +and select any searchable features reported by VidXP. Results show the source +video, time range, contributing features, and fused score. + +Timeline navigation, Source Monitor actions, marker creation, and snippet +insertion remain future host-adapter operations. They can be added without +changing the VidXP client or shared search workflow. + +## Current release limits + +- Windows is the first supported packaging target. Premiere Pro 23.2 must pass + the CEP host checklist before the release is promoted beyond preview. +- Adobe blocks ordinary `http://` URLs in Premiere UXP on macOS. The 25.6+ + macOS panel needs a trusted loopback HTTPS transport before it is supported. +- CEP on macOS uses its native Node transport, but installation and media-path + behavior still require host validation. +- Proxy, subclip, Productions, UNC, mounted-volume, offline-media, and React 19 + control behavior remain explicit manual release gates. +- Completion appears inside the panel because neither host generation exposes + a dependable native Premiere notification API for this workflow. + +Contributors should read the [extension architecture](../../premiere/README.md) +and run the [manual host checklist](../../premiere/docs/MANUAL_TEST_CHECKLIST.md). + +## Adobe references + +- [Premiere UXP introduction](https://developer.adobe.com/premiere-pro/uxp/introduction/) +- [Package a UXP plugin](https://developer.adobe.com/premiere-pro/uxp/plugins/distribution/package/) +- [Install a UXP plugin](https://developer.adobe.com/premiere-pro/uxp/plugins/distribution/install/) +- [Premiere UXP network operations](https://developer.adobe.com/premiere-pro/uxp/resources/recipes/network/) +- [Adobe CEP PProPanel sample](https://github.com/Adobe-CEP/Samples/tree/master/PProPanel) +- [Adobe CEP 11 cookbook](https://github.com/Adobe-CEP/CEP-Resources/blob/master/CEP_11.x/Documentation/CEP%2011.1%20HTML%20Extension%20Cookbook.md) diff --git a/docs/local-api.md b/docs/local-api.md index 9c0a26cc..b48aed5f 100644 --- a/docs/local-api.md +++ b/docs/local-api.md @@ -52,6 +52,24 @@ If port `32191` is already in use, choose another one: vidxp-api --port 32192 ``` +### Ingest media already available on this computer + +A local application can register media without copying the video through an +HTTP request. Send one to ten absolute file paths to +`POST /api/v1/media/local-ingestions`, then poll the URL from its `Location` +header until the session is terminal. Set `modalities` to the indexable names +returned by `GET /api/v1/capabilities`; omit it to use every enabled indexing +feature. + +This operation is available only when `vidxp-api` runs in local mode. Every +path must be readable by the VidXP process and, when trusted import roots are +configured, must be inside one of those roots. The response never returns a +source path. Use the upload operations instead when the application and VidXP +do not share a filesystem. + +The [Premiere Pro extension preview](integrations/premiere-pro.md) uses this +workflow for media already loaded in a Premiere project. + ## Connect a local AI assistant An assistant that can start a program on the same computer does not need the diff --git a/premiere/README.md b/premiere/README.md new file mode 100644 index 00000000..c782d80c --- /dev/null +++ b/premiere/README.md @@ -0,0 +1,102 @@ +# VidXP Premiere Pro extension + +This directory builds the VidXP panel for both Premiere extension generations. +The local Uplift repository was used only as a UXP packaging and host-boundary +reference; this is not an Uplift integration. + +## Architecture + +```text +Shared React workflow (`src/ui`) +├── typed VidXP client (`src/services/vidxp`) +├── shared media-library rules (`src/premiere/library.ts`) +├── Bolt UXP build + Premiere adapter (`uxp.config.ts`, `index.tsx`, `adapter.ts`) +└── CEP bootstrap + Premiere adapter (`cep/`, `cep-adapter.ts`) + ├── ES3 ExtendScript project bridge (`cep/jsx/host.jsx`) + └── CEP Node loopback transport (`cep-fetch.ts`) +``` + +The UI receives a `PremiereAdapter`; it does not import a host API. The UXP +adapter alone imports Premiere's `premierepro` module. The CEP adapter alone +uses `evalScript`, and its ExtendScript bridge returns bounded JSON. Indexing, +polling, search, status, capability discovery, and selection rules are shared. + +UXP renders Adobe's built-in Spectrum widgets through the typed control +wrapper. CEP renders native HTML controls through that same wrapper because +Spectrum UXP widgets do not exist in CEP. React remains at version 19 for both +builds, with CEP compiled for Chromium 88. + +Capability names and roles come from `GET /api/v1/capabilities`; neither host +adapter hardcodes dialogue, sound, scene, action, or future capability names. + +## Build and package + +```bash +npm ci +npm run check +npm run package +``` + +The UXP target follows Bolt UXP's React scaffold: Vite owns the HTML entry, +`@vitejs/plugin-react` compiles React, and +[Bolt UXP](https://github.com/hyperbrew/bolt-uxp)'s `vite-uxp-plugin` owns +manifest generation, UXP-compatible transforms and polyfills, hot reload, CCX +creation, and package installation actions. Bolt's Premiere-specific host color +variables are initialized before React mounts and update when the host theme +changes. The separate CEP target shares the application code but retains its +own Vite and ZXP signing path because Bolt UXP does not build CEP extensions. + +`npm run check` creates the UXP development bundle under `dist/` and the CEP +bundle under `dist/cep`. +`npm run package` additionally creates these ignored release inputs: + +- `packages/vidxp-premiere-cep.zxp`, signed and timestamped for Premiere + 23.0–25.5; +- `packages/vidxp-premiere-uxp.ccx` for Premiere 25.6 or newer. + +The Desktop release workflow performs this packaging once and embeds both +artifacts in every Desktop installer. End users do not run these commands. + +For UXP-only development, add `dist/manifest.json` to UXP Developer Tool or use +the checked-in VS Code attach configuration. Run `npm run dev` for Bolt UXP hot +reload. After `npm run package:uxp`, you can install or remove that development +CCX with Bolt's actions: + +```bash +npm run ccx-install +npm run ccx-uninstall +``` + +CEP development uses an unsigned `dist/cep` build and therefore requires the +CEP debugging setup documented by Adobe. Neither developer path is an end-user +installation method. + +Automated tests cover the shared VidXP client, library rules, React build, both +package builds, Desktop resource contract, and Desktop version routing. They do +not exercise Premiere, Creative Cloud, real media paths, a running VidXP +service, or model inference. Run the +[manual checklist](docs/MANUAL_TEST_CHECKLIST.md) for those boundaries. + +## POC decisions + +- Premiere file paths are passed to VidXP's local ingestion endpoint; source + video bytes are not copied into extension storage. +- Bolt's optional webview, hybrid-plugin, and multi-host modes are disabled. + The panel has one Premiere UXP context, and Bolt's hybrid helper targets + Photoshop and InDesign rather than Premiere. +- `bolt-uxp-utils` is not used while the UXP package supports Premiere 25.6. + The utility package requires Premiere 26.3 or newer, so the typed adapter + calls Premiere's host API directly until the minimum supported version moves. +- CEP uses its Node HTTP client for loopback requests instead of broadening the + API's browser CORS policy. +- Desktop owns runtime setup, service startup, extension detection, + installation, removal, and user-visible installation status. +- Package host ranges do not overlap: CEP is 23.0–25.5 and UXP begins at 25.6. +- Bins expand recursively and source paths are deduplicated before submission. +- In-panel progress and completion notices are the primary workflow status. +- Timeline navigation and snippet insertion remain future adapter methods. +- Windows is the first release-validation target. Premiere UXP blocks plain + loopback HTTP on macOS, so 25.6+ macOS support needs trusted local HTTPS. + +The user setup is in +[`docs/integrations/premiere-pro.md`](../docs/integrations/premiere-pro.md). diff --git a/premiere/cep/CSXS/manifest.xml b/premiere/cep/CSXS/manifest.xml new file mode 100644 index 00000000..0816fe95 --- /dev/null +++ b/premiere/cep/CSXS/manifest.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + ./index.html + ./jsx/host.jsx + + --enable-nodejs + --mixed-context + + + + true + + + Panel + VidXP Search + + + 380 + 720 + + + 320 + 480 + + + 1800 + 1800 + + + + + + + diff --git a/premiere/cep/index.html b/premiere/cep/index.html new file mode 100644 index 00000000..fee98124 --- /dev/null +++ b/premiere/cep/index.html @@ -0,0 +1,13 @@ + + + + + + VidXP Search + + + + +
+ + diff --git a/premiere/cep/index.tsx b/premiere/cep/index.tsx new file mode 100644 index 00000000..7b0e58ca --- /dev/null +++ b/premiere/cep/index.tsx @@ -0,0 +1,19 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; + +import { createCepPremiereAdapter } from "../src/premiere/cep-adapter"; +import { createCepFetch } from "../src/services/vidxp/cep-fetch"; +import { App } from "../src/ui/App"; +import { installPremiereTheme } from "../src/ui/theme"; + +Reflect.set(window, "__VIDXP_CEP__", true); +installPremiereTheme(document, process.platform); + +const rootElement = document.getElementById("root"); +if (!rootElement) throw new Error("The VidXP panel root element was not found."); + +createRoot(rootElement).render( + + + , +); diff --git a/premiere/cep/jsx/host.jsx b/premiere/cep/jsx/host.jsx new file mode 100644 index 00000000..963d539e --- /dev/null +++ b/premiere/cep/jsx/host.jsx @@ -0,0 +1,121 @@ +/* VidXP's Premiere 23.x bridge. Keep this file ES3-compatible for ExtendScript. */ +$._VIDXP = { + stringify: function (value) { + if (value === null) return "null"; + var type = typeof value; + if (type === "string") { + return '"' + value + .replace(/\\/g, "\\\\") + .replace(/\"/g, '\\"') + .replace(/\r/g, "\\r") + .replace(/\n/g, "\\n") + .replace(/\t/g, "\\t") + '"'; + } + if (type === "number" || type === "boolean") return String(value); + var entries = []; + var index; + if (value instanceof Array) { + for (index = 0; index < value.length; index += 1) { + entries.push($._VIDXP.stringify(value[index])); + } + return "[" + entries.join(",") + "]"; + } + for (var key in value) { + if (value.hasOwnProperty(key)) { + entries.push($._VIDXP.stringify(key) + ":" + $._VIDXP.stringify(value[key])); + } + } + return "{" + entries.join(",") + "}"; + }, + + result: function (operation) { + try { + return $._VIDXP.stringify({ ok: true, value: operation() }); + } catch (error) { + return $._VIDXP.stringify({ + ok: false, + error: error && error.message ? error.message : String(error) + }); + } + }, + + readItem: function (item) { + if (item.type === ProjectItemType.BIN || item.type === ProjectItemType.ROOT) { + var children = []; + for (var childIndex = 0; childIndex < item.children.numItems; childIndex += 1) { + var child = $._VIDXP.readItem(item.children[childIndex]); + if (child !== null) children.push(child); + } + return { + kind: "bin", + id: item.nodeId, + name: item.name, + children: children + }; + } + if (item.type !== ProjectItemType.CLIP && item.type !== ProjectItemType.FILE) return null; + if (item.isSequence()) return null; + if (item.isOffline()) { + return { + kind: "clip", + id: item.nodeId, + name: item.name, + availability: "offline", + detail: "Media is offline in Premiere." + }; + } + var mediaPath = item.getMediaPath(); + if (!mediaPath) { + return { + kind: "clip", + id: item.nodeId, + name: item.name, + availability: "unavailable", + detail: "Premiere did not return a file-backed media path." + }; + } + return { + kind: "clip", + id: item.nodeId, + name: item.name, + nativePath: mediaPath, + availability: "ready" + }; + }, + + getLibrary: function () { + return $._VIDXP.result(function () { + if (!app.project) return null; + var root = app.project.rootItem; + var items = []; + for (var index = 0; index < root.children.numItems; index += 1) { + var node = $._VIDXP.readItem(root.children[index]); + if (node !== null) items.push(node); + } + return { + projectName: app.project.name, + sequenceName: app.project.activeSequence ? app.project.activeSequence.name : null, + items: items + }; + }); + }, + + getSelection: function () { + return $._VIDXP.result(function () { + var ids = []; + var seen = {}; + var viewIds = app.getProjectViewIDs(); + for (var viewIndex = 0; viewIndex < viewIds.length; viewIndex += 1) { + var selected = app.getProjectViewSelection(viewIds[viewIndex]); + for (var itemIndex = 0; itemIndex < selected.length; itemIndex += 1) { + var id = selected[itemIndex].nodeId; + if (!seen[id]) { + seen[id] = true; + ids.push(id); + } + } + } + return ids; + }); + } +}; diff --git a/premiere/docs/MANUAL_TEST_CHECKLIST.md b/premiere/docs/MANUAL_TEST_CHECKLIST.md new file mode 100644 index 00000000..c4d7de6b --- /dev/null +++ b/premiere/docs/MANUAL_TEST_CHECKLIST.md @@ -0,0 +1,92 @@ +# Premiere Pro manual test checklist + +Automated tests do not exercise Premiere, CEP, UXP, Creative Cloud, media +paths, a real VidXP process, or model inference. Complete this checklist on a +clean Windows test machine with Premiere Pro 23.2 and Premiere Pro 25.6 or +newer before claiming host support. + +## Package installation + +- Install a release VidXP Desktop build; do not clone the repository or enable + Adobe developer modes for this test. +- Confirm Desktop detects Premiere 23.2 as CEP and Premiere 25.6+ as UXP. +- Select **Install for Premiere** and confirm Adobe's installer accepts the + signed `.zxp` and `.ccx` packages. +- Restart both Premiere versions. Confirm 23.2 shows one panel under **Window > + Extensions (Legacy)** and 25.6+ shows one panel under **Window > UXP + Plugins**. +- Refresh Desktop and confirm both installed statuses are reported. +- Remove both extensions from Desktop, restart Premiere, and confirm both + panels are absent. Reinstall before continuing. + +## Load and layout + +- Run every remaining item once in Premiere 23.2 and once in Premiere 25.6+. +- Dock, float, resize, close, and reopen the panel in supported Premiere themes. +- Confirm UXP's built-in Spectrum controls and CEP's native controls render + correctly and consistently with the active Premiere theme. +- Confirm Tab, Space, and Enter work as expected and focus remains visible. +- Confirm controlled values do not reset and actions fire only once after UDT + reloads and React Strict Mode remounts. +- Confirm the native bearer-token password input and media-scope selector remain + usable on Windows and macOS. +- Confirm scrolling, disclosure details, and the 320-pixel minimum layout remain + usable without CSS Grid. + +## Premiere media discovery + +- Open a project containing nested bins, online clips, offline clips, a + sequence, a subclip, a proxy, and duplicate project items for one source. +- Confirm refresh shows the active project and sequence. +- Confirm nested bins and file-backed media appear without sequences. +- Confirm offline and pathless media cannot be selected. +- Select clips and bins in the panel and confirm the count is deduplicated. +- Select clips and bins in Premiere's Project panel, choose + **Use current selection**, and confirm the same media becomes selected. +- Repeat with a Windows UNC path and, on macOS, a mounted-volume path. + +## VidXP connection + +- In Desktop, start local processing and the app integration service locally. +- Copy Desktop's dynamic API address into the extension and connect. +- Confirm CEP connects through its native loopback transport without adding a + `null` browser origin to VidXP's CORS allowlist. +- Confirm the panel shows the capabilities and indexed media reported by that + runtime, including any capability added after this extension was built. +- Stop the service and confirm the panel reports a safe, actionable error. +- Try a wrong bearer token against shared mode and confirm the token is not + shown in the error or console. + +## Ingestion and status + +- Index one short real video and confirm registration, real model inference, + active-snapshot update, and the completion notice. +- Confirm the indexing action disables while work is active and enables again + after success or failure. +- Index more than ten selected clips and confirm the panel progresses through + multiple batches without duplicate media paths. +- Include one missing, unsupported, or disallowed source and confirm the other + files can finish while the failed item is identified. +- Confirm Premiere remains responsive and the panel reports meaningful progress + during a long index. +- Close and reopen the panel during a job. Record the current POC behavior; job + recovery across panel reload is not yet implemented. + +## Search + +- Search all indexed media with each searchable capability individually and in + combination. +- Search one selected VidXP media item and confirm every result belongs to it. +- Confirm each result shows the correct source name, time range, capability + labels, rank, and score. +- Search for no-match text, stop the worker, and use an unprepared capability; + confirm empty, unavailable, and model-remediation states are readable. + +## Safety and cleanup + +- Confirm no source video is copied into `premiere/`, CEP or UXP plugin data, + or host temporary storage. +- Confirm no API token, source path, media file, index, build output, or local + UXP Developer Tool setting appears in `git status`. +- Stop services started for the test and remove both packages from Desktop when + finished. diff --git a/premiere/eslint.config.mjs b/premiere/eslint.config.mjs new file mode 100644 index 00000000..cdf3439b --- /dev/null +++ b/premiere/eslint.config.mjs @@ -0,0 +1,30 @@ +import eslint from "@eslint/js"; +import premierepro from "@adobe/eslint-plugin-premierepro"; +import reactHooks from "eslint-plugin-react-hooks"; +import { defineConfig, globalIgnores } from "eslint/config"; +import typescript from "typescript-eslint"; + +export default defineConfig( + globalIgnores(["dist/**", "ccx/**", "coverage/**"]), + { + files: ["index.tsx", "*.config.ts", "src/**/*.{ts,tsx}", "tests/**/*.ts"], + extends: [ + eslint.configs.recommended, + ...typescript.configs.recommendedTypeChecked, + premierepro.configs.recommendedTypeChecked, + reactHooks.configs.flat.recommended, + ], + languageOptions: { + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + }, + { + files: ["tests/**/*.ts"], + rules: { + "@typescript-eslint/no-explicit-any": "off", + }, + }, +); diff --git a/premiere/index.html b/premiere/index.html new file mode 100644 index 00000000..f75beeb9 --- /dev/null +++ b/premiere/index.html @@ -0,0 +1,12 @@ + + + + + + VidXP Search + + +
+ + + diff --git a/premiere/index.tsx b/premiere/index.tsx new file mode 100644 index 00000000..ff447f60 --- /dev/null +++ b/premiere/index.tsx @@ -0,0 +1,42 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; + +import "./src/ui/styles.css"; +import { App } from "./src/ui/App"; +import { createPremiereAdapter } from "./src/premiere/adapter"; +import { installPremiereTheme } from "./src/ui/theme"; + +// UXP supplies this module inside Premiere at runtime. +// eslint-disable-next-line @typescript-eslint/no-require-imports +const { entrypoints } = require("uxp") as typeof import("uxp"); +// eslint-disable-next-line @typescript-eslint/no-require-imports +const os = require("os") as typeof import("os"); + +installPremiereTheme(document, os.platform()); + +const rootElement = document.getElementById("root"); + +if (!rootElement) { + throw new Error("The VidXP panel root element was not found."); +} + +createRoot(rootElement).render( + + + , +); + +entrypoints.setup({ + panels: { + // @ts-expect-error Adobe currently declares panels as an array even though + // runtime panel entrypoints are keyed by their manifest ID. + vidxpSearch: { + show() { + // React owns the panel DOM for the plugin context lifetime. + }, + hide() { + // Correctness does not depend on this host lifecycle hook. + }, + }, + }, +}); diff --git a/premiere/package-lock.json b/premiere/package-lock.json new file mode 100644 index 00000000..2d73ca56 --- /dev/null +++ b/premiere/package-lock.json @@ -0,0 +1,6764 @@ +{ + "name": "vidxp-premiere-extension", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vidxp-premiere-extension", + "version": "0.1.0", + "dependencies": { + "react": "19.2.8", + "react-dom": "19.2.8" + }, + "devDependencies": { + "@adobe/cc-ext-uxp-types": "7.3.1", + "@adobe/eslint-plugin-premierepro": "26.3.0", + "@adobe/premierepro": "26.3.0", + "@eslint/js": "9.39.2", + "@types/node": "24.13.3", + "@types/react": "19.2.18", + "@types/react-dom": "19.2.5", + "@vitejs/plugin-react": "6.1.1", + "concurrently": "10.0.5", + "cross-env": "10.1.0", + "eslint": "9.39.2", + "eslint-plugin-react-hooks": "7.0.1", + "rimraf": "6.1.3", + "typescript": "6.0.3", + "typescript-eslint": "8.65.0", + "vite": "8.2.2", + "vite-uxp-plugin": "1.3.8", + "vitest": "4.1.11", + "zxp-signer": "1.0.6" + }, + "engines": { + "node": "^22.19.0 || ^24.15.0 || ^26.0.0", + "npm": ">=11.6.0" + } + }, + "node_modules/@adobe/cc-ext-uxp-types": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@adobe/cc-ext-uxp-types/-/cc-ext-uxp-types-7.3.1.tgz", + "integrity": "sha512-HLWYoqvDXOmVr9d7l4/hY7h+Ae6IJC1Rm8uIqUK28E8nlT36A23YUsAfnamh24LZC/Xkq5jsYNORW7wX44QEKg==", + "dev": true + }, + "node_modules/@adobe/eslint-plugin-premierepro": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/@adobe/eslint-plugin-premierepro/-/eslint-plugin-premierepro-26.3.0.tgz", + "integrity": "sha512-3PhOO4aVkK8eXRDusCvCELJBSkD/WA8LoL634F4b2q9UBTKKS2ZJmsH0ooAmQwUaPRiK/w9NtwzxJkVPtkMmHw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@typescript-eslint/utils": "^8.58.1" + }, + "peerDependencies": { + "@adobe/premierepro": "~26.3.0", + "@typescript-eslint/parser": "^8.0.0", + "eslint": "^9.0.0", + "typescript": ">=5.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/parser": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@adobe/premierepro": { + "version": "26.3.0", + "resolved": "https://registry.npmjs.org/@adobe/premierepro/-/premierepro-26.3.0.tgz", + "integrity": "sha512-J84zEX8R4L5EU5EVVs3AWkd4LRoXPKueo28jPNfwwDAo69TOSNsAblImtsAmJR4HGDDazsvHkMQE3JBJqIcB9Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=20.0.0" + } + }, + "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/core/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/@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-compilation-targets/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/@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/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/@clack/core": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.3.5.tgz", + "integrity": "sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@clack/prompts": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.7.0.tgz", + "integrity": "sha512-0MhX9/B4iL6Re04jPrttDm+BsP8y6mS7byuv0BvXgdXhbV5PdlsHt55dvNsuBCPZ7xq1oTAOOuotR9NFbQyMSA==", + "bundleDependencies": [ + "is-unicode-supported" + ], + "dev": true, + "license": "MIT", + "dependencies": { + "@clack/core": "^0.3.3", + "is-unicode-supported": "*", + "picocolors": "^1.0.0", + "sisteransi": "^1.0.5" + } + }, + "node_modules/@clack/prompts/node_modules/is-unicode-supported": { + "version": "1.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@epic-web/invariant": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", + "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", + "dev": true, + "license": "MIT" + }, + "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/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-array/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/@eslint/config-array/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/@eslint/config-array/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/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.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/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/@eslint/eslintrc/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/@eslint/eslintrc/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/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.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/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "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/@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.147.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.147.0.tgz", + "integrity": "sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.6.tgz", + "integrity": "sha512-b+jTcARdTiFLI6jB4a5XjTm0RWd6KcRfQj/I2356fxUZemiho9zQLxo0RtCuMDAyKcLo6cEltkgbQp6d1+sjjQ==", + "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.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.6.tgz", + "integrity": "sha512-lkWU8ZJaRk9q3CIEY1Tc7vIFALp3Xw5NfGJo2hQg5oIqNgxWi1zI+IiDEK3r70BF5Dzol1tcXsnzsRc8NLhG+Q==", + "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.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.6.tgz", + "integrity": "sha512-dgR56NYnvAszm7Ob1B2/Vn0e8bUQYZH2UjVaMMtMVOCKFSfjhfLmuA/9+O+F+ajUdG6B/bSssrKW6JJYASa8jA==", + "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.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.6.tgz", + "integrity": "sha512-vpVxFvUCFioJqug7OTvqptkc4yb8UX0AwfDmJpaR/0sWz+BUmqSVAf7c8JkUgnN8YLspb4a/N6NhTyMAmdyQ7Q==", + "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.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.6.tgz", + "integrity": "sha512-h1wG6Y6K3JlRswxsI64qQJqBAy4vrLuHgRbc8CZMGSWTOFRY6ghMApM1NKzB2I0n5xV1fjkE18SuVl2QpLeNpA==", + "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.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.6.tgz", + "integrity": "sha512-tbCiqub0q2MVWJKgF5PoAlNWCtQydiOYSLIkd8sByqK/6MMYLJRcSXSYodqYtd0O+Fw7QaVmKKlS4oL94YRZ0w==", + "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.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.6.tgz", + "integrity": "sha512-oxK9+baEBPhZG5HB4URY+uU04zJWeZlH6Tb9rB5DK4DF9XR1uXNLXt5Q5ZsugTKayNCNLhkcwz/ye74hRI98dg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.6.tgz", + "integrity": "sha512-muWCk27FVBEZtv0MsK8gnfSmgczA8KQ0uRVJbTABKhkRfQc38aUrcb7fhi3BNiyseFmgcRsoMfQsSNJ+DbZdSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.6.tgz", + "integrity": "sha512-eWDoSfU7Co2qj3vgB3Dt4lj1mG6CoWbcJQkRMP3XJplyCMtuaq3LHvPFjS9QIPvMGWVadJC04Xiy0IdcVPtnwQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.6.tgz", + "integrity": "sha512-2bWNjRSIayvupRKxXUY2tWG9fYdoUlTqWywHRvE8Eq3GvuQ+f2HeIkve697fIt+IQs/PV8yFsdWuhp1aJ1PdnA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.6.tgz", + "integrity": "sha512-KekI0gS0wLxe1UBSQSjenBVwou/JkcQPDzBPICGZjxUv9k3RteHDPBQaiOicZUFKRIH2wKEimGwVpnJsbPzu7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.6.tgz", + "integrity": "sha512-TvtPnfVr+HtyGiDmPK4VWmlNm7QhNNAcK5Q9A7aOXsI8545yCyaoMaicXrFZ72JzeYjaUVk7yT243zT0jzjFKQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.6.tgz", + "integrity": "sha512-iOo0VEay2XFhaCcH0sps5XIimkSuOnNaZrf6+ZkoSOQBJPKNU48RkmJv0/lSpipexu5P+ouFgafe5IGr/DiQfg==", + "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.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.6.tgz", + "integrity": "sha512-y5NTmmasMS455JlOCO4ZM9krIchv3Mvm1crL1iUPGOPgEzSkves9n0SdC5Sjz6+qWDFhd8/JpfWMH8NSWNHe+A==", + "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.6", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.6.tgz", + "integrity": "sha512-np8iZSLfXlAD4kWhiyq/u0Yt8oZDtRQ8lGhQaCXo2rl37KNjeU0GjJuwr4P3oeZ++ROfofsKNBqR5LTO8aXyWQ==", + "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/@ts-morph/common": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.18.1.tgz", + "integrity": "sha512-RVE+zSRICWRsfrkAw5qCAK+4ZH9kwEFv5h0+/YeHTLieWP7F4wWq4JsKFuNWG+fYh/KF+8rAtgdj5zb2mm+DVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.2.12", + "minimatch": "^5.1.0", + "mkdirp": "^1.0.4", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@ts-morph/common/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/@ts-morph/common/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/@ts-morph/common/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.13.tgz", + "integrity": "sha512-gcLdvR9HO1ZJBypsOGqaP6TFEzb6vIta0KSTLt9NAQ6pXQO3cRgSVyCN6pzYqI9DlJgY71XKO0dpDhCf08b3pg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "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/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.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/prettier": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-3.0.0.tgz", + "integrity": "sha512-mFMBfMOz8QxhYVbuINtswBp9VL2b4Y0QqYHwqLz3YbgtfAcat2Dl6Y1o4e22S/OVE6Ebl9m7wWiMT2lSbAs1wA==", + "deprecated": "This is a stub types definition. prettier provides its own type definitions, so you do not need this installed.", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier": "*" + } + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-fMPwH9v7r/pp43yUd2/Mbiex5KouJwwR3dzHkhLREUC6764VyDsqxhAxv6OFEYR1RhjOyD1naqba8ECDBe7ZQg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/resolve-dir": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/@types/resolve-dir/-/resolve-dir-0.0.0.tgz", + "integrity": "sha512-OuK0+SZ5RMR1nlLlsmDrHZJV58aLwXu2ET4tKZEkQzizSCpIotDmUz3RDlTA3VYW3XH8xaa/CjXvG/YJkOgHfA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", + "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/type-utils": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.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.65.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/project-service": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", + "debug": "^4.4.3" + }, + "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.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.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/eslint-plugin/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", + "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.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "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/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.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.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.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 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "eslint-visitor-keys": "^5.0.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/eslint-plugin/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.65.0.tgz", + "integrity": "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3" + }, + "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 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/project-service": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", + "debug": "^4.4.3" + }, + "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.1.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.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/parser/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", + "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.1.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "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/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.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.1.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "eslint-visitor-keys": "^5.0.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/parser/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.68.0.tgz", + "integrity": "sha512-5GQtWZCXFcFYux955pvoS02WLc49pXNlvIxocKjS0clvwo3in1RdlzVKyiqQH9vE5AKWFLTaUgeQkOrTS+0Qxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.68.0", + "@typescript-eslint/types": "^8.68.0", + "debug": "^4.4.3" + }, + "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.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.68.0.tgz", + "integrity": "sha512-T5eXpcaJNg8bhjHJ8Rjp68Vq/QBteYtTKY8TZqVNPaUbuz0f6jI9t6aDkylwvalpAB9XTTFeFOjrjXAZ3YvmVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.68.0", + "@typescript-eslint/visitor-keys": "8.68.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.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.68.0.tgz", + "integrity": "sha512-F7zrGQfiJHojPwi8vhxZQC1tWtJzvL74cK/nqri2lk8YUXvYaYwl263xOJ69jDWPUk1hmcdoayFwk9lX09npVw==", + "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.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", + "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.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 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/project-service": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", + "debug": "^4.4.3" + }, + "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.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.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/type-utils/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", + "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.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "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/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.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.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.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 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "eslint-visitor-keys": "^5.0.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/type-utils/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.68.0.tgz", + "integrity": "sha512-9RnpsGJjrAllCMefGVVsImJM24YurhC0Q1h4UbvivtvOqXmR/vEJge2OoE++z9m6hyg8T1Q8t5SNT6tHSbrxcg==", + "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.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.68.0.tgz", + "integrity": "sha512-OKKsD0tYmoNiU5PW2zehO1yO56jYOm1ShYlxon/Z0SJNidAkdVg86eg9ruRuoXf8xfnuWZGbwDsStkoXbZtIIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.68.0", + "@typescript-eslint/tsconfig-utils": "8.68.0", + "@typescript-eslint/types": "8.68.0", + "@typescript-eslint/visitor-keys": "8.68.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.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.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.68.0.tgz", + "integrity": "sha512-PB5gJMMOg0Q5P1tsgWtEAqQacJXq0qEqRHDX/YJ4FaTMLfZPpHB3gjl2EJuiZyPABxmj4ZQYiY9m1bdAJ5y7tQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.68.0", + "@typescript-eslint/types": "8.68.0", + "@typescript-eslint/typescript-estree": "8.68.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 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.68.0.tgz", + "integrity": "sha512-YR65gGdGvTUAWLldC3xLOvOzamdGzB4A5/N8rehEaHs3Zvoe39BhgY+u0SPch1OvrVTfLcc55wsSgK2NcnTS/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.68.0", + "eslint-visitor-keys": "^5.0.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/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz", + "integrity": "sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "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", + "oxc-transform-react": "^0.145.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "oxc-transform-react": { + "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/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "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/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "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": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils/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/archiver-utils/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/archiver-utils/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/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/archiver-utils/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/archiver-utils/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "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/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/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/bare-events": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.2.tgz", + "integrity": "sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.8.1.tgz", + "integrity": "sha512-N1nnXdHZAOSstz0XiHikGS4HGMH4CnSwhqWdGQQMqqdvp4Jybm9sE3R1WVnpWVd4SFkc8ryPDBLViNLwiEqECg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.28.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.1.tgz", + "integrity": "sha512-JprUlveX3QjApC1cTpsUOiscADftCGVWkzitbHsRqv84hzYwYHw2mbluddsq5TvI8mH/8Ov1f4BiMAdcB0oYnQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/bare-stream": { + "version": "2.13.4", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.4.tgz", + "integrity": "sha512-PcrQ8lVLbiJscNm1Kez+Yp4Gy4AHGcN1lzwjvf5NybWen7VvEgUfyfnXYJ2zNqWnzOfCb1Abq6lH8ti0syQszA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.8.1", + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.5.2.tgz", + "integrity": "sha512-L13PCJzKG8RGvx8V1/DdMi12ERhC3tprr7/8a94BxpmnRsFqxh5XZNdhtMxu5HPkRshYOOWRGY8lDP7ZhpG9Cg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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/baseline-browser-mapping": { + "version": "2.11.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.19.tgz", + "integrity": "sha512-Grytf1xOxOEMTGRwx6rLGKkTabd4vMg3VrKdj/7joCmV0qgh4QwMMO6xh34YEXQqirAuUdgQGa5orJQQ+69RBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "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": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "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/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "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.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "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": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/code-block-writer": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-11.0.3.tgz", + "integrity": "sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw==", + "dev": true, + "license": "MIT" + }, + "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/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "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/concurrently": { + "version": "10.0.5", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-10.0.5.tgz", + "integrity": "sha512-JaP/CoftUrCcAFW/g//RbgEGwlelnEae6cfBLgH6ZdO6s8jPkn6p9SB9u6pdVxYXoiSnFqseOlHfrEfF82TVOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "5.6.2", + "rxjs": "7.8.2", + "shell-quote": "1.9.0", + "supports-color": "10.2.2", + "tree-kill": "1.2.2", + "yargs": "18.0.0" + }, + "bin": { + "conc": "dist/bin/index.js", + "concurrently": "dist/bin/index.js" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "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/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "dev": true, + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-env": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz", + "integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@epic-web/invariant": "^1.0.0", + "cross-spawn": "^7.0.6" + }, + "bin": { + "cross-env": "dist/bin/cross-env.js", + "cross-env-shell": "dist/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.415", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.415.tgz", + "integrity": "sha512-958V+Kbhtgz+SxXeEVKBjrlKRBIDAYvUJfwhjxMZ5S6ut9jAl7l9ZKBkBrvjyjZE36PabLUo2L8kEeV5O4vgJg==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "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.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "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.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.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.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-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": "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/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/eslint/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/eslint/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/eslint/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/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/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/eslint/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/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/espree/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/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/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "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.2", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.2.tgz", + "integrity": "sha512-UpGiiODyCGprM8EPP6JodP6jC9Rws6TCuiDOD+nn0CJhR8guI3g/ozo4ugL0vJ+Yz1UtJuuRPqvQuybVOF1VQA==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "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/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/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT" + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "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/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "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/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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": "BSD-3-Clause" + }, + "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/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "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-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "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/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "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/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/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.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/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "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, + "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, + "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, + "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, + "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/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": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "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/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "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/meta-bolt": { + "version": "0.0.17", + "resolved": "https://registry.npmjs.org/meta-bolt/-/meta-bolt-0.0.17.tgz", + "integrity": "sha512-GIiQbuHJ5wtHEy+D1RjNiIyB+Z1bjEYOsVYzAFBWowKxXf/aS9PnHfiM584Igv6s0rez2VxIODKIdc8L7M1sMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@clack/prompts": "^0.7.0", + "@types/prettier": "^3.0.0", + "@types/resolve-dir": "^0.0.0", + "archiver": "^7.0.1", + "cliui": "^8.0.1", + "commander": "^12.1.0", + "execa": "^5.1.1", + "fast-glob": "^3.3.2", + "fs-extra": "^10.0.1", + "jsonc-parser": "^3.2.1", + "picocolors": "^1.0.0", + "prettier": "^3.1.0", + "radash": "^11.0.0", + "resolve-dir": "^1.0.1", + "ts-morph": "^17.0.1", + "ts-node": "^10.7.0", + "typescript": "^4.9.5", + "yargs": "^17.7.2" + }, + "bin": { + "create-meta-bolt": "dist/index.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/meta-bolt/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", + "engines": { + "node": ">=8" + } + }, + "node_modules/meta-bolt/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/meta-bolt/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/meta-bolt/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/meta-bolt/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/meta-bolt/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/meta-bolt/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/meta-bolt/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/meta-bolt/node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/meta-bolt/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "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/micromatch/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/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "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/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "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/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "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-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "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": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "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.9.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", + "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "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/radash": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/radash/-/radash-11.0.0.tgz", + "integrity": "sha512-CRWxTFTDff0IELGJ/zz58yY4BDgyI14qSM5OLNKbCItJrff7m7dXbVF0kWYVCXQtPb3SXIVhXvAImH6eT7VLSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/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/readdir-glob/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/readdir-glob/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/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/rimraf": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz", + "integrity": "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "glob": "^13.0.3", + "package-json-from-dist": "^1.0.1" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rolldown": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.6.tgz", + "integrity": "sha512-vMM4q3aixf46GiF1Kok8jDPFsEpXgFWGjUHXNkNHNm+Y2adXAG2dbX91jkti3i0ZRsOlcmbuzAz1poObSHCmUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.147.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.6", + "@rolldown/binding-android-arm64": "1.2.6", + "@rolldown/binding-darwin-arm64": "1.2.6", + "@rolldown/binding-darwin-x64": "1.2.6", + "@rolldown/binding-freebsd-x64": "1.2.6", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.6", + "@rolldown/binding-linux-arm64-gnu": "1.2.6", + "@rolldown/binding-linux-arm64-musl": "1.2.6", + "@rolldown/binding-linux-ppc64-gnu": "1.2.6", + "@rolldown/binding-linux-s390x-gnu": "1.2.6", + "@rolldown/binding-linux-x64-gnu": "1.2.6", + "@rolldown/binding-linux-x64-musl": "1.2.6", + "@rolldown/binding-openharmony-arm64": "1.2.6", + "@rolldown/binding-win32-arm64-msvc": "1.2.6", + "@rolldown/binding-win32-x64-msvc": "1.2.6" + } + }, + "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==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "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/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/shell-quote": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz", + "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "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/streamx": { + "version": "2.28.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.1.tgz", + "integrity": "sha512-zEzXb0s5Cds7tqMH6rhZ05lcJydCWiQPEwiNngVqzsxCc962vLY4Uw+mW7od8kDH258k2Uz/JrOkdIAAhSh9VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/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", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/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", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "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": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tar-stream": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.1.tgz", + "integrity": "sha512-nqsEO8zLZJvrOMdEwkA0QdCLFbetHMn95Zqu4fKwX+hkaTWJPZZOrxx/PwtxoK0MMGQmBQNRW3CPs8IFYQz4cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "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/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/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/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "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/ts-morph": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-17.0.1.tgz", + "integrity": "sha512-10PkHyXmrtsTvZSL+cqtJLTgFXkU43Gd0JCc0Rw6GchWbqKe0Rwgt1v3ouobTZwQzF1mGhDeAlWYBMGRV7y+3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ts-morph/common": "~0.18.0", + "code-block-writer": "^11.0.3" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "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==", + "dev": true, + "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/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.65.0.tgz", + "integrity": "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.65.0", + "@typescript-eslint/parser": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.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 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/project-service": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", + "debug": "^4.4.3" + }, + "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.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/scope-manager": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.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/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", + "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.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "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/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.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.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.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 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.65.0", + "eslint-visitor-keys": "^5.0.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/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "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/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "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-uxp-plugin": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/vite-uxp-plugin/-/vite-uxp-plugin-1.3.8.tgz", + "integrity": "sha512-lUxWk80n5VurOcluJBzDhoEcPFtbjvj9ZhfXDiUvC1bE6K3zc2CzWPN1i5IW0bazooArygoZj9p7l5QYH+gX3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver": "5.3.0", + "magic-string": "^0.30.21", + "meta-bolt": "^0.0.17", + "picocolors": "^1.1.1", + "typescript": "^5.2.2", + "ws": "^8.14.2" + } + }, + "node_modules/vite-uxp-plugin/node_modules/archiver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.0.tgz", + "integrity": "sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.0", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.0.0", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/vite-uxp-plugin/node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/vite-uxp-plugin/node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/vite-uxp-plugin/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/vite-uxp-plugin/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/vite-uxp-plugin/node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/vite-uxp-plugin/node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/vite-uxp-plugin/node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/vite-uxp-plugin/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/vite-uxp-plugin/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/vite-uxp-plugin/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/vite-uxp-plugin/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite-uxp-plugin/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/vite-uxp-plugin/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/vite-uxp-plugin/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/vite-uxp-plugin/node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/vite-uxp-plugin/node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "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/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/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/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", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/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/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "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/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "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/yargs": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", + "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^7.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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/zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "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" + } + }, + "node_modules/zxp-signer": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/zxp-signer/-/zxp-signer-1.0.6.tgz", + "integrity": "sha512-4SxJds9qPdSvGhQ59R/+jD2yLYqkc3gLTOUrRS7CN0Sbb+H/VOZR2DWg5I7OBQCXU31n+WTOF5mGGEhmO7aLhA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + } + } +} diff --git a/premiere/package.json b/premiere/package.json new file mode 100644 index 00000000..ef63df92 --- /dev/null +++ b/premiere/package.json @@ -0,0 +1,52 @@ +{ + "name": "vidxp-premiere-extension", + "version": "0.1.0", + "private": true, + "type": "module", + "packageManager": "npm@11.6.0", + "engines": { + "node": "^22.19.0 || ^24.15.0 || ^26.0.0", + "npm": ">=11.6.0" + }, + "scripts": { + "clean": "rimraf dist ccx coverage", + "build": "npm run clean && npm run build:uxp && npm run build:cep", + "build:uxp": "cross-env BOLT_MODE=build vite build --mode uxp", + "build:cep": "vite build --mode cep && node scripts/prepare-cep.mjs", + "package:uxp": "cross-env BOLT_MODE=package vite build --mode uxp", + "package": "npm run clean && npm run package:uxp && npm run build:cep && node scripts/package-extensions.mjs", + "ccx-install": "cross-env BOLT_ACTION=ccx-install vite --mode uxp", + "ccx-uninstall": "cross-env BOLT_ACTION=ccx-uninstall vite --mode uxp", + "dev": "concurrently -k -n build,types \"cross-env BOLT_MODE=dev vite build --mode uxp --watch\" \"tsc --watch --noEmit --preserveWatchOutput\"", + "lint": "eslint . --max-warnings 0", + "typecheck": "tsc --noEmit", + "test": "vitest", + "test:run": "vitest run", + "check": "npm run typecheck && npm run lint && npm run test:run && npm run build" + }, + "dependencies": { + "react": "19.2.8", + "react-dom": "19.2.8" + }, + "devDependencies": { + "@adobe/cc-ext-uxp-types": "7.3.1", + "@adobe/eslint-plugin-premierepro": "26.3.0", + "@adobe/premierepro": "26.3.0", + "@eslint/js": "9.39.2", + "@types/node": "24.13.3", + "@types/react": "19.2.18", + "@types/react-dom": "19.2.5", + "@vitejs/plugin-react": "6.1.1", + "concurrently": "10.0.5", + "cross-env": "10.1.0", + "eslint": "9.39.2", + "eslint-plugin-react-hooks": "7.0.1", + "rimraf": "6.1.3", + "typescript": "6.0.3", + "typescript-eslint": "8.65.0", + "vite": "8.2.2", + "vite-uxp-plugin": "1.3.8", + "vitest": "4.1.11", + "zxp-signer": "1.0.6" + } +} diff --git a/premiere/scripts/package-extensions.mjs b/premiere/scripts/package-extensions.mjs new file mode 100644 index 00000000..19ac10be --- /dev/null +++ b/premiere/scripts/package-extensions.mjs @@ -0,0 +1,55 @@ +import { copyFileSync, mkdirSync, readdirSync, rmSync } from "node:fs"; +import { createRequire } from "node:module"; +import { resolve } from "node:path"; +import { spawnSync } from "node:child_process"; + +const require = createRequire(import.meta.url); +const root = resolve(import.meta.dirname, ".."); +const packages = resolve(root, "packages"); +const uxpPackage = resolve(packages, "vidxp-premiere-uxp.ccx"); +const cepPackage = resolve(packages, "vidxp-premiere-cep.zxp"); +const certificate = resolve(packages, "vidxp-premiere-build-certificate.p12"); + +mkdirSync(packages, { recursive: true }); +const generatedUxpPackages = readdirSync(resolve(root, "ccx")) + .filter((name) => name.endsWith(".ccx")); +if (generatedUxpPackages.length !== 1) { + throw new Error(`Expected one Bolt UXP package, found ${generatedUxpPackages.length}.`); +} +copyFileSync( + resolve(root, "ccx", generatedUxpPackages[0]), + uxpPackage, +); + +const signerRoot = resolve(require.resolve("zxp-signer/package.json"), ".."); +const signerPlatform = process.platform === "win32" + ? process.arch === "x64" ? "win64" : "Win32" + : "osx"; +const signer = resolve(signerRoot, "bin", "4.1.3", signerPlatform, `ZXPSignCmd${process.platform === "win32" ? ".exe" : ""}`); +const certificatePassword = process.env.ZXP_CERT_PASSWORD || "vidxp-release-build"; +runSigner([ + "-selfSignedCert", + process.env.ZXP_CERT_COUNTRY || "PK", + process.env.ZXP_CERT_PROVINCE || "Punjab", + process.env.ZXP_CERT_ORG || "Grayhat Developers PVT Ltd", + process.env.ZXP_CERT_NAME || "org.grayhat.vidxp-premiere.cep", + certificatePassword, + certificate, +]); +const result = runSigner([ + "-sign", + resolve(root, "dist", "cep"), + cepPackage, + certificate, + certificatePassword, + "-tsa", + process.env.ZXP_TIMESTAMP || "http://timestamp.digicert.com/", +], false); +rmSync(certificate, { force: true }); +if (result.status !== 0) process.exit(result.status ?? 1); + +function runSigner(arguments_, exitOnFailure = true) { + const result = spawnSync(signer, arguments_, { stdio: "inherit" }); + if (exitOnFailure && result.status !== 0) process.exit(result.status ?? 1); + return result; +} diff --git a/premiere/scripts/prepare-cep.mjs b/premiere/scripts/prepare-cep.mjs new file mode 100644 index 00000000..a811404f --- /dev/null +++ b/premiere/scripts/prepare-cep.mjs @@ -0,0 +1,12 @@ +import { cpSync, mkdirSync } from "node:fs"; +import { resolve } from "node:path"; + +const root = resolve(import.meta.dirname, ".."); +const output = resolve(root, "dist", "cep"); + +mkdirSync(resolve(output, "CSXS"), { recursive: true }); +mkdirSync(resolve(output, "jsx"), { recursive: true }); +cpSync(resolve(root, "cep", "index.html"), resolve(output, "index.html")); +cpSync(resolve(root, "cep", "CSXS", "manifest.xml"), resolve(output, "CSXS", "manifest.xml")); +cpSync(resolve(root, "cep", "jsx", "host.jsx"), resolve(output, "jsx", "host.jsx")); +cpSync(resolve(root, "src", "ui", "styles.css"), resolve(output, "styles.css")); diff --git a/premiere/src/premiere/adapter.ts b/premiere/src/premiere/adapter.ts new file mode 100644 index 00000000..dd6a3422 --- /dev/null +++ b/premiere/src/premiere/adapter.ts @@ -0,0 +1,104 @@ +import type { + FolderItem, + premierepro, + ProjectItem, +} from "@adobe/premierepro"; + +import type { + PremiereAdapter, + PremiereClip, + PremiereLibrary, + PremiereMediaNode, +} from "./types"; + +// Premiere supplies this module at runtime and Vite leaves it external. +// eslint-disable-next-line @typescript-eslint/no-require-imports +const ppro = require("premierepro") as premierepro; + +export function createPremiereAdapter(): PremiereAdapter { + return { + async getLibrary(): Promise { + const project = await ppro.Project.getActiveProject(); + if (!project) return undefined; + + const [sequence, root] = await Promise.all([ + project.getActiveSequence(), + project.getRootItem(), + ]); + const items = await readFolder(root); + + return { + projectName: project.name, + sequenceName: sequence?.name, + items, + }; + }, + + async getSelectedProjectItemIds(): Promise { + const project = await ppro.Project.getActiveProject(); + if (!project) return []; + const selection = await ppro.ProjectUtils.getSelection(project); + const items = await selection.getItems(); + return items.map((item) => item.getId()); + }, + }; +} + +async function readFolder(folder: FolderItem): Promise { + const children = await folder.getItems(); + const nodes = await Promise.all(children.map(readProjectItem)); + return nodes.filter((node): node is PremiereMediaNode => node !== undefined); +} + +async function readProjectItem( + item: ProjectItem, +): Promise { + const id = item.getId(); + if ( + item.type === ppro.ProjectItem.TYPE_BIN || + item.type === ppro.ProjectItem.TYPE_ROOT + ) { + const folder = ppro.FolderItem.cast(item); + return { + kind: "bin", + id, + name: item.name, + children: await readFolder(folder), + }; + } + + try { + const clip = ppro.ClipProjectItem.cast(item); + if (await clip.isSequence()) return undefined; + if (await clip.isOffline()) { + return unavailableClip(id, item.name, "offline", "Media is offline in Premiere."); + } + const nativePath = (await clip.getMediaFilePath()).trim(); + if (!nativePath) { + return unavailableClip( + id, + item.name, + "unavailable", + "Premiere did not return a file-backed media path.", + ); + } + return { + kind: "clip", + id, + name: item.name, + nativePath, + availability: "ready", + }; + } catch { + return undefined; + } +} + +function unavailableClip( + id: string, + name: string, + availability: PremiereClip["availability"], + detail: string, +): PremiereClip { + return { kind: "clip", id, name, availability, detail }; +} diff --git a/premiere/src/premiere/cep-adapter.ts b/premiere/src/premiere/cep-adapter.ts new file mode 100644 index 00000000..20073194 --- /dev/null +++ b/premiere/src/premiere/cep-adapter.ts @@ -0,0 +1,43 @@ +import type { PremiereAdapter, PremiereLibrary } from "./types"; + +interface CepBridge { + evalScript(script: string, callback: (result: string) => void): void; +} + +interface BridgeEnvelope { + ok: boolean; + value?: T; + error?: string; +} + +export function createCepPremiereAdapter(): PremiereAdapter { + return { + getLibrary: () => evaluate("$._VIDXP.getLibrary()"), + getSelectedProjectItemIds: () => + evaluate("$._VIDXP.getSelection()"), + }; +} + +function evaluate(script: string): Promise { + const bridge = Reflect.get(window, "__adobe_cep__") as CepBridge | undefined; + if (!bridge) return Promise.reject(new Error("Premiere's CEP bridge is unavailable.")); + + return new Promise((resolve, reject) => { + bridge.evalScript(script, (rawResult) => { + if (!rawResult || rawResult === "EvalScript error.") { + reject(new Error("Premiere could not run the VidXP host bridge.")); + return; + } + try { + const envelope = JSON.parse(rawResult) as BridgeEnvelope; + if (!envelope.ok) { + reject(new Error(envelope.error || "Premiere returned an unknown error.")); + return; + } + resolve(envelope.value as T); + } catch { + reject(new Error("Premiere returned an invalid VidXP host response.")); + } + }); + }); +} diff --git a/premiere/src/premiere/library.ts b/premiere/src/premiere/library.ts new file mode 100644 index 00000000..a55fdb3c --- /dev/null +++ b/premiere/src/premiere/library.ts @@ -0,0 +1,69 @@ +import type { + PremiereBin, + PremiereClip, + PremiereMediaNode, +} from "./types"; + +export function collectSelectedClips( + items: PremiereMediaNode[], + selectedIds: ReadonlySet, +): PremiereClip[] { + const clips = new Map(); + + function visit(node: PremiereMediaNode, ancestorSelected: boolean) { + const selected = ancestorSelected || selectedIds.has(node.id); + if (node.kind === "bin") { + node.children.forEach((child) => visit(child, selected)); + return; + } + if (selected && node.availability === "ready" && node.nativePath) { + clips.set(node.nativePath, node); + } + } + + items.forEach((item) => visit(item, false)); + return [...clips.values()]; +} + +export function countReadyClips(items: PremiereMediaNode[]): number { + return items.reduce( + (total, item) => + total + + (item.kind === "bin" + ? countReadyClips(item.children) + : item.availability === "ready" + ? 1 + : 0), + 0, + ); +} + +export function filterLibrary( + items: PremiereMediaNode[], + rawQuery: string, +): PremiereMediaNode[] { + const query = rawQuery.trim().toLocaleLowerCase(); + if (!query) return items; + + return items.flatMap((node): PremiereMediaNode[] => { + if (node.kind === "clip") { + const searchable = `${node.name} ${node.nativePath ?? ""}`.toLocaleLowerCase(); + return searchable.includes(query) ? [node] : []; + } + const children = filterLibrary(node.children, query); + return node.name.toLocaleLowerCase().includes(query) || children.length > 0 + ? [{ ...node, children } satisfies PremiereBin] + : []; + }); +} + +export function chunkPaths(paths: string[], maximum = 10): string[][] { + if (!Number.isInteger(maximum) || maximum < 1) { + throw new Error("The ingestion batch size must be a positive integer."); + } + const batches: string[][] = []; + for (let index = 0; index < paths.length; index += maximum) { + batches.push(paths.slice(index, index + maximum)); + } + return batches; +} diff --git a/premiere/src/premiere/types.ts b/premiere/src/premiere/types.ts new file mode 100644 index 00000000..8a9b3f21 --- /dev/null +++ b/premiere/src/premiere/types.ts @@ -0,0 +1,28 @@ +export interface PremiereLibrary { + projectName: string; + sequenceName?: string; + items: PremiereMediaNode[]; +} + +export type PremiereMediaNode = PremiereBin | PremiereClip; + +export interface PremiereBin { + kind: "bin"; + id: string; + name: string; + children: PremiereMediaNode[]; +} + +export interface PremiereClip { + kind: "clip"; + id: string; + name: string; + nativePath?: string; + availability: "ready" | "offline" | "unavailable"; + detail?: string; +} + +export interface PremiereAdapter { + getLibrary(): Promise; + getSelectedProjectItemIds(): Promise; +} diff --git a/premiere/src/services/vidxp/cep-fetch.ts b/premiere/src/services/vidxp/cep-fetch.ts new file mode 100644 index 00000000..af9f2737 --- /dev/null +++ b/premiere/src/services/vidxp/cep-fetch.ts @@ -0,0 +1,75 @@ +import type { VidXPFetch } from "./client"; + +interface CepNodeRuntime { + require(name: "http" | "https"): NodeHttpModule; +} + +interface NodeHttpModule { + request( + url: string, + options: { headers: Record; method: string }, + callback: (response: NodeResponse) => void, + ): NodeRequest; +} + +interface NodeResponse { + headers: Record; + statusCode?: number; + statusMessage?: string; + on(event: "data", listener: (chunk: string) => void): void; + on(event: "end", listener: () => void): void; + setEncoding(encoding: "utf8"): void; +} + +interface NodeRequest { + destroy(error?: Error): void; + end(body?: string): void; + on(event: "error", listener: (error: Error) => void): void; +} + +export function createCepFetch(): VidXPFetch { + return (input, init = {}) => { + const requestInit = init as RequestInit; + const url = input instanceof Request ? input.url : String(input); + const runtime = Reflect.get(window, "cep_node") as CepNodeRuntime | undefined; + if (!runtime) return Promise.reject(new Error("Premiere's CEP network runtime is unavailable.")); + const protocol = new URL(url).protocol; + const transport = runtime.require(protocol === "https:" ? "https" : "http"); + const headers = Object.fromEntries(new Headers(requestInit.headers).entries()); + + return new Promise((resolve, reject) => { + const request = transport.request( + url, + { headers, method: requestInit.method || "GET" }, + (response) => { + response.setEncoding("utf8"); + let body = ""; + response.on("data", (chunk) => { + body += chunk; + }); + response.on("end", () => { + const responseHeaders = new Headers(); + for (const [name, value] of Object.entries(response.headers)) { + if (Array.isArray(value)) value.forEach((item) => responseHeaders.append(name, item)); + else if (value !== undefined) responseHeaders.set(name, value); + } + resolve(new Response(body, { + headers: responseHeaders, + status: response.statusCode || 500, + statusText: response.statusMessage, + })); + }); + }, + ); + request.on("error", reject); + requestInit.signal?.addEventListener("abort", () => request.destroy(abortError()), { once: true }); + request.end(typeof requestInit.body === "string" ? requestInit.body : undefined); + }); + }; +} + +function abortError(): Error { + const error = new Error("Operation cancelled"); + error.name = "AbortError"; + return error; +} diff --git a/premiere/src/services/vidxp/client.ts b/premiere/src/services/vidxp/client.ts new file mode 100644 index 00000000..bbcdc454 --- /dev/null +++ b/premiere/src/services/vidxp/client.ts @@ -0,0 +1,226 @@ +import type { + CapabilitySummary, + MediaIngestionStatus, + VidXPJob, + WorkspaceOverview, +} from "./types"; + +export type VidXPFetch = typeof fetch; +type Sleep = (milliseconds: number) => Promise; + +export interface VidXPClientOptions { + baseUrl: string; + bearerToken?: string; + fetchImpl?: VidXPFetch; + sleep?: Sleep; +} + +export interface SearchRequest { + query: string; + modalities: string[]; + mediaId?: string; + topK?: number; +} + +export class VidXPApiError extends Error { + constructor( + message: string, + readonly status: number, + readonly code?: string, + ) { + super(message); + this.name = "VidXPApiError"; + } +} + +export class VidXPClient { + private readonly baseUrl: string; + private readonly bearerToken?: string; + private readonly fetchImpl: VidXPFetch; + private readonly sleep: Sleep; + + constructor(options: VidXPClientOptions) { + this.baseUrl = normalizeBaseUrl(options.baseUrl); + this.bearerToken = options.bearerToken?.trim() || undefined; + this.fetchImpl = options.fetchImpl ?? fetch; + this.sleep = options.sleep ?? delay; + } + + async health(): Promise { + await this.request<{ status: string }>("/health"); + } + + async listCapabilities(): Promise { + const response = await this.request<{ items: CapabilitySummary[] }>( + "/api/v1/capabilities", + ); + return response.items; + } + + workspace(): Promise { + return this.request("/api/v1/workspace?page_size=100"); + } + + createLocalIngestion( + paths: string[], + modalities: string[], + idempotencyKey: string, + ): Promise { + return this.request( + "/api/v1/media/local-ingestions", + { + method: "POST", + headers: { "Idempotency-Key": idempotencyKey }, + body: JSON.stringify({ + paths, + modalities, + index_after_import: true, + }), + }, + ); + } + + getLocalIngestion(ingestionId: string): Promise { + return this.request( + `/api/v1/media/local-ingestions/${encodeURIComponent(ingestionId)}`, + ); + } + + async waitForLocalIngestion( + initial: MediaIngestionStatus, + onProgress: (status: MediaIngestionStatus) => void, + signal?: AbortSignal, + ): Promise { + let status = initial; + while (!status.terminal) { + onProgress(status); + await this.wait(status.poll_after_seconds, signal); + status = await this.getLocalIngestion(status.session_id); + } + onProgress(status); + return status; + } + + submitSearch(request: SearchRequest, idempotencyKey: string): Promise { + const query = request.query.trim(); + if (!query) throw new Error("Enter something to search for."); + return this.request("/api/v1/jobs/search", { + method: "POST", + headers: { "Idempotency-Key": idempotencyKey }, + body: JSON.stringify({ + query, + modalities: request.modalities, + media_id: request.mediaId || null, + top_k: request.topK ?? 20, + }), + }); + } + + getJob(jobId: string): Promise { + return this.request(`/api/v1/jobs/${encodeURIComponent(jobId)}`); + } + + async waitForJob( + initial: VidXPJob, + onProgress: (job: VidXPJob) => void, + signal?: AbortSignal, + ): Promise { + let job = initial; + while (!job.terminal) { + onProgress(job); + await this.wait(job.poll_after_seconds, signal); + job = await this.getJob(job.job_id); + } + onProgress(job); + if (job.state !== "succeeded") { + throw new VidXPApiError( + job.error?.message ?? "The VidXP job did not complete.", + 409, + job.error?.code, + ); + } + return job; + } + + private async wait(seconds: number, signal?: AbortSignal): Promise { + if (signal?.aborted) throw abortError(); + await this.sleep(Math.max(250, Math.min(10_000, seconds * 1000))); + if (signal?.aborted) throw abortError(); + } + + private async request(path: string, init: RequestInit = {}): Promise { + const headers = new Headers(init.headers); + headers.set("Accept", "application/json"); + if (init.body !== undefined) headers.set("Content-Type", "application/json"); + if (this.bearerToken) { + headers.set("Authorization", `Bearer ${this.bearerToken}`); + } + const response = await this.fetchImpl(`${this.baseUrl}${path}`, { + ...init, + headers, + }); + if (!response.ok) throw await apiError(response); + return (await response.json()) as T; + } +} + +export function createIdempotencyKey(): string { + if (typeof crypto !== "undefined" && "randomUUID" in crypto) { + return crypto.randomUUID(); + } + const timestamp = Date.now().toString(16).padStart(16, "0"); + const random = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER) + .toString(16) + .padStart(16, "0"); + return `${timestamp}${random}`; +} + +function normalizeBaseUrl(value: string): string { + const candidate = value.trim(); + if (!candidate) throw new Error("Enter the VidXP API address shown by Desktop."); + let parsed: URL; + try { + parsed = new URL(candidate); + } catch { + throw new Error("Enter a complete VidXP API address, including http:// or https://."); + } + if (!['http:', 'https:'].includes(parsed.protocol)) { + throw new Error("The VidXP API address must use HTTP or HTTPS."); + } + parsed.pathname = ""; + parsed.search = ""; + parsed.hash = ""; + return parsed.toString().replace(/\/$/, ""); +} + +async function apiError(response: Response): Promise { + let message = `VidXP returned HTTP ${response.status}.`; + let code: string | undefined; + try { + const payload = (await response.json()) as { + error?: { + code?: string; + message?: string; + details?: { remediation?: string }; + }; + }; + if (payload.error?.message) message = payload.error.message; + if (payload.error?.details?.remediation) { + message += ` ${payload.error.details.remediation}`; + } + code = payload.error?.code; + } catch { + // Keep the bounded status-only fallback; response bodies may contain internals. + } + return new VidXPApiError(message, response.status, code); +} + +function delay(milliseconds: number): Promise { + return new Promise((resolve) => setTimeout(resolve, milliseconds)); +} + +function abortError(): Error { + const error = new Error("Operation cancelled"); + error.name = "AbortError"; + return error; +} diff --git a/premiere/src/services/vidxp/types.ts b/premiere/src/services/vidxp/types.ts new file mode 100644 index 00000000..e85a39a2 --- /dev/null +++ b/premiere/src/services/vidxp/types.ts @@ -0,0 +1,112 @@ +export interface CapabilitySummary { + name: string; + description: string; + supports_indexing: boolean; + prepares_models: boolean; + roles: string[]; +} + +export interface WorkspaceMedia { + media_id: string; + original_filename: string; + duration_seconds?: number; + state: string; + in_active_snapshot: boolean; +} + +export interface WorkspaceOverview { + capabilities: CapabilitySummary[]; + media: WorkspaceMedia[]; + media_total: number; + next_cursor?: string; + index: { + state: string; + stage: string; + message: string; + summary?: { + media_count: number; + modalities: string[]; + }; + }; + next_actions: string[]; +} + +export interface ErrorDetail { + code: string; + message: string; + retryable?: boolean; + details?: { + remediation?: string; + }; +} + +export interface MediaUploadStatus { + intent_id: string; + original_filename: string; + phase: string; + media_id?: string; + searchable: boolean; + terminal: boolean; + status: string; + error?: ErrorDetail; +} + +export interface MediaIngestionStatus { + session_id: string; + aggregate_state: string; + index_modalities: string[]; + file_count: number; + searchable_file_count: number; + failed_file_count: number; + index_failed_file_count: number; + items: MediaUploadStatus[]; + terminal: boolean; + poll_after_seconds: number; + status: string; + next_action: string; +} + +export interface JobProgress { + stage: string; + message: string; + current?: number; + total?: number; +} + +export interface SearchHit { + modality: string; + score: number; + metadata: Record; +} + +export interface FusedMoment { + moment_id?: string; + rank: number; + score: number; + media_id: string; + start: number; + end: number; + modalities: string[]; + hits: SearchHit[]; +} + +export interface FusedSearchResult { + query_id: string; + query: string; + modalities: string[]; + moments: FusedMoment[]; +} + +export interface VidXPJob { + job_id: string; + kind: string; + state: string; + progress?: JobProgress; + result?: { + kind: string; + result: FusedSearchResult; + }; + error?: ErrorDetail; + terminal: boolean; + poll_after_seconds: number; +} diff --git a/premiere/src/ui/App.tsx b/premiere/src/ui/App.tsx new file mode 100644 index 00000000..27fe6e64 --- /dev/null +++ b/premiere/src/ui/App.tsx @@ -0,0 +1,650 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; + +import { + chunkPaths, + collectSelectedClips, + countReadyClips, + filterLibrary, +} from "../premiere/library"; +import type { + PremiereAdapter, + PremiereLibrary, + PremiereMediaNode, +} from "../premiere/types"; +import { + createIdempotencyKey, + VidXPClient, + type VidXPFetch, +} from "../services/vidxp/client"; +import type { + CapabilitySummary, + FusedMoment, + WorkspaceOverview, +} from "../services/vidxp/types"; +import { + SpectrumActionButton, + SpectrumButton, + SpectrumCheckbox, + SpectrumTextArea, + SpectrumTextField, +} from "./components/Spectrum"; + +type ConnectionState = + | { status: "disconnected" } + | { status: "connecting" } + | { status: "ready" } + | { status: "error"; message: string }; + +type OperationState = + | { status: "idle" } + | { status: "indexing" | "searching"; message: string } + | { status: "error"; message: string }; + +interface Notice { + tone: "success" | "warning"; + title: string; + message: string; +} + +const DEFAULT_API_ADDRESS = "http://127.0.0.1:32191"; + +interface AppProps { + fetchImpl?: VidXPFetch; + premiere: PremiereAdapter; +} + +export function App({ fetchImpl, premiere }: AppProps) { + const abortController = useRef(undefined); + const [apiAddress, setApiAddress] = useState(DEFAULT_API_ADDRESS); + const [bearerToken, setBearerToken] = useState(""); + const [client, setClient] = useState(); + const [connection, setConnection] = useState({ + status: "disconnected", + }); + const [library, setLibrary] = useState(); + const [libraryError, setLibraryError] = useState(); + const [libraryFilter, setLibraryFilter] = useState(""); + const [selectedIds, setSelectedIds] = useState([]); + const [capabilities, setCapabilities] = useState([]); + const [indexModalities, setIndexModalities] = useState([]); + const [searchModalities, setSearchModalities] = useState([]); + const [workspace, setWorkspace] = useState(); + const [operation, setOperation] = useState({ status: "idle" }); + const [notice, setNotice] = useState(); + const [query, setQuery] = useState(""); + const [mediaScope, setMediaScope] = useState(""); + const [moments, setMoments] = useState([]); + + const loadLibrary = useCallback(async () => { + setLibraryError(undefined); + try { + const nextLibrary = await premiere.getLibrary(); + setLibrary(nextLibrary); + if (!nextLibrary) setSelectedIds([]); + } catch (error) { + setLibrary(undefined); + setLibraryError(messageOf(error)); + } + }, [premiere]); + + useEffect(() => { + void loadLibrary(); + return () => abortController.current?.abort(); + }, [loadLibrary]); + + const selectedSet = useMemo(() => new Set(selectedIds), [selectedIds]); + const selectedClips = useMemo( + () => collectSelectedClips(library?.items ?? [], selectedSet), + [library, selectedSet], + ); + const visibleLibrary = useMemo( + () => filterLibrary(library?.items ?? [], libraryFilter), + [library, libraryFilter], + ); + const indexableCapabilities = capabilities.filter( + (capability) => capability.supports_indexing, + ); + const searchableCapabilities = capabilities.filter((capability) => + capability.roles.includes("searchable"), + ); + const busy = operation.status === "indexing" || operation.status === "searching"; + + async function connect() { + setConnection({ status: "connecting" }); + setNotice(undefined); + try { + const nextClient = new VidXPClient({ + baseUrl: apiAddress, + bearerToken, + fetchImpl, + }); + const [, nextCapabilities, nextWorkspace] = await Promise.all([ + nextClient.health(), + nextClient.listCapabilities(), + nextClient.workspace(), + ]); + setClient(nextClient); + setCapabilities(nextCapabilities); + setIndexModalities( + nextCapabilities + .filter((capability) => capability.supports_indexing) + .map((capability) => capability.name), + ); + setSearchModalities( + nextCapabilities + .filter((capability) => capability.roles.includes("searchable")) + .map((capability) => capability.name), + ); + setWorkspace(nextWorkspace); + setConnection({ status: "ready" }); + } catch (error) { + setClient(undefined); + setConnection({ status: "error", message: messageOf(error) }); + } + } + + async function selectCurrentPremiereItems() { + try { + setSelectedIds(await premiere.getSelectedProjectItemIds()); + } catch (error) { + setLibraryError(messageOf(error)); + } + } + + async function indexSelection() { + if (!client || selectedClips.length === 0 || indexModalities.length === 0) return; + const controller = beginOperation(); + setNotice(undefined); + const batches = chunkPaths(selectedClips.map((clip) => clip.nativePath!)); + let searchable = 0; + const failures: string[] = []; + + try { + for (const [batchIndex, paths] of batches.entries()) { + const initial = await client.createLocalIngestion( + paths, + indexModalities, + createIdempotencyKey(), + ); + const final = await client.waitForLocalIngestion( + initial, + (status) => { + setOperation({ + status: "indexing", + message: `Batch ${batchIndex + 1} of ${batches.length} · ${status.status}`, + }); + }, + controller.signal, + ); + searchable += final.searchable_file_count; + failures.push( + ...final.items + .filter((item) => item.error) + .map( + (item) => + `${item.original_filename}: ${item.error?.message ?? "Indexing failed."}`, + ), + ); + } + setWorkspace(await client.workspace()); + setNotice({ + tone: failures.length > 0 ? "warning" : "success", + title: failures.length > 0 ? "Indexing finished with issues" : "Indexing complete", + message: + failures.length > 0 + ? `${searchable} media item(s) are searchable. ${failures.join(" ")}` + : `${searchable} media item(s) from Premiere are now searchable.`, + }); + setOperation({ status: "idle" }); + } catch (error) { + if (!controller.signal.aborted) { + setOperation({ status: "error", message: messageOf(error) }); + } + } + } + + async function search() { + if (!client || !query.trim()) return; + const controller = beginOperation(); + setNotice(undefined); + try { + const initial = await client.submitSearch( + { + query, + modalities: searchModalities, + mediaId: mediaScope || undefined, + topK: 20, + }, + createIdempotencyKey(), + ); + const completed = await client.waitForJob( + initial, + (job) => { + setOperation({ + status: "searching", + message: job.progress?.message ?? "VidXP is searching indexed media…", + }); + }, + controller.signal, + ); + const result = completed.result?.result; + if (!result) throw new Error("VidXP completed the search without a result payload."); + setMoments(result.moments); + if (result.moments.length === 0) { + setNotice({ + tone: "warning", + title: "No matching moments", + message: "Try a broader description, another search feature, or the complete indexed library.", + }); + } + setOperation({ status: "idle" }); + } catch (error) { + if (!controller.signal.aborted) { + setMoments([]); + setOperation({ status: "error", message: messageOf(error) }); + } + } + } + + function beginOperation(): AbortController { + abortController.current?.abort(); + const controller = new AbortController(); + abortController.current = controller; + return controller; + } + + function toggleSelected(id: string) { + setSelectedIds((current) => + current.includes(id) + ? current.filter((candidate) => candidate !== id) + : [...current, id], + ); + } + + return ( +
+
+
+ Premiere Pro · Preview +

VidXP Search

+

Index project media and find the moment you remember.

+
+
+ +
+
+
+

VidXP connection

+

Use the API address shown by VidXP Desktop’s app integration service.

+
+ +
+ +
+ Shared-server authentication + +

The token stays in panel memory and is not written to the Premiere project.

+
+ void connect()} + disabled={busy} + > + {connection.status === "connecting" ? "Connecting…" : "Connect"} + + {connection.status === "error" && ( +

{connection.message}

+ )} +
+ +
+
+
+

Premiere media

+

+ {library + ? `${library.projectName}${library.sequenceName ? ` · ${library.sequenceName}` : ""}` + : "Open a Premiere project to browse its bins."} +

+
+ void loadLibrary()} + > + Refresh + +
+ {libraryError &&

{libraryError}

} + {library && ( + <> +
+ + void selectCurrentPremiereItems()} + > + Use current selection + +
+
+ {visibleLibrary.length > 0 ? ( + visibleLibrary.map((node) => ( + + )) + ) : ( +

No matching media.

+ )} +
+

+ {selectedClips.length} selected · {countReadyClips(library.items)} file-backed media items available +

+ + )} +
+ +
+
+
+

Index selected media

+

VidXP expands selected bins, deduplicates clips, and indexes in batches of ten.

+
+
+ setIndexModalities(next)} + emptyMessage="Connect to VidXP to discover indexable features." + /> + void indexSelection()} + > + {operation.status === "indexing" + ? "Indexing…" + : `Index ${selectedClips.length || "selected"} media item${selectedClips.length === 1 ? "" : "s"}`} + +
+ +
+
+
+

Search moments

+

Search one indexed video or the complete active VidXP library.

+
+
+ + + setSearchModalities(next)} + emptyMessage="No searchable features are available yet." + /> + void search()} + > + {operation.status === "searching" ? "Searching…" : "Search indexed media"} + +
+ + {operation.status !== "idle" && ( +
+
+ )} + + {notice && ( +
+
+ {notice.title} +

{notice.message}

+
+ setNotice(undefined)} + > + × + +
+ )} + + +
+ ); +} + +function ConnectionBadge({ state }: { state: ConnectionState }) { + const label = + state.status === "ready" + ? "Connected" + : state.status === "connecting" + ? "Checking" + : state.status === "error" + ? "Unavailable" + : "Not connected"; + return {label}; +} + +function MediaTreeNode({ + node, + selected, + inherited, + onToggle, +}: { + node: PremiereMediaNode; + selected: ReadonlySet; + inherited: boolean; + onToggle: (id: string) => void; +}) { + const effectiveSelected = inherited || selected.has(node.id); + if (node.kind === "clip") { + const disabled = node.availability !== "ready"; + return ( +
+ onToggle(node.id)} + > + + {node.name} + {disabled && {node.availability}} +
+ ); + } + return ( +
+ + event.stopPropagation()} + onCheckedChange={() => onToggle(node.id)} + > + + {node.name} + {countReadyClips(node.children)} + +
+ {node.children.map((child) => ( + + ))} +
+
+ ); +} + +function CapabilityChoices({ + capabilities, + selected, + onChange, + emptyMessage, +}: { + capabilities: CapabilitySummary[]; + selected: string[]; + onChange: (next: string[]) => void; + emptyMessage: string; +}) { + if (capabilities.length === 0) return

{emptyMessage}

; + return ( +
+ {capabilities.map((capability) => ( +
+ + onChange( + selected.includes(capability.name) + ? selected.filter((name) => name !== capability.name) + : [...selected, capability.name], + ) + } + > + {capability.name} + + + {capability.description} + +
+ ))} +
+ ); +} + +function SearchResults({ + moments, + workspace, +}: { + moments: FusedMoment[]; + workspace?: WorkspaceOverview; +}) { + if (moments.length === 0) return null; + const names = new Map( + workspace?.media.map((media) => [media.media_id, media.original_filename]) ?? [], + ); + return ( +
+
+
+

Matching moments

+

{moments.length} ranked result{moments.length === 1 ? "" : "s"}

+
+
+
    + {moments.map((moment) => ( +
  1. +
    {moment.rank}
    +
    + {names.get(moment.media_id) ?? moment.media_id} + {formatTime(moment.start)} – {formatTime(moment.end)} +
    + {moment.modalities.map((modality) => {modality})} +
    +
    + {moment.score.toFixed(3)} +
  2. + ))} +
+
+ ); +} + +function formatTime(seconds: number): string { + const whole = Math.max(0, Math.floor(seconds)); + const hours = Math.floor(whole / 3600); + const minutes = Math.floor((whole % 3600) / 60); + const remainder = whole % 60; + return hours > 0 + ? `${hours}:${minutes.toString().padStart(2, "0")}:${remainder.toString().padStart(2, "0")}` + : `${minutes}:${remainder.toString().padStart(2, "0")}`; +} + +function messageOf(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} diff --git a/premiere/src/ui/components/Spectrum.tsx b/premiere/src/ui/components/Spectrum.tsx new file mode 100644 index 00000000..0b16686b --- /dev/null +++ b/premiere/src/ui/components/Spectrum.tsx @@ -0,0 +1,334 @@ +import { + useEffect, + useRef, + type ReactNode, + type RefObject, +} from "react"; + +type ButtonVariant = + | "cta" + | "primary" + | "secondary" + | "warning" + | "overBackground"; + +interface CommonControlProps { + ariaLabel?: string; + className?: string; + disabled?: boolean; +} + +interface SpectrumButtonProps extends CommonControlProps { + children: ReactNode; + onPress?: (event: Event) => void; + quiet?: boolean; + variant?: ButtonVariant; +} + +interface SpectrumActionButtonProps extends CommonControlProps { + children: ReactNode; + onPress?: (event: Event) => void; + quiet?: boolean; + selected?: boolean; +} + +interface SpectrumTextFieldProps extends CommonControlProps { + label?: string; + onValueChange: (value: string) => void; + placeholder?: string; + quiet?: boolean; + type?: "text" | "number" | "search"; + value: string; +} + +interface SpectrumTextAreaProps extends CommonControlProps { + label?: string; + onValueChange: (value: string) => void; + placeholder?: string; + quiet?: boolean; + value: string; +} + +interface SpectrumCheckboxProps extends CommonControlProps { + checked: boolean; + children?: ReactNode; + indeterminate?: boolean; + onCheckedChange: (checked: boolean) => void; + onPress?: (event: Event) => void; +} + +export function SpectrumButton(props: SpectrumButtonProps) { + if (isCepRuntime()) return ; + return ; +} + +export function SpectrumActionButton(props: SpectrumActionButtonProps) { + if (isCepRuntime()) return ; + return ; +} + +function NativeButton({ + ariaLabel, + children, + className, + disabled = false, + onPress, + quiet = false, + selected = false, +}: SpectrumActionButtonProps) { + return ( + + ); +} + +function SpectrumButtonElement({ + tag, + ariaLabel, + children, + className, + disabled = false, + onPress, + quiet = false, + selected = false, + variant, +}: SpectrumButtonProps & { + selected?: boolean; + tag: "sp-action-button" | "sp-button"; +}) { + const ref = useRef(null); + useUxpProperty(ref, "disabled", disabled); + useUxpProperty(ref, "quiet", quiet); + useUxpProperty(ref, "selected", selected); + useUxpEvent(ref, "click", onPress); + + return tag === "sp-button" ? ( + + {children} + + ) : ( + + {children} + + ); +} + +export function SpectrumTextField(props: SpectrumTextFieldProps) { + return isCepRuntime() ? : ; +} + +function NativeTextField({ + ariaLabel, + className, + disabled = false, + label, + onValueChange, + placeholder, + type = "text", + value, +}: SpectrumTextFieldProps) { + return ( + + ); +} + +function UxpTextField({ + ariaLabel, className, disabled = false, label, onValueChange, placeholder, + quiet = false, type = "text", value, +}: SpectrumTextFieldProps) { + const ref = useRef(null); + useUxpProperty(ref, "disabled", disabled); + useUxpProperty(ref, "quiet", quiet); + useUxpProperty(ref, "value", value); + useUxpEvent(ref, "input", (event) => { + onValueChange(readEventProperty(event, "value", "")); + }); + + return ( + + {label ? {label} : undefined} + + ); +} + +export function SpectrumTextArea(props: SpectrumTextAreaProps) { + return isCepRuntime() ? : ; +} + +function NativeTextArea({ + ariaLabel, + className, + disabled = false, + label, + onValueChange, + placeholder, + quiet = false, + value, +}: SpectrumTextAreaProps) { + void quiet; + return ( +