From 4972dd5694ed41290d9ddd7de3ead55b123f4750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Sirieix?= Date: Tue, 5 May 2026 14:40:11 +0200 Subject: [PATCH] v2.9.4 (#669) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Clément Drouin Co-authored-by: Jean-Baptiste Muscat Co-authored-by: JeroenvdV Co-authored-by: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com> Co-authored-by: Mathias Gesbert Co-authored-by: Paul Cacheux Co-authored-by: Pierre Rossinès Co-authored-by: Simon Van de Kerckhove Co-authored-by: Val <102326092+vdeva@users.noreply.github.com> Co-authored-by: Vincent G <10739306+VinceOPS@users.noreply.github.com> Co-authored-by: allansimon-mistral Co-authored-by: Mistral Vibe --- .github/workflows/build-and-upload.yml | 62 +- AGENTS.md | 1 + CHANGELOG.md | 26 + README.md | 14 +- action.yml | 1 - distribution/zed/extension.toml | 16 +- pyproject.toml | 6 +- tests/acp/test_acp.py | 8 +- tests/acp/test_initialize.py | 4 +- tests/acp/test_session_set_title.py | 363 +++++++++ tests/acp/test_telemetry_notification.py | 32 + tests/acp/test_utils.py | 9 +- tests/autocompletion/test_path_prompt.py | 18 + tests/cli/test_commands.py | 10 + tests/cli/test_rename_command.py | 162 ++++ tests/core/test_telemetry_send.py | 75 ++ tests/e2e/test_cli_tui_fresh_install.py | 110 +++ tests/e2e/test_cli_tui_tool_approval.py | 4 +- tests/session/test_saved_sessions.py | 178 +++++ tests/session/test_session_loader.py | 30 + tests/session/test_session_logger.py | 88 +++ ...ort_command_hidden_for_non_pro_account.svg | 104 +-- ...leport_command_visible_for_pro_account.svg | 105 ++- tests/test_agent_observer_streaming.py | 45 ++ tests/test_agent_stats.py | 1 + tests/test_approve_always_permanent.py | 120 +++ tests/test_cli_programmatic_preload.py | 4 +- tests/tools/test_connectors.py | 207 ++++- tests/tools/test_ui_bash_execution.py | 80 ++ uv.lock | 746 +++++++++--------- vibe/__init__.py | 2 +- vibe/acp/acp_agent_loop.py | 158 +++- vibe/acp/utils.py | 44 +- vibe/cli/cli.py | 1 + vibe/cli/commands.py | 7 +- vibe/cli/textual_ui/app.py | 237 +++++- vibe/cli/textual_ui/app.tcss | 17 +- vibe/cli/textual_ui/widgets/approval_app.py | 66 +- vibe/cli/textual_ui/widgets/messages.py | 81 +- vibe/core/agent_loop.py | 24 +- vibe/core/autocompletion/path_prompt.py | 9 +- .../autocompletion/path_prompt_adapter.py | 2 +- vibe/core/config/_settings.py | 16 +- vibe/core/programmatic.py | 4 +- vibe/core/session/saved_sessions.py | 58 ++ vibe/core/session/session_logger.py | 37 +- vibe/core/telemetry/send.py | 16 + .../tools/connectors/connector_registry.py | 309 ++++---- vibe/core/types.py | 2 + vibe/whats_new.md | 15 +- 50 files changed, 2892 insertions(+), 842 deletions(-) create mode 100644 tests/acp/test_session_set_title.py create mode 100644 tests/autocompletion/test_path_prompt.py create mode 100644 tests/cli/test_rename_command.py create mode 100644 tests/e2e/test_cli_tui_fresh_install.py create mode 100644 tests/session/test_saved_sessions.py create mode 100644 tests/test_approve_always_permanent.py create mode 100644 vibe/core/session/saved_sessions.py diff --git a/.github/workflows/build-and-upload.yml b/.github/workflows/build-and-upload.yml index 3110785..5f0be12 100644 --- a/.github/workflows/build-and-upload.yml +++ b/.github/workflows/build-and-upload.yml @@ -20,47 +20,25 @@ jobs: id: set-matrix run: | # Linux: manylinux_2_28 containers → binary works on glibc >= 2.28 (RHEL 8+, Ubuntu 20.04+) - if [[ "${{ github.repository }}" == "mistralai/mistral-vibe" ]]; then - matrix='{ - "include": [ - {"runner": "ubuntu-22.04", "os": "linux", "arch": "x86_64", "container": "quay.io/pypa/manylinux_2_28_x86_64"}, - {"runner": "ubuntu-22.04-arm", "os": "linux", "arch": "aarch64", "container": "quay.io/pypa/manylinux_2_28_aarch64"}, - {"runner": "macos-15-intel", "os": "darwin", "arch": "x86_64"}, - {"runner": "macos-14", "os": "darwin", "arch": "aarch64"}, - {"runner": "windows-2022", "os": "windows", "arch": "x86_64"}, - {"runner": "windows-11-arm", "os": "windows", "arch": "aarch64"} - ] - }' - smoke_matrix='{ - "include": [ - {"runner": "ubuntu-24.04", "os": "linux", "arch": "x86_64"}, - {"runner": "ubuntu-24.04", "os": "linux", "arch": "x86_64", "tag": "old-glibc", "container": "almalinux:8"}, - {"runner": "ubuntu-24.04-arm", "os": "linux", "arch": "aarch64"}, - {"runner": "macos-15", "os": "darwin", "arch": "aarch64"}, - {"runner": "macos-15-intel", "os": "darwin", "arch": "x86_64"}, - {"runner": "windows-latest", "os": "windows", "arch": "x86_64"}, - {"runner": "windows-11-arm", "os": "windows", "arch": "aarch64"} - ] - }' - else # skip ARM Linux/Windows (runners not available on non public repos) - matrix='{ - "include": [ - {"runner": "ubuntu-22.04", "os": "linux", "arch": "x86_64", "container": "quay.io/pypa/manylinux_2_28_x86_64"}, - {"runner": "macos-15-intel", "os": "darwin", "arch": "x86_64"}, - {"runner": "macos-14", "os": "darwin", "arch": "aarch64"}, - {"runner": "windows-2022", "os": "windows", "arch": "x86_64"} - ] - }' - smoke_matrix='{ - "include": [ - {"runner": "ubuntu-24.04", "os": "linux", "arch": "x86_64"}, - {"runner": "ubuntu-24.04", "os": "linux", "arch": "x86_64", "tag": "old-glibc", "container": "almalinux:8"}, - {"runner": "macos-15", "os": "darwin", "arch": "aarch64"}, - {"runner": "macos-15-intel", "os": "darwin", "arch": "x86_64"}, - {"runner": "windows-latest", "os": "windows", "arch": "x86_64"} - ] - }' - fi + matrix='{ + "include": [ + {"runner": "ubuntu-22.04", "os": "linux", "arch": "x86_64", "container": "quay.io/pypa/manylinux_2_28_x86_64"}, + {"runner": "ubuntu-22.04-arm", "os": "linux", "arch": "aarch64", "container": "quay.io/pypa/manylinux_2_28_aarch64"}, + {"runner": "macos-15-intel", "os": "darwin", "arch": "x86_64"}, + {"runner": "macos-14", "os": "darwin", "arch": "aarch64"}, + {"runner": "windows-2022", "os": "windows", "arch": "x86_64"} + ] + }' + smoke_matrix='{ + "include": [ + {"runner": "ubuntu-24.04", "os": "linux", "arch": "x86_64"}, + {"runner": "ubuntu-24.04", "os": "linux", "arch": "x86_64", "tag": "old-glibc", "container": "almalinux:8"}, + {"runner": "ubuntu-24.04-arm", "os": "linux", "arch": "aarch64"}, + {"runner": "macos-15", "os": "darwin", "arch": "aarch64"}, + {"runner": "macos-15-intel", "os": "darwin", "arch": "x86_64"}, + {"runner": "windows-latest", "os": "windows", "arch": "x86_64"} + ] + }' echo "matrix=$(echo $matrix | jq -c .)" >> $GITHUB_OUTPUT echo "smoke_matrix=$(echo $smoke_matrix | jq -c .)" >> $GITHUB_OUTPUT @@ -75,8 +53,6 @@ jobs: container: ${{ matrix.container || '' }} steps: - - name: echo - run: echo github.repository=${{ github.repository }} - name: Checkout repository uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 diff --git a/AGENTS.md b/AGENTS.md index 8220f78..1f3bce3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,6 +34,7 @@ Always go through `uv` — never invoke bare `python` or `pip`. - Enums: `StrEnum` / `IntEnum` with `auto()` and UPPERCASE members. For type-mixing, the mix-in type comes before `Enum` in the bases. Add methods or `@property` rather than parallel lookup tables. - Write declarative, minimalist code: express intent, drop boilerplate. - Never call a private method from outside of it's class +- Avoid comments and docstrings, except for when there's a hard to spot corner case ## Typing & imports diff --git a/CHANGELOG.md b/CHANGELOG.md index a0ed2eb..3839799 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,32 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.9.4] - 2026-05-05 + +### Added + +- `/rename` command to rename the current session +- `feat: vibe.at_mention_inserted` telemetry event +- "Always allow" tool permissions persist across sessions +- Eager agent-loop warmup so `vibe.ready` telemetry fires sooner + +### Changed + +- `bash` (`!command`) bang commands run via async subprocess for better latency +- Bumped `mistral` SDK to 2.4.4 +- Bumped `cryptography` to address upstream CVEs + +### Fixed + +- Preserve `non_retryable` flag on exceptions raised through `_chat` / `_chat_streaming`, so callers driving the agent loop from a Temporal activity can signal "do not retry" +- `/clear` no longer chains `parent_session_id` to the previous session +- `vibe.new_session` telemetry no longer fires when resuming a session + +### Removed + +- Windows ARM build artifacts (no longer published; required to bump `cryptography`) + + ## [2.9.3] - 2026-04-30 ### Added diff --git a/README.md b/README.md index 13ac149..9a21110 100644 --- a/README.md +++ b/README.md @@ -219,7 +219,7 @@ Simply run `vibe` to enter the interactive chat loop. - **Tool Output Toggle**: Press `Ctrl+O` to toggle the tool output view. - **Todo View Toggle**: Press `Ctrl+T` to toggle the todo list view. - **Debug Console**: Press `Ctrl+\` to toggle the debug console. -- **Auto-Approve Toggle**: Press `Shift+Tab` to toggle auto-approve mode on/off. +- **Agent Selection**: Press `Shift+Tab` to cycle through agents (default, plan, ...). You can start Vibe with a prompt using the following command: @@ -227,8 +227,6 @@ You can start Vibe with a prompt using the following command: vibe "Refactor the main function in cli/main.py to be more modular." ``` -**Note**: The `--auto-approve` flag automatically approves all tool executions without prompting. In interactive mode, you can also toggle auto-approve on/off using `Shift+Tab`. - ### Trust Folder System Vibe includes a trust folder system to ensure you only run the agent in directories you trust. When you first run Vibe in a new directory which contains a `.vibe` subfolder, it may ask you to confirm whether you trust the folder. @@ -412,7 +410,15 @@ Vibe supports multiple ways to configure your API keys: ### Custom System Prompts -You can create custom system prompts to replace the default one (`prompts/cli.md`). Create a markdown file in the `~/.vibe/prompts/` directory with your custom prompt content. +You can create `AGENTS.md` files to add custom instructions. You can also replace the entire system prompt. + +Place `AGENTS.md` files in: +- `~/.vibe/AGENTS.md` — user-level instructions for all projects +- Project directories — project-specific instructions, loaded from cwd up to the trust root + +Priority: closer directories override more distant ones. Instructions in `AGENTS.md` override the default system prompt. Files are only loaded for trusted folders. + +Custom system prompts entirely replace the default one (`prompts/cli.md`). Create a markdown file in the `~/.vibe/prompts/` directory with your custom prompt content. To use a custom system prompt, set the `system_prompt_id` in your configuration to match the filename (without the `.md` extension): diff --git a/action.yml b/action.yml index 711693a..0ba333f 100644 --- a/action.yml +++ b/action.yml @@ -56,5 +56,4 @@ runs: run: | cd "${{ github.workspace }}" uv run --directory "${{ github.action_path }}" vibe \ - --auto-approve \ -p "$PROMPT_INPUT" diff --git a/distribution/zed/extension.toml b/distribution/zed/extension.toml index fdd5d70..962ee3d 100644 --- a/distribution/zed/extension.toml +++ b/distribution/zed/extension.toml @@ -1,7 +1,7 @@ id = "mistral-vibe" name = "Mistral Vibe" description = "Mistral's open-source coding assistant" -version = "2.9.3" +version = "2.9.4" schema_version = 1 authors = ["Mistral AI"] repository = "https://github.com/mistralai/mistral-vibe" @@ -11,25 +11,21 @@ name = "Mistral Vibe" icon = "./icons/mistral_vibe.svg" [agent_servers.mistral-vibe.targets.darwin-aarch64] -archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.9.3/vibe-acp-darwin-aarch64-2.9.3.zip" +archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.9.4/vibe-acp-darwin-aarch64-2.9.4.zip" cmd = "./vibe-acp" [agent_servers.mistral-vibe.targets.darwin-x86_64] -archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.9.3/vibe-acp-darwin-x86_64-2.9.3.zip" +archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.9.4/vibe-acp-darwin-x86_64-2.9.4.zip" cmd = "./vibe-acp" [agent_servers.mistral-vibe.targets.linux-aarch64] -archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.9.3/vibe-acp-linux-aarch64-2.9.3.zip" +archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.9.4/vibe-acp-linux-aarch64-2.9.4.zip" cmd = "./vibe-acp" [agent_servers.mistral-vibe.targets.linux-x86_64] -archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.9.3/vibe-acp-linux-x86_64-2.9.3.zip" +archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.9.4/vibe-acp-linux-x86_64-2.9.4.zip" cmd = "./vibe-acp" -[agent_servers.mistral-vibe.targets.windows-aarch64] -archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.9.3/vibe-acp-windows-aarch64-2.9.3.zip" -cmd = "./vibe-acp.exe" - [agent_servers.mistral-vibe.targets.windows-x86_64] -archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.9.3/vibe-acp-windows-x86_64-2.9.3.zip" +archive = "https://github.com/mistralai/mistral-vibe/releases/download/v2.9.4/vibe-acp-windows-x86_64-2.9.4.zip" cmd = "./vibe-acp.exe" diff --git a/pyproject.toml b/pyproject.toml index 4a6bbc8..f8adcf2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "mistral-vibe" -version = "2.9.3" +version = "2.9.4" description = "Minimal CLI coding agent by Mistral" readme = "README.md" requires-python = ">=3.12" @@ -31,7 +31,7 @@ dependencies = [ "anyio>=4.12.0", "cachetools>=5.5.0", "charset-normalizer>=3.4.4", - "cryptography>=44.0.0,<=46.0.3", + "cryptography>=44.0.0", "gitpython>=3.1.46", "giturlparse>=0.14.0", "google-auth>=2.0.0", @@ -40,7 +40,7 @@ dependencies = [ "keyring>=25.6.0", "markdownify>=1.2.2", "mcp>=1.14.0", - "mistralai==2.3.2", + "mistralai==2.4.4", "opentelemetry-api>=1.39.1", "opentelemetry-exporter-otlp-proto-http>=1.39.1", "opentelemetry-sdk>=1.39.1", diff --git a/tests/acp/test_acp.py b/tests/acp/test_acp.py index 7c5bdf3..32fe5ac 100644 --- a/tests/acp/test_acp.py +++ b/tests/acp/test_acp.py @@ -565,7 +565,7 @@ async def start_session_with_request_permission( assert isinstance(last_response, RequestPermissionJsonRpcRequest) assert last_response.params is not None - assert len(last_response.params.options) == 3 + assert len(last_response.params.options) == 4 return last_response @@ -778,13 +778,15 @@ class TestToolCallStructure: ) assert permission_request.params is not None - # Verify "Allow always" option includes the pattern label + # Verify granular permissions are passed in field_meta allow_always = next( o for o in permission_request.params.options if o.option_id == ToolOption.ALLOW_ALWAYS ) - assert "npm install *" in allow_always.name + assert allow_always.name == "Allow for remainder of this session" + assert allow_always.field_meta is not None + assert "required_permissions" in allow_always.field_meta @pytest.mark.skip(reason="Long running tool call updates are not implemented yet") @pytest.mark.asyncio diff --git a/tests/acp/test_initialize.py b/tests/acp/test_initialize.py index 31bbc01..f8d7b16 100644 --- a/tests/acp/test_initialize.py +++ b/tests/acp/test_initialize.py @@ -34,7 +34,7 @@ class TestACPInitialize: ), ) assert response.agent_info == Implementation( - name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.9.3" + name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.9.4" ) assert response.auth_methods == [] @@ -62,7 +62,7 @@ class TestACPInitialize: ), ) assert response.agent_info == Implementation( - name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.9.3" + name="@mistralai/mistral-vibe", title="Mistral Vibe", version="2.9.4" ) assert response.auth_methods is not None diff --git a/tests/acp/test_session_set_title.py b/tests/acp/test_session_set_title.py new file mode 100644 index 0000000..7109de1 --- /dev/null +++ b/tests/acp/test_session_set_title.py @@ -0,0 +1,363 @@ +from __future__ import annotations + +import json +from pathlib import Path + +from acp.schema import SessionInfoUpdate +import pytest +import tomli_w + +from tests.conftest import build_test_vibe_config, get_base_config +from tests.stubs.fake_backend import FakeBackend +from tests.stubs.fake_client import FakeClient +from vibe.acp.acp_agent_loop import VibeAcpAgentLoop +from vibe.acp.exceptions import InternalError, InvalidRequestError, SessionNotFoundError +from vibe.core.agent_loop import AgentLoop +from vibe.core.config import ModelConfig, SessionLoggingConfig + + +@pytest.fixture +def acp_agent_with_session_config( + backend: FakeBackend, temp_session_dir: Path, monkeypatch: pytest.MonkeyPatch +) -> tuple[VibeAcpAgentLoop, FakeClient]: + session_config = SessionLoggingConfig( + save_dir=str(temp_session_dir), session_prefix="session", enabled=True + ) + config = build_test_vibe_config( + active_model="devstral-latest", + models=[ + ModelConfig( + name="devstral-latest", provider="mistral", alias="devstral-latest" + ) + ], + session_logging=session_config, + ) + + class PatchedAgentLoop(AgentLoop): + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **{**kwargs, "backend": backend}) + self._base_config = config + self.agent_manager.invalidate_config() + + monkeypatch.setattr("vibe.acp.acp_agent_loop.AgentLoop", PatchedAgentLoop) + monkeypatch.setattr(VibeAcpAgentLoop, "_load_config", lambda self: config) + monkeypatch.setattr( + "vibe.acp.acp_agent_loop.VibeConfig.load", lambda *args, **kwargs: config + ) + + vibe_acp_agent = VibeAcpAgentLoop() + client = FakeClient() + vibe_acp_agent.on_connect(client) + client.on_connect(vibe_acp_agent) + + return vibe_acp_agent, client + + +class TestSessionSetTitle: + @pytest.mark.asyncio + async def test_updates_live_unsaved_session_title( + self, acp_agent_with_session_config: tuple[VibeAcpAgentLoop, FakeClient] + ) -> None: + acp_agent, client = acp_agent_with_session_config + + response = await acp_agent.new_session(cwd=str(Path.cwd()), mcp_servers=[]) + assert response is not None + + result = await acp_agent.ext_method( + "session/set_title", + {"sessionId": response.session_id, "title": "Manual title"}, + ) + + assert result == {} + + session = acp_agent.sessions[response.session_id] + metadata = session.agent_loop.session_logger.session_metadata + assert metadata is not None + assert metadata.title == "Manual title" + assert metadata.title_source == "manual" + assert metadata.end_time is None + assert not session.agent_loop.session_logger.metadata_filepath.exists() + + info_updates = [ + notification.update + for notification in client._session_updates + if isinstance(notification.update, SessionInfoUpdate) + ] + assert len(info_updates) == 1 + assert info_updates[0].title == "Manual title" + assert info_updates[0].updated_at is None + + @pytest.mark.asyncio + async def test_updates_live_saved_session_title( + self, + acp_agent_with_session_config: tuple[VibeAcpAgentLoop, FakeClient], + temp_session_dir: Path, + create_test_session, + ) -> None: + acp_agent, client = acp_agent_with_session_config + + saved_session_id = "saved-session-12345678" + acp_session_id = saved_session_id[:8] + cwd = str(Path.cwd()) + session_dir = create_test_session( + temp_session_dir, + saved_session_id, + cwd, + title="Old title", + end_time="2024-01-01T12:05:00Z", + ) + + await acp_agent.load_session(cwd=cwd, mcp_servers=[], session_id=acp_session_id) + client._session_updates.clear() + + result = await acp_agent.ext_method( + "session/set_title", + {"sessionId": saved_session_id, "title": "Renamed session"}, + ) + + assert result == {} + + session = acp_agent.sessions[acp_session_id] + metadata = session.agent_loop.session_logger.session_metadata + assert metadata is not None + assert metadata.title == "Renamed session" + assert metadata.title_source == "manual" + assert metadata.end_time == "2024-01-01T12:05:00Z" + assert session.agent_loop.session_id == saved_session_id + + saved_metadata = json.loads((session_dir / "meta.json").read_text()) + assert saved_metadata["title"] == "Renamed session" + assert saved_metadata["title_source"] == "manual" + assert saved_metadata["end_time"] == "2024-01-01T12:05:00Z" + + info_updates = [ + notification + for notification in client._session_updates + if isinstance(notification.update, SessionInfoUpdate) + ] + assert len(info_updates) == 1 + assert info_updates[0].session_id == acp_session_id + assert info_updates[0].update.title == "Renamed session" + assert info_updates[0].update.updated_at == metadata.end_time + assert saved_metadata["end_time"] == info_updates[0].update.updated_at + + @pytest.mark.asyncio + async def test_loaded_session_title_is_unchanged_when_persist_fails( + self, + acp_agent_with_session_config: tuple[VibeAcpAgentLoop, FakeClient], + temp_session_dir: Path, + create_test_session, + monkeypatch: pytest.MonkeyPatch, + ) -> None: + acp_agent, client = acp_agent_with_session_config + + saved_session_id = "saved-session-12345678" + acp_session_id = saved_session_id[:8] + cwd = str(Path.cwd()) + create_test_session( + temp_session_dir, + saved_session_id, + cwd, + title="Old title", + end_time="2024-01-01T12:05:00Z", + ) + + await acp_agent.load_session(cwd=cwd, mcp_servers=[], session_id=acp_session_id) + client._session_updates.clear() + + async def fail_persist(*args, **kwargs): + raise ValueError("Cannot rewrite metadata") + + monkeypatch.setattr( + "vibe.acp.acp_agent_loop.update_saved_session_title_at_path", fail_persist + ) + + with pytest.raises(InternalError): + await acp_agent.ext_method( + "session/set_title", + {"sessionId": saved_session_id, "title": "Renamed session"}, + ) + + session = acp_agent.sessions[acp_session_id] + metadata = session.agent_loop.session_logger.session_metadata + assert metadata is not None + assert metadata.title == "Old title" + assert metadata.title_source == "auto" + assert not [ + notification + for notification in client._session_updates + if isinstance(notification.update, SessionInfoUpdate) + ] + + @pytest.mark.asyncio + async def test_updates_saved_but_not_loaded_session_title( + self, + acp_agent_with_session_config: tuple[VibeAcpAgentLoop, FakeClient], + temp_session_dir: Path, + create_test_session, + ) -> None: + acp_agent, client = acp_agent_with_session_config + + session_id = "offline-session-12345678" + cwd = str(Path.cwd()) + session_dir = create_test_session( + temp_session_dir, + session_id, + cwd, + title="Old title", + end_time="2024-01-01T12:05:00Z", + ) + + result = await acp_agent.ext_method( + "session/set_title", {"sessionId": session_id, "title": "Renamed session"} + ) + + assert result == {} + + saved_metadata = json.loads((session_dir / "meta.json").read_text()) + assert saved_metadata["title"] == "Renamed session" + assert saved_metadata["title_source"] == "manual" + + info_updates = [ + notification + for notification in client._session_updates + if isinstance(notification.update, SessionInfoUpdate) + ] + assert len(info_updates) == 1 + assert info_updates[0].session_id == session_id + assert info_updates[0].update.title == "Renamed session" + assert info_updates[0].update.updated_at == saved_metadata["end_time"] + + @pytest.mark.asyncio + async def test_updates_saved_session_with_configured_log_dir_without_api_key( + self, + config_dir: Path, + temp_session_dir: Path, + create_test_session, + monkeypatch: pytest.MonkeyPatch, + ) -> None: + config = { + **get_base_config(), + "session_logging": { + "enabled": True, + "save_dir": str(temp_session_dir), + "session_prefix": "session", + }, + } + monkeypatch.setenv("VIBE_HOME", str(config_dir)) + (config_dir / "config.toml").write_text(tomli_w.dumps(config), encoding="utf-8") + monkeypatch.delenv("MISTRAL_API_KEY", raising=False) + + session_id = "offline-session-12345678" + session_dir = create_test_session( + temp_session_dir, + session_id, + str(Path.cwd()), + title="Old title", + end_time="2024-01-01T12:05:00Z", + ) + acp_agent = VibeAcpAgentLoop() + client = FakeClient() + acp_agent.on_connect(client) + client.on_connect(acp_agent) + + result = await acp_agent.ext_method( + "session/set_title", + {"sessionId": session_id, "title": "Renamed without key"}, + ) + + assert result == {} + saved_metadata = json.loads((session_dir / "meta.json").read_text()) + assert saved_metadata["title"] == "Renamed without key" + assert saved_metadata["title_source"] == "manual" + + info_updates = [ + notification + for notification in client._session_updates + if isinstance(notification.update, SessionInfoUpdate) + ] + assert len(info_updates) == 1 + assert info_updates[0].session_id == session_id + assert info_updates[0].update.title == "Renamed without key" + + @pytest.mark.asyncio + async def test_raises_on_invalid_params( + self, acp_agent_with_session_config: tuple[VibeAcpAgentLoop, FakeClient] + ) -> None: + acp_agent, _client = acp_agent_with_session_config + + with pytest.raises(InvalidRequestError): + await acp_agent.ext_method( + "session/set_title", {"title": "Missing session id"} + ) + + with pytest.raises(InvalidRequestError): + await acp_agent.ext_method( + "session/set_title", {"sessionId": "missing-title-session"} + ) + + with pytest.raises(InvalidRequestError): + await acp_agent.ext_method( + "session/set_title", + {"sessionId": "blank-title-session", "title": " "}, + ) + + with pytest.raises(InvalidRequestError): + await acp_agent.ext_method( + "session/set_title", {"sessionId": " ", "title": "Blank session id"} + ) + + with pytest.raises(InvalidRequestError): + await acp_agent.ext_method( + "session/set_title", + {"savedSessionId": "saved-session", "title": "Unsupported target"}, + ) + + @pytest.mark.asyncio + async def test_session_id_falls_back_to_saved_session_lookup( + self, + acp_agent_with_session_config: tuple[VibeAcpAgentLoop, FakeClient], + temp_session_dir: Path, + create_test_session, + ) -> None: + acp_agent, client = acp_agent_with_session_config + + session_id = "saved-session-12345678" + cwd = str(Path.cwd()) + session_dir = create_test_session( + temp_session_dir, + session_id, + cwd, + title="Old title", + end_time="2024-01-01T12:05:00Z", + ) + + result = await acp_agent.ext_method( + "session/set_title", {"sessionId": session_id, "title": "Renamed session"} + ) + + assert result == {} + saved_metadata = json.loads((session_dir / "meta.json").read_text()) + assert saved_metadata["title"] == "Renamed session" + assert saved_metadata["title_source"] == "manual" + + info_updates = [ + notification + for notification in client._session_updates + if isinstance(notification.update, SessionInfoUpdate) + ] + assert len(info_updates) == 1 + assert info_updates[0].session_id == session_id + assert info_updates[0].update.title == "Renamed session" + + @pytest.mark.asyncio + async def test_raises_when_session_cannot_be_found( + self, acp_agent_with_session_config: tuple[VibeAcpAgentLoop, FakeClient] + ) -> None: + acp_agent, _client = acp_agent_with_session_config + + with pytest.raises(SessionNotFoundError): + await acp_agent.ext_method( + "session/set_title", + {"sessionId": "missing-session", "title": "Renamed session"}, + ) diff --git a/tests/acp/test_telemetry_notification.py b/tests/acp/test_telemetry_notification.py index 9259946..aa4ff9a 100644 --- a/tests/acp/test_telemetry_notification.py +++ b/tests/acp/test_telemetry_notification.py @@ -1,6 +1,7 @@ from __future__ import annotations from pathlib import Path +from typing import Any from unittest.mock import patch import pytest @@ -42,6 +43,37 @@ class TestTelemetryNotification: assert telemetry_events == [] + @pytest.mark.asyncio + async def test_at_mention_inserted_dispatches_telemetry( + self, acp_agent_loop: VibeAcpAgentLoop, telemetry_events: list[dict[str, Any]] + ) -> None: + session = await acp_agent_loop.new_session(cwd=str(Path.cwd()), mcp_servers=[]) + telemetry_events.clear() + + await acp_agent_loop.ext_notification( + "telemetry/send", + { + "event": "vibe.at_mention_inserted", + "session_id": session.session_id, + "properties": { + "nb_mentions": 2, + "context_types": {"file": 1, "folder": 1}, + "file_extensions": {".py": 1}, + "message_id": "msg-abc", + }, + }, + ) + + at_mention_events = [ + e for e in telemetry_events if e["event_name"] == "vibe.at_mention_inserted" + ] + assert len(at_mention_events) == 1 + props = at_mention_events[0]["properties"] + assert props["nb_mentions"] == 2 + assert props["context_types"] == {"file": 1, "folder": 1} + assert props["file_extensions"] == {".py": 1} + assert props["message_id"] == "msg-abc" + @pytest.mark.asyncio async def test_raises_on_invalid_params( self, acp_agent_loop: VibeAcpAgentLoop diff --git a/tests/acp/test_utils.py b/tests/acp/test_utils.py index 4a938ab..49f0fbb 100644 --- a/tests/acp/test_utils.py +++ b/tests/acp/test_utils.py @@ -81,10 +81,13 @@ class TestBuildPermissionOptions: ] result = build_permission_options(permissions) - assert len(result) == 3 + assert len(result) == 4 allow_always = next(o for o in result if o.option_id == ToolOption.ALLOW_ALWAYS) - assert "npm install *" in allow_always.name assert "session" in allow_always.name.lower() + allow_permanent = next( + o for o in result if o.option_id == ToolOption.ALLOW_ALWAYS_PERMANENT + ) + assert "Always allow" == allow_permanent.name def test_allow_always_has_field_meta(self) -> None: permissions = [ @@ -118,6 +121,6 @@ class TestBuildPermissionOptions: allow_once = next(o for o in result if o.option_id == ToolOption.ALLOW_ONCE) reject_once = next(o for o in result if o.option_id == ToolOption.REJECT_ONCE) assert allow_once.name == "Allow once" - assert reject_once.name == "Reject once" + assert reject_once.name == "Deny" assert allow_once.field_meta is None assert reject_once.field_meta is None diff --git a/tests/autocompletion/test_path_prompt.py b/tests/autocompletion/test_path_prompt.py new file mode 100644 index 0000000..01e48fd --- /dev/null +++ b/tests/autocompletion/test_path_prompt.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from pathlib import Path + +from vibe.core.autocompletion.path_prompt import build_path_prompt_payload + + +def test_deduplicates_same_file_mentioned_twice(tmp_path: Path) -> None: + readme = tmp_path / "README.md" + readme.write_text("hello", encoding="utf-8") + + payload = build_path_prompt_payload( + "See @README.md and again @README.md", base_dir=tmp_path + ) + + assert len(payload.resources) == 1 + assert payload.resources[0].path == readme + assert len(payload.all_resources) == 2 diff --git a/tests/cli/test_commands.py b/tests/cli/test_commands.py index 3a7bc2a..372542c 100644 --- a/tests/cli/test_commands.py +++ b/tests/cli/test_commands.py @@ -102,6 +102,16 @@ class TestCommandRegistry: _, cmd, _ = result assert cmd.handler == "_show_session_picker" + def test_rename_command_registration(self) -> None: + registry = CommandRegistry() + assert registry.get_command_name("/rename") == "rename" + assert registry.get_command_name("/title") is None + result = registry.parse_command("/rename Better title") + assert result is not None + _, cmd, cmd_args = result + assert cmd.handler == "_rename_session" + assert cmd_args == "Better title" + def test_parse_command_keeps_args_for_no_arg_commands(self) -> None: registry = CommandRegistry() result = registry.parse_command("/help extra") diff --git a/tests/cli/test_rename_command.py b/tests/cli/test_rename_command.py new file mode 100644 index 0000000..165c7bf --- /dev/null +++ b/tests/cli/test_rename_command.py @@ -0,0 +1,162 @@ +from __future__ import annotations + +import json +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest + +from tests.conftest import build_test_vibe_app, build_test_vibe_config +from vibe.cli.textual_ui.widgets.messages import ErrorMessage, UserCommandMessage +from vibe.core.config import SessionLoggingConfig + + +def _enabled_session_config(save_dir: Path) -> SessionLoggingConfig: + return SessionLoggingConfig(enabled=True, save_dir=str(save_dir)) + + +@pytest.mark.asyncio +async def test_rename_command_updates_live_unsaved_session_title( + tmp_path: Path, +) -> None: + config = build_test_vibe_config(session_logging=_enabled_session_config(tmp_path)) + app = build_test_vibe_app(config=config) + + async with app.run_test() as pilot: + handled = await app._handle_command("/rename Manual title") + await pilot.pause() + messages = app.query(UserCommandMessage) + assert any( + message._content == 'Session renamed to "Manual title".' + for message in messages + ) + + assert handled is True + + metadata = app.agent_loop.session_logger.session_metadata + assert metadata is not None + assert metadata.title == "Manual title" + assert metadata.title_source == "manual" + assert not app.agent_loop.session_logger.metadata_filepath.exists() + + +@pytest.mark.asyncio +async def test_rename_command_persists_existing_session_metadata( + tmp_path: Path, +) -> None: + config = build_test_vibe_config(session_logging=_enabled_session_config(tmp_path)) + app = build_test_vibe_app(config=config) + logger = app.agent_loop.session_logger + assert logger.session_dir is not None + assert logger.session_metadata is not None + + logger.session_dir.mkdir(parents=True) + existing_metadata = { + **logger.session_metadata.model_dump(), + "end_time": "2024-01-01T12:05:00Z", + "extra_field": "preserved", + } + logger.metadata_filepath.write_text(json.dumps(existing_metadata), encoding="utf-8") + + async with app.run_test() as pilot: + handled = await app._handle_command("/rename Persisted title") + await pilot.pause() + + assert handled is True + + metadata = logger.session_metadata + assert metadata is not None + assert metadata.title == "Persisted title" + assert metadata.title_source == "manual" + + saved_metadata = json.loads(logger.metadata_filepath.read_text()) + assert saved_metadata["title"] == "Persisted title" + assert saved_metadata["title_source"] == "manual" + assert saved_metadata["end_time"] == "2024-01-01T12:05:00Z" + assert saved_metadata["extra_field"] == "preserved" + + +@pytest.mark.asyncio +async def test_resume_picker_shows_renamed_session_title( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + config = build_test_vibe_config( + session_logging=_enabled_session_config(tmp_path), vibe_code_enabled=False + ) + app = build_test_vibe_app(config=config) + logger = app.agent_loop.session_logger + assert logger.session_dir is not None + assert logger.session_metadata is not None + + logger.session_dir.mkdir(parents=True) + existing_metadata = { + **logger.session_metadata.model_dump(), + "end_time": "2024-01-01T12:05:00Z", + "total_messages": 1, + } + logger.metadata_filepath.write_text(json.dumps(existing_metadata), encoding="utf-8") + logger.messages_filepath.write_text( + '{"role": "user", "content": "Original prompt"}\n', encoding="utf-8" + ) + + captured_picker = None + + async def capture_picker(picker): + nonlocal captured_picker + captured_picker = picker + + monkeypatch.setattr(app, "_switch_from_input", capture_picker) + + async with app.run_test() as pilot: + handled = await app._handle_command("/rename New title") + await app._show_session_picker() + await pilot.pause() + + assert handled is True + assert captured_picker is not None + assert captured_picker._latest_messages[f"local:{logger.session_id}"] == "New title" + + +@pytest.mark.asyncio +async def test_rename_command_requires_title(tmp_path: Path) -> None: + config = build_test_vibe_config(session_logging=_enabled_session_config(tmp_path)) + app = build_test_vibe_app(config=config) + + async with app.run_test() as pilot: + handled = await app._handle_command("/rename") + await pilot.pause() + errors = app.query(ErrorMessage) + assert any(error._error == "Usage: /rename " for error in errors) + + assert handled is True + + +@pytest.mark.asyncio +async def test_rename_command_is_intercepted_for_remote_sessions( + tmp_path: Path, +) -> None: + config = build_test_vibe_config(session_logging=_enabled_session_config(tmp_path)) + app = build_test_vibe_app(config=config) + + async with app.run_test() as pilot: + with patch( + "vibe.cli.textual_ui.remote.remote_session_manager.RemoteEventsSource" + ) as MockSource: + remote_source = MagicMock() + remote_source.session_id = "remote-session-id" + remote_source.is_terminated = False + remote_source.is_waiting_for_input = False + MockSource.return_value = remote_source + + await app._remote_manager.attach( + session_id="remote-session-id", config=config + ) + handled = await app._handle_command("/rename Remote title") + await pilot.pause() + errors = app.query(ErrorMessage) + assert any( + error._error == "Renaming is only supported for local sessions." + for error in errors + ) + + assert handled is True diff --git a/tests/core/test_telemetry_send.py b/tests/core/test_telemetry_send.py index 3b3f1d2..7a2683b 100644 --- a/tests/core/test_telemetry_send.py +++ b/tests/core/test_telemetry_send.py @@ -249,6 +249,81 @@ class TestTelemetryClient: assert telemetry_events[0]["event_name"] == "vibe.user_cancelled_action" assert telemetry_events[0]["properties"]["action"] == "interrupt_agent" + def test_send_at_mention_inserted_payload( + self, telemetry_events: list[dict[str, Any]] + ) -> None: + config = build_test_vibe_config(enable_telemetry=True) + client = TelemetryClient(config_getter=lambda: config) + + client.send_at_mention_inserted( + nb_mentions=2, + context_types={"file": 1, "folder": 1}, + file_extensions={".py": 1}, + message_id="msg-123", + ) + + assert len(telemetry_events) == 1 + assert telemetry_events[0]["event_name"] == "vibe.at_mention_inserted" + assert telemetry_events[0]["properties"] == { + "nb_mentions": 2, + "context_types": {"file": 1, "folder": 1}, + "file_extensions": {".py": 1}, + "message_id": "msg-123", + } + + def test_send_at_mention_inserted_null_file_extensions( + self, telemetry_events: list[dict[str, Any]] + ) -> None: + config = build_test_vibe_config(enable_telemetry=True) + client = TelemetryClient(config_getter=lambda: config) + + client.send_at_mention_inserted( + nb_mentions=1, + context_types={"folder": 1}, + file_extensions=None, + message_id=None, + ) + + assert len(telemetry_events) == 1 + assert telemetry_events[0]["properties"]["file_extensions"] is None + assert telemetry_events[0]["properties"]["message_id"] is None + + def test_send_at_mention_inserted_multiple_files_counts_extensions( + self, telemetry_events: list[dict[str, Any]] + ) -> None: + config = build_test_vibe_config(enable_telemetry=True) + client = TelemetryClient(config_getter=lambda: config) + + client.send_at_mention_inserted( + nb_mentions=4, + context_types={"file": 3, "folder": 1}, + file_extensions={".py": 2, ".ts": 1}, + message_id="msg-multi", + ) + + props = telemetry_events[0]["properties"] + assert props["nb_mentions"] == 4 + assert props["context_types"] == {"file": 3, "folder": 1} + assert props["file_extensions"] == {".py": 2, ".ts": 1} + + def test_send_at_mention_inserted_duplicate_references_reflected_in_counts( + self, telemetry_events: list[dict[str, Any]] + ) -> None: + config = build_test_vibe_config(enable_telemetry=True) + client = TelemetryClient(config_getter=lambda: config) + + client.send_at_mention_inserted( + nb_mentions=4, + context_types={"file": 2, "folder": 2}, + file_extensions={".py": 2}, + message_id="msg-dupes", + ) + + props = telemetry_events[0]["properties"] + assert props["nb_mentions"] == 4 + assert props["context_types"] == {"file": 2, "folder": 2} + assert props["file_extensions"] == {".py": 2} + def test_send_auto_compact_triggered_payload( self, telemetry_events: list[dict[str, Any]] ) -> None: diff --git a/tests/e2e/test_cli_tui_fresh_install.py b/tests/e2e/test_cli_tui_fresh_install.py new file mode 100644 index 0000000..c83d408 --- /dev/null +++ b/tests/e2e/test_cli_tui_fresh_install.py @@ -0,0 +1,110 @@ +from __future__ import annotations + +import io +import os +from pathlib import Path +import subprocess +import sys + +import pexpect +import pytest + +from tests import TESTS_ROOT +from tests.e2e.common import ( + ansi_tolerant_pattern, + send_ctrl_c_until_quit_confirmation, + wait_for_main_screen, + wait_for_request_count, +) +from tests.e2e.mock_server import StreamingMockServer + + +def _venv_executable(venv_path: Path, name: str) -> Path: + if os.name == "nt": + return venv_path / "Scripts" / f"{name}.exe" + return venv_path / "bin" / name + + +def _build_wheel(dist_dir: Path) -> Path: + subprocess.run( + ["uv", "build", "--wheel", "--out-dir", str(dist_dir)], + cwd=TESTS_ROOT.parent, + check=True, + ) + wheels = sorted(dist_dir.glob("mistral_vibe-*.whl")) + assert len(wheels) == 1 + return wheels[0] + + +def _install_fresh_wheel(tmp_path: Path, wheel_path: Path) -> Path: + venv_path = tmp_path / "fresh-install-venv" + subprocess.run( + ["uv", "venv", "--no-config", "--python", sys.executable, str(venv_path)], + cwd=tmp_path, + check=True, + ) + + python_path = _venv_executable(venv_path, "python") + subprocess.run( + [ + "uv", + "pip", + "install", + "--no-config", + "--refresh", + "--python", + str(python_path), + str(wheel_path), + ], + cwd=tmp_path, + check=True, + ) + return _venv_executable(venv_path, "vibe") + + +@pytest.mark.timeout(90) +def test_fresh_wheel_install_can_spawn_cli_and_complete_happy_path( + streaming_mock_server: StreamingMockServer, + setup_e2e_env: None, + e2e_workdir: Path, + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + wheel_path = _build_wheel(tmp_path / "dist") + vibe_executable = _install_fresh_wheel(tmp_path, wheel_path) + + monkeypatch.delenv("PYTHONPATH", raising=False) + + captured = io.StringIO() + child = pexpect.spawn( + str(vibe_executable), + ["--workdir", str(e2e_workdir)], + cwd=str(tmp_path), + env=os.environ, + encoding="utf-8", + timeout=30, + dimensions=(36, 120), + ) + child.logfile_read = captured + + try: + wait_for_main_screen(child, timeout=20) + child.send("Greet") + child.send("\r") + + wait_for_request_count( + lambda: len(streaming_mock_server.requests), expected_count=1, timeout=10 + ) + child.expect(ansi_tolerant_pattern("Hello from mock server"), timeout=10) + + send_ctrl_c_until_quit_confirmation(child, captured, timeout=5) + child.expect(pexpect.EOF, timeout=10) + finally: + if child.isalive(): + child.terminate(force=True) + if not child.closed: + child.close() + + output = captured.getvalue() + assert "Welcome to Mistral Vibe" not in output + assert streaming_mock_server.requests[-1].get("model") == "mock-model" diff --git a/tests/e2e/test_cli_tui_tool_approval.py b/tests/e2e/test_cli_tui_tool_approval.py index 58043cb..83ed683 100644 --- a/tests/e2e/test_cli_tui_tool_approval.py +++ b/tests/e2e/test_cli_tui_tool_approval.py @@ -79,7 +79,9 @@ def test_spawn_cli_asks_bash_permission_and_shows_tool_output_after_approval( wait_for_request_count( lambda: len(streaming_mock_server.requests), expected_count=1, timeout=10 ) - wait_for_rendered_text(child, captured, needle="bash command", timeout=10) + wait_for_rendered_text( + child, captured, needle="Permission for the bash tool", timeout=10 + ) child.send("y") child.send("\r") wait_for_rendered_text(child, captured, needle=PREDICTABLE_OUTPUT, timeout=10) diff --git a/tests/session/test_saved_sessions.py b/tests/session/test_saved_sessions.py new file mode 100644 index 0000000..e3b31d9 --- /dev/null +++ b/tests/session/test_saved_sessions.py @@ -0,0 +1,178 @@ +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from vibe.core.config import SessionLoggingConfig +from vibe.core.session.saved_sessions import update_saved_session_title + + +@pytest.fixture +def temp_session_dir(tmp_path: Path) -> Path: + session_dir = tmp_path / "sessions" + session_dir.mkdir() + return session_dir + + +@pytest.fixture +def session_config(temp_session_dir: Path) -> SessionLoggingConfig: + return SessionLoggingConfig( + save_dir=str(temp_session_dir), session_prefix="test", enabled=True + ) + + +class TestUpdateSavedSessionTitle: + @pytest.mark.asyncio + async def test_updates_saved_session_title_without_losing_existing_metadata( + self, session_config: SessionLoggingConfig + ) -> None: + session_dir = Path(session_config.save_dir) + saved_session_dir = session_dir / "test_20240101_120000_aaaaaaaa" + saved_session_dir.mkdir() + + (saved_session_dir / "messages.jsonl").write_text( + '{"role": "user", "content": "Hello"}\n', encoding="utf-8" + ) + + original_metadata = { + "session_id": "aaaaaaaa-1111", + "start_time": "2024-01-01T12:00:00Z", + "end_time": "2024-01-01T12:05:00Z", + "git_commit": None, + "git_branch": None, + "username": "test-user", + "environment": {"working_directory": "/home/user/project"}, + "title": "Old title", + "stats": {"steps": 2}, + "total_messages": 1, + "tools_available": [ + { + "type": "function", + "function": {"name": "bash", "description": "Run shell commands"}, + } + ], + "config": {"active_model": "test-model"}, + "system_prompt": {"role": "system", "content": "You are helpful"}, + } + metadata_file = saved_session_dir / "meta.json" + metadata_file.write_text(json.dumps(original_metadata), encoding="utf-8") + + updated_metadata = await update_saved_session_title( + "aaaaaaaa-1111", "Renamed session", session_config + ) + + assert updated_metadata == { + **original_metadata, + "title": "Renamed session", + "title_source": "manual", + } + assert json.loads(metadata_file.read_text(encoding="utf-8")) == updated_metadata + + @pytest.mark.asyncio + async def test_rejects_empty_title( + self, session_config: SessionLoggingConfig + ) -> None: + session_dir = Path(session_config.save_dir) + saved_session_dir = session_dir / "test_20240101_120000_bbbbbbbb" + saved_session_dir.mkdir() + + (saved_session_dir / "messages.jsonl").write_text( + '{"role": "user", "content": "Hello"}\n', encoding="utf-8" + ) + + original_metadata = { + "session_id": "bbbbbbbb-2222", + "start_time": "2024-01-01T12:00:00Z", + "end_time": "2024-01-01T12:05:00Z", + "git_commit": None, + "git_branch": None, + "username": "test-user", + "environment": {"working_directory": "/home/user/project"}, + "title": "Manual title", + "title_source": "manual", + "stats": {"steps": 2}, + } + metadata_file = saved_session_dir / "meta.json" + metadata_file.write_text(json.dumps(original_metadata), encoding="utf-8") + + with pytest.raises(ValueError, match="Session title cannot be empty."): + await update_saved_session_title("bbbbbbbb-2222", " ", session_config) + + @pytest.mark.asyncio + async def test_preserves_saved_session_end_time_when_updating_title( + self, session_config: SessionLoggingConfig + ) -> None: + session_dir = Path(session_config.save_dir) + saved_session_dir = session_dir / "test_20240101_120000_cccccccc" + saved_session_dir.mkdir() + + (saved_session_dir / "messages.jsonl").write_text( + '{"role": "user", "content": "Hello"}\n', encoding="utf-8" + ) + + original_metadata = { + "session_id": "cccccccc-3333", + "start_time": "2024-01-01T12:00:00Z", + "end_time": "2024-01-01T12:05:00Z", + "git_commit": None, + "git_branch": None, + "username": "test-user", + "environment": {"working_directory": "/home/user/project"}, + "title": "Old title", + } + metadata_file = saved_session_dir / "meta.json" + metadata_file.write_text(json.dumps(original_metadata), encoding="utf-8") + + updated_metadata = await update_saved_session_title( + "cccccccc-3333", "Renamed session", session_config + ) + + assert updated_metadata == { + **original_metadata, + "title": "Renamed session", + "title_source": "manual", + } + assert json.loads(metadata_file.read_text(encoding="utf-8")) == updated_metadata + + @pytest.mark.asyncio + async def test_raises_for_missing_saved_session( + self, session_config: SessionLoggingConfig + ) -> None: + with pytest.raises(ValueError, match="Session not found: missing-session"): + await update_saved_session_title( + "missing-session", "Renamed", session_config + ) + + @pytest.mark.asyncio + async def test_requires_exact_saved_session_id( + self, session_config: SessionLoggingConfig + ) -> None: + session_dir = Path(session_config.save_dir) + saved_session_dir = session_dir / "test_20240101_120000_dddddddd" + saved_session_dir.mkdir() + + (saved_session_dir / "messages.jsonl").write_text( + '{"role": "user", "content": "Hello"}\n', encoding="utf-8" + ) + + original_metadata = { + "session_id": "dddddddd-4444", + "start_time": "2024-01-01T12:00:00Z", + "end_time": "2024-01-01T12:05:00Z", + "git_commit": None, + "git_branch": None, + "username": "test-user", + "environment": {"working_directory": "/home/user/project"}, + "title": "Old title", + } + metadata_file = saved_session_dir / "meta.json" + metadata_file.write_text(json.dumps(original_metadata), encoding="utf-8") + + with pytest.raises(ValueError, match="Session not found: dddddddd"): + await update_saved_session_title("dddddddd", "Renamed", session_config) + + assert ( + json.loads(metadata_file.read_text(encoding="utf-8")) == original_metadata + ) diff --git a/tests/session/test_session_loader.py b/tests/session/test_session_loader.py index d43f1f7..09ae73a 100644 --- a/tests/session/test_session_loader.py +++ b/tests/session/test_session_loader.py @@ -1049,6 +1049,36 @@ class TestSessionLoaderGetFirstUserMessage: class TestSessionLoaderUTF8Encoding: + def test_load_metadata_defaults_title_source_for_existing_sessions( + self, session_config: SessionLoggingConfig + ) -> None: + session_dir = Path(session_config.save_dir) + session_folder = session_dir / "test_20230101_120000_legacyttl" + session_folder.mkdir() + + metadata_content = { + "session_id": "legacy-title-test", + "start_time": "2023-01-01T12:00:00Z", + "end_time": "2023-01-01T12:05:00Z", + "environment": {"working_directory": "/home/user/project"}, + "username": "testuser", + "git_commit": None, + "git_branch": None, + "title": "Existing title", + } + + metadata_file = session_folder / "meta.json" + with metadata_file.open("w", encoding="utf-8") as f: + json.dump(metadata_content, f, indent=2, ensure_ascii=False) + + messages_file = session_folder / "messages.jsonl" + messages_file.write_text('{"role": "user", "content": "Hello"}\n') + + metadata = SessionLoader.load_metadata(session_folder) + + assert metadata.title == "Existing title" + assert metadata.title_source == "auto" + def test_load_metadata_with_utf8_encoding( self, session_config: SessionLoggingConfig, create_test_session ) -> None: diff --git a/tests/session/test_session_logger.py b/tests/session/test_session_logger.py index 080caf5..73cf099 100644 --- a/tests/session/test_session_logger.py +++ b/tests/session/test_session_logger.py @@ -139,6 +139,8 @@ class TestSessionLoggerMetadata: assert metadata.username == "testuser" assert "working_directory" in metadata.environment assert metadata.environment["working_directory"] == str(Path.cwd()) + assert metadata.title is None + assert metadata.title_source == "auto" @patch("vibe.core.session.session_logger.subprocess.run") @patch("vibe.core.session.session_logger.getpass.getuser") @@ -161,6 +163,50 @@ class TestSessionLoggerMetadata: assert metadata.username == "testuser" +class TestSessionLoggerTitleManagement: + def test_set_title_marks_live_session_title_as_manual( + self, session_config: SessionLoggingConfig + ) -> None: + logger = SessionLogger(session_config, "test-session-123") + + logger.set_title("Manual title") + + assert logger.session_metadata is not None + assert logger.session_metadata.title == "Manual title" + assert logger.session_metadata.title_source == "manual" + + def test_set_title_none_returns_live_session_to_auto_mode( + self, session_config: SessionLoggingConfig + ) -> None: + logger = SessionLogger(session_config, "test-session-123") + logger.set_title("Manual title") + + logger.set_title(None) + + assert logger.session_metadata is not None + assert logger.session_metadata.title is None + assert logger.session_metadata.title_source == "auto" + + def test_set_title_rejects_empty_title( + self, session_config: SessionLoggingConfig + ) -> None: + logger = SessionLogger(session_config, "test-session-123") + + with pytest.raises(ValueError, match="Session title cannot be empty."): + logger.set_title(" ") + + def test_set_title_preserves_live_session_end_time( + self, session_config: SessionLoggingConfig + ) -> None: + logger = SessionLogger(session_config, "test-session-123") + assert logger.session_metadata is not None + logger.session_metadata.end_time = "2026-01-01T10:00:00+00:00" + + logger.set_title("Manual title") + + assert logger.session_metadata.end_time == "2026-01-01T10:00:00+00:00" + + class TestSessionLoggerSaveInteraction: @pytest.mark.asyncio async def test_save_interaction_disabled( @@ -234,6 +280,7 @@ class TestSessionLoggerSaveInteraction: assert metadata["stats"]["steps"] == stats.steps assert "title" in metadata assert metadata["title"] == "Hello" + assert metadata["title_source"] == "auto" assert "system_prompt" in metadata @pytest.mark.asyncio @@ -447,6 +494,7 @@ class TestSessionLoggerSaveInteraction: assert metadata["total_messages"] == 1 assert metadata["stats"]["steps"] == stats.steps assert metadata["title"] == "Untitled session" + assert metadata["title_source"] == "auto" messages_file = logger.session_dir / "messages.jsonl" assert messages_file.exists() @@ -496,6 +544,46 @@ class TestSessionLoggerSaveInteraction: assert metadata["stats"]["steps"] == stats.steps expected_title = long_message[:50] + "…" assert metadata["title"] == expected_title + assert metadata["title_source"] == "auto" + + @pytest.mark.asyncio + async def test_save_interaction_preserves_manual_title( + self, + session_config: SessionLoggingConfig, + mock_vibe_config: VibeConfig, + mock_tool_manager: ToolManager, + mock_agent_profile: AgentProfile, + ) -> None: + session_id = "test-session-123" + logger = SessionLogger(session_config, session_id) + assert logger.session_metadata is not None + + logger.set_title("Manual title") + + messages = [ + LLMMessage(role=Role.system, content="System prompt"), + LLMMessage(role=Role.user, content="Hello"), + LLMMessage(role=Role.assistant, content="Hi there!"), + ] + stats = AgentStats( + steps=1, session_prompt_tokens=10, session_completion_tokens=20 + ) + + await logger.save_interaction( + messages=messages, + stats=stats, + base_config=mock_vibe_config, + tool_manager=mock_tool_manager, + agent_profile=mock_agent_profile, + ) + + assert logger.session_dir is not None + metadata_file = logger.session_dir / "meta.json" + with open(metadata_file) as f: + metadata = json.load(f) + + assert metadata["title"] == "Manual title" + assert metadata["title_source"] == "manual" messages_file = logger.session_dir / "messages.jsonl" assert messages_file.exists() diff --git a/tests/snapshots/__snapshots__/test_ui_snapshot_teleport/test_snapshot_teleport_command_hidden_for_non_pro_account.svg b/tests/snapshots/__snapshots__/test_ui_snapshot_teleport/test_snapshot_teleport_command_hidden_for_non_pro_account.svg index 485db56..1b28fc3 100644 --- a/tests/snapshots/__snapshots__/test_ui_snapshot_teleport/test_snapshot_teleport_command_hidden_for_non_pro_account.svg +++ b/tests/snapshots/__snapshots__/test_ui_snapshot_teleport/test_snapshot_teleport_command_hidden_for_non_pro_account.svg @@ -33,11 +33,11 @@ } .terminal-r1 { fill: #c5c8c6 } -.terminal-r2 { fill: #4b4e55 } -.terminal-r3 { fill: #68a0b3 } -.terminal-r4 { fill: #292929 } -.terminal-r5 { fill: #9a9b99 } -.terminal-r6 { fill: #608ab1;font-weight: bold } +.terminal-r2 { fill: #68a0b3 } +.terminal-r3 { fill: #4b4e55 } +.terminal-r4 { fill: #9a9b99 } +.terminal-r5 { fill: #608ab1;font-weight: bold } +.terminal-r6 { fill: #292929 } .terminal-r7 { fill: #98a84b;font-weight: bold } .terminal-r8 { fill: #ff8205;font-weight: bold } </style> @@ -197,56 +197,56 @@ </g> <g transform="translate(9, 41)" clip-path="url(#terminal-clip-terminal)"> - <rect fill="#4b4e55" x="1451.8" y="25.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="74.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="123.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="147.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="172.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="196.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="221.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="294.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="343.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="367.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="391.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="416.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="440.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="465.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="489.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="513.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="538.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="562.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="587.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="611.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="635.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="660.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="684.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="709.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="733.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="757.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="782.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="806.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="831.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="855.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="879.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="904.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="928.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="953.1" width="12.2" height="24.65" shape-rendering="crispEdges"/> + <rect fill="#4b4e55" x="1451.8" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="74.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="123.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="147.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="172.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="196.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="221.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="294.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="343.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="367.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="391.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="416.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="440.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="465.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="489.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="513.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="538.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="562.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="587.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="611.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="635.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="660.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="684.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="709.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="733.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="757.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="782.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="806.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="831.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="855.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="879.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="904.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="928.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="953.1" width="12.2" height="24.65" shape-rendering="crispEdges"/> <g class="terminal-matrix"> - <text class="terminal-r1" x="0" y="20" textLength="134.2" clip-path="url(#terminal-line-0)"> ⢸⠸⣀⡔⢉⠱⣃⡢⣂⡣</text><text class="terminal-r1" x="170.8" y="20" textLength="414.8" clip-path="url(#terminal-line-0)">1 model · 0 MCP servers · 0 skills</text><text class="terminal-r2" x="1451.8" y="20" textLength="12.2" clip-path="url(#terminal-line-0)">▁</text><text class="terminal-r1" x="1464" y="20" textLength="12.2" clip-path="url(#terminal-line-0)"> -</text><text class="terminal-r1" x="0" y="44.4" textLength="134.2" clip-path="url(#terminal-line-1)">  ⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="44.4" textLength="61" clip-path="url(#terminal-line-1)">Type </text><text class="terminal-r3" x="231.8" y="44.4" textLength="61" clip-path="url(#terminal-line-1)">/help</text><text class="terminal-r1" x="292.8" y="44.4" textLength="256.2" clip-path="url(#terminal-line-1)"> for more information</text><text class="terminal-r1" x="1464" y="44.4" textLength="12.2" clip-path="url(#terminal-line-1)"> -</text><text class="terminal-r1" x="1464" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)"> -</text><text class="terminal-r5" x="24.4" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)">⎢</text><text class="terminal-r6" x="48.8" y="93.2" textLength="219.6" clip-path="url(#terminal-line-3)">Keyboard Shortcuts</text><text class="terminal-r1" x="1464" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)"> -</text><text class="terminal-r5" x="24.4" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)">⎢</text><text class="terminal-r1" x="48.8" y="117.6" textLength="24.4" clip-path="url(#terminal-line-4)">• </text><text class="terminal-r7" x="73.2" y="117.6" textLength="61" clip-path="url(#terminal-line-4)">Enter</text><text class="terminal-r1" x="134.2" y="117.6" textLength="183" clip-path="url(#terminal-line-4)"> Submit message</text><text class="terminal-r1" x="1464" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)"> -</text><text class="terminal-r5" x="24.4" y="142" textLength="12.2" clip-path="url(#terminal-line-5)">⎢</text><text class="terminal-r1" x="48.8" y="142" textLength="24.4" clip-path="url(#terminal-line-5)">• </text><text class="terminal-r7" x="73.2" y="142" textLength="73.2" clip-path="url(#terminal-line-5)">Ctrl+J</text><text class="terminal-r1" x="146.4" y="142" textLength="36.6" clip-path="url(#terminal-line-5)"> / </text><text class="terminal-r7" x="183" y="142" textLength="134.2" clip-path="url(#terminal-line-5)">Shift+Enter</text><text class="terminal-r1" x="317.2" y="142" textLength="183" clip-path="url(#terminal-line-5)"> Insert newline</text><text class="terminal-r1" x="1464" y="142" textLength="12.2" clip-path="url(#terminal-line-5)"> -</text><text class="terminal-r5" x="24.4" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)">⎢</text><text class="terminal-r1" x="48.8" y="166.4" textLength="24.4" clip-path="url(#terminal-line-6)">• </text><text class="terminal-r7" x="73.2" y="166.4" textLength="73.2" clip-path="url(#terminal-line-6)">Escape</text><text class="terminal-r1" x="146.4" y="166.4" textLength="402.6" clip-path="url(#terminal-line-6)"> Interrupt agent or close dialogs</text><text class="terminal-r1" x="1464" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)"> -</text><text class="terminal-r5" x="24.4" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)">⎢</text><text class="terminal-r1" x="48.8" y="190.8" textLength="24.4" clip-path="url(#terminal-line-7)">• </text><text class="terminal-r7" x="73.2" y="190.8" textLength="73.2" clip-path="url(#terminal-line-7)">Ctrl+C</text><text class="terminal-r1" x="146.4" y="190.8" textLength="463.6" clip-path="url(#terminal-line-7)"> Quit (or clear input if text present)</text><text class="terminal-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)"> -</text><text class="terminal-r5" x="24.4" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)">⎢</text><text class="terminal-r1" x="48.8" y="215.2" textLength="24.4" clip-path="url(#terminal-line-8)">• </text><text class="terminal-r7" x="73.2" y="215.2" textLength="73.2" clip-path="url(#terminal-line-8)">Ctrl+G</text><text class="terminal-r1" x="146.4" y="215.2" textLength="366" clip-path="url(#terminal-line-8)"> Edit input in external editor</text><text class="terminal-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)"> -</text><text class="terminal-r5" x="24.4" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">⎢</text><text class="terminal-r1" x="48.8" y="239.6" textLength="24.4" clip-path="url(#terminal-line-9)">• </text><text class="terminal-r7" x="73.2" y="239.6" textLength="73.2" clip-path="url(#terminal-line-9)">Ctrl+O</text><text class="terminal-r1" x="146.4" y="239.6" textLength="292.8" clip-path="url(#terminal-line-9)"> Toggle tool output view</text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)"> -</text><text class="terminal-r5" x="24.4" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">⎢</text><text class="terminal-r1" x="48.8" y="264" textLength="24.4" clip-path="url(#terminal-line-10)">• </text><text class="terminal-r7" x="73.2" y="264" textLength="109.8" clip-path="url(#terminal-line-10)">Shift+Tab</text><text class="terminal-r1" x="183" y="264" textLength="305" clip-path="url(#terminal-line-10)"> Toggle auto-approve mode</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> -</text><text class="terminal-r5" x="24.4" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">⎢</text><text class="terminal-r1" x="48.8" y="288.4" textLength="24.4" clip-path="url(#terminal-line-11)">• </text><text class="terminal-r7" x="73.2" y="288.4" textLength="73.2" clip-path="url(#terminal-line-11)">Alt+↑↓</text><text class="terminal-r1" x="146.4" y="288.4" textLength="36.6" clip-path="url(#terminal-line-11)"> / </text><text class="terminal-r7" x="183" y="288.4" textLength="97.6" clip-path="url(#terminal-line-11)">Ctrl+P/N</text><text class="terminal-r1" x="280.6" y="288.4" textLength="390.4" clip-path="url(#terminal-line-11)"> Rewind to previous/next message</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> -</text><text class="terminal-r5" x="24.4" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">⎢</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> -</text><text class="terminal-r5" x="24.4" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">⎢</text><text class="terminal-r6" x="48.8" y="337.2" textLength="195.2" clip-path="url(#terminal-line-13)">Special Features</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> -</text><text class="terminal-r5" x="24.4" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">⎢</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> -</text><text class="terminal-r5" x="24.4" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">⎢</text><text class="terminal-r1" x="48.8" y="386" textLength="24.4" clip-path="url(#terminal-line-15)">• </text><text class="terminal-r7" x="73.2" y="386" textLength="122" clip-path="url(#terminal-line-15)">!<command></text><text class="terminal-r1" x="195.2" y="386" textLength="366" clip-path="url(#terminal-line-15)"> Execute bash command directly</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> -</text><text class="terminal-r5" x="24.4" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">⎢</text><text class="terminal-r1" x="48.8" y="410.4" textLength="24.4" clip-path="url(#terminal-line-16)">• </text><text class="terminal-r7" x="73.2" y="410.4" textLength="170.8" clip-path="url(#terminal-line-16)">@path/to/file/</text><text class="terminal-r1" x="244" y="410.4" textLength="305" clip-path="url(#terminal-line-16)"> Autocompletes file paths</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> -</text><text class="terminal-r5" x="24.4" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">⎢</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> -</text><text class="terminal-r5" x="24.4" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">⎢</text><text class="terminal-r6" x="48.8" y="459.2" textLength="97.6" clip-path="url(#terminal-line-18)">Commands</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> -</text><text class="terminal-r5" x="24.4" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">⎢</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> -</text><text class="terminal-r5" x="24.4" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">⎢</text><text class="terminal-r1" x="48.8" y="508" textLength="24.4" clip-path="url(#terminal-line-20)">• </text><text class="terminal-r7" x="73.2" y="508" textLength="61" clip-path="url(#terminal-line-20)">/help</text><text class="terminal-r1" x="134.2" y="508" textLength="231.8" clip-path="url(#terminal-line-20)">: Show help message</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> -</text><text class="terminal-r5" x="24.4" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">⎢</text><text class="terminal-r1" x="48.8" y="532.4" textLength="24.4" clip-path="url(#terminal-line-21)">• </text><text class="terminal-r7" x="73.2" y="532.4" textLength="85.4" clip-path="url(#terminal-line-21)">/config</text><text class="terminal-r1" x="158.6" y="532.4" textLength="268.4" clip-path="url(#terminal-line-21)">: Edit config settings</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> -</text><text class="terminal-r5" x="24.4" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">⎢</text><text class="terminal-r1" x="48.8" y="556.8" textLength="24.4" clip-path="url(#terminal-line-22)">• </text><text class="terminal-r7" x="73.2" y="556.8" textLength="73.2" clip-path="url(#terminal-line-22)">/model</text><text class="terminal-r1" x="146.4" y="556.8" textLength="256.2" clip-path="url(#terminal-line-22)">: Select active model</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> -</text><text class="terminal-r5" x="24.4" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">⎢</text><text class="terminal-r1" x="48.8" y="581.2" textLength="24.4" clip-path="url(#terminal-line-23)">• </text><text class="terminal-r7" x="73.2" y="581.2" textLength="109.8" clip-path="url(#terminal-line-23)">/thinking</text><text class="terminal-r1" x="183" y="581.2" textLength="280.6" clip-path="url(#terminal-line-23)">: Select thinking level</text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> -</text><text class="terminal-r5" x="24.4" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">⎢</text><text class="terminal-r1" x="48.8" y="605.6" textLength="24.4" clip-path="url(#terminal-line-24)">• </text><text class="terminal-r7" x="73.2" y="605.6" textLength="85.4" clip-path="url(#terminal-line-24)">/reload</text><text class="terminal-r1" x="158.6" y="605.6" textLength="780.8" clip-path="url(#terminal-line-24)">: Reload configuration, agent instructions, and skills from disk</text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)"> -</text><text class="terminal-r5" x="24.4" y="630" textLength="12.2" clip-path="url(#terminal-line-25)">⎢</text><text class="terminal-r1" x="48.8" y="630" textLength="24.4" clip-path="url(#terminal-line-25)">• </text><text class="terminal-r7" x="73.2" y="630" textLength="73.2" clip-path="url(#terminal-line-25)">/clear</text><text class="terminal-r1" x="146.4" y="630" textLength="341.6" clip-path="url(#terminal-line-25)">: Clear conversation history</text><text class="terminal-r1" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-line-25)"> -</text><text class="terminal-r5" x="24.4" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)">⎢</text><text class="terminal-r1" x="48.8" y="654.4" textLength="24.4" clip-path="url(#terminal-line-26)">• </text><text class="terminal-r7" x="73.2" y="654.4" textLength="61" clip-path="url(#terminal-line-26)">/copy</text><text class="terminal-r1" x="134.2" y="654.4" textLength="561.2" clip-path="url(#terminal-line-26)">: Copy the last agent message to the clipboard</text><text class="terminal-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)"> -</text><text class="terminal-r5" x="24.4" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)">⎢</text><text class="terminal-r1" x="48.8" y="678.8" textLength="24.4" clip-path="url(#terminal-line-27)">• </text><text class="terminal-r7" x="73.2" y="678.8" textLength="48.8" clip-path="url(#terminal-line-27)">/log</text><text class="terminal-r1" x="122" y="678.8" textLength="524.6" clip-path="url(#terminal-line-27)">: Show path to current interaction log file</text><text class="terminal-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)"> -</text><text class="terminal-r5" x="24.4" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)">⎢</text><text class="terminal-r1" x="48.8" y="703.2" textLength="24.4" clip-path="url(#terminal-line-28)">• </text><text class="terminal-r7" x="73.2" y="703.2" textLength="73.2" clip-path="url(#terminal-line-28)">/debug</text><text class="terminal-r1" x="146.4" y="703.2" textLength="268.4" clip-path="url(#terminal-line-28)">: Toggle debug console</text><text class="terminal-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)"> -</text><text class="terminal-r5" x="24.4" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)">⎢</text><text class="terminal-r1" x="48.8" y="727.6" textLength="24.4" clip-path="url(#terminal-line-29)">• </text><text class="terminal-r7" x="73.2" y="727.6" textLength="97.6" clip-path="url(#terminal-line-29)">/compact</text><text class="terminal-r1" x="170.8" y="727.6" textLength="1171.2" clip-path="url(#terminal-line-29)">: Compact conversation history by summarizing. Optionally pass instructions to guide the summary</text><text class="terminal-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)"> -</text><text class="terminal-r5" x="24.4" y="752" textLength="12.2" clip-path="url(#terminal-line-30)">⎢</text><text class="terminal-r1" x="48.8" y="752" textLength="24.4" clip-path="url(#terminal-line-30)">• </text><text class="terminal-r7" x="73.2" y="752" textLength="61" clip-path="url(#terminal-line-30)">/exit</text><text class="terminal-r1" x="134.2" y="752" textLength="268.4" clip-path="url(#terminal-line-30)">: Exit the application</text><text class="terminal-r1" x="1464" y="752" textLength="12.2" clip-path="url(#terminal-line-30)"> -</text><text class="terminal-r5" x="24.4" y="776.4" textLength="12.2" clip-path="url(#terminal-line-31)">⎢</text><text class="terminal-r1" x="48.8" y="776.4" textLength="24.4" clip-path="url(#terminal-line-31)">• </text><text class="terminal-r7" x="73.2" y="776.4" textLength="85.4" clip-path="url(#terminal-line-31)">/status</text><text class="terminal-r1" x="158.6" y="776.4" textLength="317.2" clip-path="url(#terminal-line-31)">: Display agent statistics</text><text class="terminal-r1" x="1464" y="776.4" textLength="12.2" clip-path="url(#terminal-line-31)"> -</text><text class="terminal-r5" x="24.4" y="800.8" textLength="12.2" clip-path="url(#terminal-line-32)">⎢</text><text class="terminal-r1" x="48.8" y="800.8" textLength="24.4" clip-path="url(#terminal-line-32)">• </text><text class="terminal-r7" x="73.2" y="800.8" textLength="146.4" clip-path="url(#terminal-line-32)">/proxy-setup</text><text class="terminal-r1" x="219.6" y="800.8" textLength="561.2" clip-path="url(#terminal-line-32)">: Configure proxy and SSL certificate settings</text><text class="terminal-r1" x="1464" y="800.8" textLength="12.2" clip-path="url(#terminal-line-32)"> -</text><text class="terminal-r5" x="24.4" y="825.2" textLength="12.2" clip-path="url(#terminal-line-33)">⎢</text><text class="terminal-r1" x="48.8" y="825.2" textLength="24.4" clip-path="url(#terminal-line-33)">• </text><text class="terminal-r7" x="73.2" y="825.2" textLength="109.8" clip-path="url(#terminal-line-33)">/continue</text><text class="terminal-r1" x="183" y="825.2" textLength="24.4" clip-path="url(#terminal-line-33)">, </text><text class="terminal-r7" x="207.4" y="825.2" textLength="85.4" clip-path="url(#terminal-line-33)">/resume</text><text class="terminal-r1" x="292.8" y="825.2" textLength="402.6" clip-path="url(#terminal-line-33)">: Browse and resume past sessions</text><text class="terminal-r1" x="1464" y="825.2" textLength="12.2" clip-path="url(#terminal-line-33)"> -</text><text class="terminal-r5" x="24.4" y="849.6" textLength="12.2" clip-path="url(#terminal-line-34)">⎢</text><text class="terminal-r1" x="48.8" y="849.6" textLength="24.4" clip-path="url(#terminal-line-34)">• </text><text class="terminal-r7" x="73.2" y="849.6" textLength="134.2" clip-path="url(#terminal-line-34)">/connectors</text><text class="terminal-r1" x="207.4" y="849.6" textLength="24.4" clip-path="url(#terminal-line-34)">, </text><text class="terminal-r7" x="231.8" y="849.6" textLength="48.8" clip-path="url(#terminal-line-34)">/mcp</text><text class="terminal-r1" x="280.6" y="849.6" textLength="939.4" clip-path="url(#terminal-line-34)">: Display available MCP servers and connectors. Pass a name to list its tools</text><text class="terminal-r1" x="1464" y="849.6" textLength="12.2" clip-path="url(#terminal-line-34)"> -</text><text class="terminal-r5" x="24.4" y="874" textLength="12.2" clip-path="url(#terminal-line-35)">⎢</text><text class="terminal-r1" x="48.8" y="874" textLength="24.4" clip-path="url(#terminal-line-35)">• </text><text class="terminal-r7" x="73.2" y="874" textLength="73.2" clip-path="url(#terminal-line-35)">/voice</text><text class="terminal-r1" x="146.4" y="874" textLength="317.2" clip-path="url(#terminal-line-35)">: Configure voice settings</text><text class="terminal-r1" x="1464" y="874" textLength="12.2" clip-path="url(#terminal-line-35)"> -</text><text class="terminal-r5" x="24.4" y="898.4" textLength="12.2" clip-path="url(#terminal-line-36)">⎢</text><text class="terminal-r1" x="48.8" y="898.4" textLength="24.4" clip-path="url(#terminal-line-36)">• </text><text class="terminal-r7" x="73.2" y="898.4" textLength="122" clip-path="url(#terminal-line-36)">/leanstall</text><text class="terminal-r1" x="195.2" y="898.4" textLength="463.6" clip-path="url(#terminal-line-36)">: Install the Lean 4 agent (leanstral)</text><text class="terminal-r1" x="1464" y="898.4" textLength="12.2" clip-path="url(#terminal-line-36)"> -</text><text class="terminal-r5" x="24.4" y="922.8" textLength="12.2" clip-path="url(#terminal-line-37)">⎢</text><text class="terminal-r1" x="48.8" y="922.8" textLength="24.4" clip-path="url(#terminal-line-37)">• </text><text class="terminal-r7" x="73.2" y="922.8" textLength="146.4" clip-path="url(#terminal-line-37)">/unleanstall</text><text class="terminal-r1" x="219.6" y="922.8" textLength="341.6" clip-path="url(#terminal-line-37)">: Uninstall the Lean 4 agent</text><text class="terminal-r1" x="1464" y="922.8" textLength="12.2" clip-path="url(#terminal-line-37)"> -</text><text class="terminal-r5" x="24.4" y="947.2" textLength="12.2" clip-path="url(#terminal-line-38)">⎢</text><text class="terminal-r1" x="48.8" y="947.2" textLength="24.4" clip-path="url(#terminal-line-38)">• </text><text class="terminal-r7" x="73.2" y="947.2" textLength="85.4" clip-path="url(#terminal-line-38)">/rewind</text><text class="terminal-r1" x="158.6" y="947.2" textLength="366" clip-path="url(#terminal-line-38)">: Rewind to a previous message</text><text class="terminal-r1" x="1464" y="947.2" textLength="12.2" clip-path="url(#terminal-line-38)"> -</text><text class="terminal-r5" x="24.4" y="971.6" textLength="12.2" clip-path="url(#terminal-line-39)">⎣</text><text class="terminal-r1" x="48.8" y="971.6" textLength="24.4" clip-path="url(#terminal-line-39)">• </text><text class="terminal-r7" x="73.2" y="971.6" textLength="183" clip-path="url(#terminal-line-39)">/data-retention</text><text class="terminal-r1" x="256.2" y="971.6" textLength="402.6" clip-path="url(#terminal-line-39)">: Show data retention information</text><text class="terminal-r1" x="1464" y="971.6" textLength="12.2" clip-path="url(#terminal-line-39)"> + <text class="terminal-r1" x="0" y="20" textLength="134.2" clip-path="url(#terminal-line-0)">  ⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="20" textLength="61" clip-path="url(#terminal-line-0)">Type </text><text class="terminal-r2" x="231.8" y="20" textLength="61" clip-path="url(#terminal-line-0)">/help</text><text class="terminal-r1" x="292.8" y="20" textLength="256.2" clip-path="url(#terminal-line-0)"> for more information</text><text class="terminal-r1" x="1464" y="20" textLength="12.2" clip-path="url(#terminal-line-0)"> +</text><text class="terminal-r3" x="1451.8" y="44.4" textLength="12.2" clip-path="url(#terminal-line-1)">▁</text><text class="terminal-r1" x="1464" y="44.4" textLength="12.2" clip-path="url(#terminal-line-1)"> +</text><text class="terminal-r4" x="24.4" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)">⎢</text><text class="terminal-r5" x="48.8" y="68.8" textLength="219.6" clip-path="url(#terminal-line-2)">Keyboard Shortcuts</text><text class="terminal-r1" x="1464" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)"> +</text><text class="terminal-r4" x="24.4" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)">⎢</text><text class="terminal-r1" x="48.8" y="93.2" textLength="24.4" clip-path="url(#terminal-line-3)">• </text><text class="terminal-r7" x="73.2" y="93.2" textLength="61" clip-path="url(#terminal-line-3)">Enter</text><text class="terminal-r1" x="134.2" y="93.2" textLength="183" clip-path="url(#terminal-line-3)"> Submit message</text><text class="terminal-r1" x="1464" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)"> +</text><text class="terminal-r4" x="24.4" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)">⎢</text><text class="terminal-r1" x="48.8" y="117.6" textLength="24.4" clip-path="url(#terminal-line-4)">• </text><text class="terminal-r7" x="73.2" y="117.6" textLength="73.2" clip-path="url(#terminal-line-4)">Ctrl+J</text><text class="terminal-r1" x="146.4" y="117.6" textLength="36.6" clip-path="url(#terminal-line-4)"> / </text><text class="terminal-r7" x="183" y="117.6" textLength="134.2" clip-path="url(#terminal-line-4)">Shift+Enter</text><text class="terminal-r1" x="317.2" y="117.6" textLength="183" clip-path="url(#terminal-line-4)"> Insert newline</text><text class="terminal-r1" x="1464" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)"> +</text><text class="terminal-r4" x="24.4" y="142" textLength="12.2" clip-path="url(#terminal-line-5)">⎢</text><text class="terminal-r1" x="48.8" y="142" textLength="24.4" clip-path="url(#terminal-line-5)">• </text><text class="terminal-r7" x="73.2" y="142" textLength="73.2" clip-path="url(#terminal-line-5)">Escape</text><text class="terminal-r1" x="146.4" y="142" textLength="402.6" clip-path="url(#terminal-line-5)"> Interrupt agent or close dialogs</text><text class="terminal-r1" x="1464" y="142" textLength="12.2" clip-path="url(#terminal-line-5)"> +</text><text class="terminal-r4" x="24.4" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)">⎢</text><text class="terminal-r1" x="48.8" y="166.4" textLength="24.4" clip-path="url(#terminal-line-6)">• </text><text class="terminal-r7" x="73.2" y="166.4" textLength="73.2" clip-path="url(#terminal-line-6)">Ctrl+C</text><text class="terminal-r1" x="146.4" y="166.4" textLength="463.6" clip-path="url(#terminal-line-6)"> Quit (or clear input if text present)</text><text class="terminal-r1" x="1464" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)"> +</text><text class="terminal-r4" x="24.4" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)">⎢</text><text class="terminal-r1" x="48.8" y="190.8" textLength="24.4" clip-path="url(#terminal-line-7)">• </text><text class="terminal-r7" x="73.2" y="190.8" textLength="73.2" clip-path="url(#terminal-line-7)">Ctrl+G</text><text class="terminal-r1" x="146.4" y="190.8" textLength="366" clip-path="url(#terminal-line-7)"> Edit input in external editor</text><text class="terminal-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)"> +</text><text class="terminal-r4" x="24.4" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)">⎢</text><text class="terminal-r1" x="48.8" y="215.2" textLength="24.4" clip-path="url(#terminal-line-8)">• </text><text class="terminal-r7" x="73.2" y="215.2" textLength="73.2" clip-path="url(#terminal-line-8)">Ctrl+O</text><text class="terminal-r1" x="146.4" y="215.2" textLength="292.8" clip-path="url(#terminal-line-8)"> Toggle tool output view</text><text class="terminal-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)"> +</text><text class="terminal-r4" x="24.4" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">⎢</text><text class="terminal-r1" x="48.8" y="239.6" textLength="24.4" clip-path="url(#terminal-line-9)">• </text><text class="terminal-r7" x="73.2" y="239.6" textLength="109.8" clip-path="url(#terminal-line-9)">Shift+Tab</text><text class="terminal-r1" x="183" y="239.6" textLength="512.4" clip-path="url(#terminal-line-9)"> Cycle through agents (default, plan, ...)</text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)"> +</text><text class="terminal-r4" x="24.4" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">⎢</text><text class="terminal-r1" x="48.8" y="264" textLength="24.4" clip-path="url(#terminal-line-10)">• </text><text class="terminal-r7" x="73.2" y="264" textLength="73.2" clip-path="url(#terminal-line-10)">Alt+↑↓</text><text class="terminal-r1" x="146.4" y="264" textLength="36.6" clip-path="url(#terminal-line-10)"> / </text><text class="terminal-r7" x="183" y="264" textLength="97.6" clip-path="url(#terminal-line-10)">Ctrl+P/N</text><text class="terminal-r1" x="280.6" y="264" textLength="390.4" clip-path="url(#terminal-line-10)"> Rewind to previous/next message</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> +</text><text class="terminal-r4" x="24.4" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">⎢</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> +</text><text class="terminal-r4" x="24.4" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">⎢</text><text class="terminal-r5" x="48.8" y="312.8" textLength="195.2" clip-path="url(#terminal-line-12)">Special Features</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> +</text><text class="terminal-r4" x="24.4" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">⎢</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> +</text><text class="terminal-r4" x="24.4" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">⎢</text><text class="terminal-r1" x="48.8" y="361.6" textLength="24.4" clip-path="url(#terminal-line-14)">• </text><text class="terminal-r7" x="73.2" y="361.6" textLength="122" clip-path="url(#terminal-line-14)">!<command></text><text class="terminal-r1" x="195.2" y="361.6" textLength="366" clip-path="url(#terminal-line-14)"> Execute bash command directly</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> +</text><text class="terminal-r4" x="24.4" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">⎢</text><text class="terminal-r1" x="48.8" y="386" textLength="24.4" clip-path="url(#terminal-line-15)">• </text><text class="terminal-r7" x="73.2" y="386" textLength="170.8" clip-path="url(#terminal-line-15)">@path/to/file/</text><text class="terminal-r1" x="244" y="386" textLength="305" clip-path="url(#terminal-line-15)"> Autocompletes file paths</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> +</text><text class="terminal-r4" x="24.4" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">⎢</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> +</text><text class="terminal-r4" x="24.4" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">⎢</text><text class="terminal-r5" x="48.8" y="434.8" textLength="97.6" clip-path="url(#terminal-line-17)">Commands</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> +</text><text class="terminal-r4" x="24.4" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">⎢</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> +</text><text class="terminal-r4" x="24.4" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">⎢</text><text class="terminal-r1" x="48.8" y="483.6" textLength="24.4" clip-path="url(#terminal-line-19)">• </text><text class="terminal-r7" x="73.2" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">/help</text><text class="terminal-r1" x="134.2" y="483.6" textLength="231.8" clip-path="url(#terminal-line-19)">: Show help message</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> +</text><text class="terminal-r4" x="24.4" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">⎢</text><text class="terminal-r1" x="48.8" y="508" textLength="24.4" clip-path="url(#terminal-line-20)">• </text><text class="terminal-r7" x="73.2" y="508" textLength="85.4" clip-path="url(#terminal-line-20)">/config</text><text class="terminal-r1" x="158.6" y="508" textLength="268.4" clip-path="url(#terminal-line-20)">: Edit config settings</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> +</text><text class="terminal-r4" x="24.4" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">⎢</text><text class="terminal-r1" x="48.8" y="532.4" textLength="24.4" clip-path="url(#terminal-line-21)">• </text><text class="terminal-r7" x="73.2" y="532.4" textLength="73.2" clip-path="url(#terminal-line-21)">/model</text><text class="terminal-r1" x="146.4" y="532.4" textLength="256.2" clip-path="url(#terminal-line-21)">: Select active model</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> +</text><text class="terminal-r4" x="24.4" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">⎢</text><text class="terminal-r1" x="48.8" y="556.8" textLength="24.4" clip-path="url(#terminal-line-22)">• </text><text class="terminal-r7" x="73.2" y="556.8" textLength="109.8" clip-path="url(#terminal-line-22)">/thinking</text><text class="terminal-r1" x="183" y="556.8" textLength="280.6" clip-path="url(#terminal-line-22)">: Select thinking level</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> +</text><text class="terminal-r4" x="24.4" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">⎢</text><text class="terminal-r1" x="48.8" y="581.2" textLength="24.4" clip-path="url(#terminal-line-23)">• </text><text class="terminal-r7" x="73.2" y="581.2" textLength="85.4" clip-path="url(#terminal-line-23)">/reload</text><text class="terminal-r1" x="158.6" y="581.2" textLength="780.8" clip-path="url(#terminal-line-23)">: Reload configuration, agent instructions, and skills from disk</text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> +</text><text class="terminal-r4" x="24.4" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">⎢</text><text class="terminal-r1" x="48.8" y="605.6" textLength="24.4" clip-path="url(#terminal-line-24)">• </text><text class="terminal-r7" x="73.2" y="605.6" textLength="73.2" clip-path="url(#terminal-line-24)">/clear</text><text class="terminal-r1" x="146.4" y="605.6" textLength="341.6" clip-path="url(#terminal-line-24)">: Clear conversation history</text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)"> +</text><text class="terminal-r4" x="24.4" y="630" textLength="12.2" clip-path="url(#terminal-line-25)">⎢</text><text class="terminal-r1" x="48.8" y="630" textLength="24.4" clip-path="url(#terminal-line-25)">• </text><text class="terminal-r7" x="73.2" y="630" textLength="61" clip-path="url(#terminal-line-25)">/copy</text><text class="terminal-r1" x="134.2" y="630" textLength="561.2" clip-path="url(#terminal-line-25)">: Copy the last agent message to the clipboard</text><text class="terminal-r1" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-line-25)"> +</text><text class="terminal-r4" x="24.4" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)">⎢</text><text class="terminal-r1" x="48.8" y="654.4" textLength="24.4" clip-path="url(#terminal-line-26)">• </text><text class="terminal-r7" x="73.2" y="654.4" textLength="48.8" clip-path="url(#terminal-line-26)">/log</text><text class="terminal-r1" x="122" y="654.4" textLength="524.6" clip-path="url(#terminal-line-26)">: Show path to current interaction log file</text><text class="terminal-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)"> +</text><text class="terminal-r4" x="24.4" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)">⎢</text><text class="terminal-r1" x="48.8" y="678.8" textLength="24.4" clip-path="url(#terminal-line-27)">• </text><text class="terminal-r7" x="73.2" y="678.8" textLength="73.2" clip-path="url(#terminal-line-27)">/debug</text><text class="terminal-r1" x="146.4" y="678.8" textLength="268.4" clip-path="url(#terminal-line-27)">: Toggle debug console</text><text class="terminal-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)"> +</text><text class="terminal-r4" x="24.4" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)">⎢</text><text class="terminal-r1" x="48.8" y="703.2" textLength="24.4" clip-path="url(#terminal-line-28)">• </text><text class="terminal-r7" x="73.2" y="703.2" textLength="97.6" clip-path="url(#terminal-line-28)">/compact</text><text class="terminal-r1" x="170.8" y="703.2" textLength="1171.2" clip-path="url(#terminal-line-28)">: Compact conversation history by summarizing. Optionally pass instructions to guide the summary</text><text class="terminal-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)"> +</text><text class="terminal-r4" x="24.4" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)">⎢</text><text class="terminal-r1" x="48.8" y="727.6" textLength="24.4" clip-path="url(#terminal-line-29)">• </text><text class="terminal-r7" x="73.2" y="727.6" textLength="61" clip-path="url(#terminal-line-29)">/exit</text><text class="terminal-r1" x="134.2" y="727.6" textLength="268.4" clip-path="url(#terminal-line-29)">: Exit the application</text><text class="terminal-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)"> +</text><text class="terminal-r4" x="24.4" y="752" textLength="12.2" clip-path="url(#terminal-line-30)">⎢</text><text class="terminal-r1" x="48.8" y="752" textLength="24.4" clip-path="url(#terminal-line-30)">• </text><text class="terminal-r7" x="73.2" y="752" textLength="85.4" clip-path="url(#terminal-line-30)">/status</text><text class="terminal-r1" x="158.6" y="752" textLength="317.2" clip-path="url(#terminal-line-30)">: Display agent statistics</text><text class="terminal-r1" x="1464" y="752" textLength="12.2" clip-path="url(#terminal-line-30)"> +</text><text class="terminal-r4" x="24.4" y="776.4" textLength="12.2" clip-path="url(#terminal-line-31)">⎢</text><text class="terminal-r1" x="48.8" y="776.4" textLength="24.4" clip-path="url(#terminal-line-31)">• </text><text class="terminal-r7" x="73.2" y="776.4" textLength="146.4" clip-path="url(#terminal-line-31)">/proxy-setup</text><text class="terminal-r1" x="219.6" y="776.4" textLength="561.2" clip-path="url(#terminal-line-31)">: Configure proxy and SSL certificate settings</text><text class="terminal-r1" x="1464" y="776.4" textLength="12.2" clip-path="url(#terminal-line-31)"> +</text><text class="terminal-r4" x="24.4" y="800.8" textLength="12.2" clip-path="url(#terminal-line-32)">⎢</text><text class="terminal-r1" x="48.8" y="800.8" textLength="24.4" clip-path="url(#terminal-line-32)">• </text><text class="terminal-r7" x="73.2" y="800.8" textLength="109.8" clip-path="url(#terminal-line-32)">/continue</text><text class="terminal-r1" x="183" y="800.8" textLength="24.4" clip-path="url(#terminal-line-32)">, </text><text class="terminal-r7" x="207.4" y="800.8" textLength="85.4" clip-path="url(#terminal-line-32)">/resume</text><text class="terminal-r1" x="292.8" y="800.8" textLength="402.6" clip-path="url(#terminal-line-32)">: Browse and resume past sessions</text><text class="terminal-r1" x="1464" y="800.8" textLength="12.2" clip-path="url(#terminal-line-32)"> +</text><text class="terminal-r4" x="24.4" y="825.2" textLength="12.2" clip-path="url(#terminal-line-33)">⎢</text><text class="terminal-r1" x="48.8" y="825.2" textLength="24.4" clip-path="url(#terminal-line-33)">• </text><text class="terminal-r7" x="73.2" y="825.2" textLength="85.4" clip-path="url(#terminal-line-33)">/rename</text><text class="terminal-r1" x="158.6" y="825.2" textLength="341.6" clip-path="url(#terminal-line-33)">: Rename the current session</text><text class="terminal-r1" x="1464" y="825.2" textLength="12.2" clip-path="url(#terminal-line-33)"> +</text><text class="terminal-r4" x="24.4" y="849.6" textLength="12.2" clip-path="url(#terminal-line-34)">⎢</text><text class="terminal-r1" x="48.8" y="849.6" textLength="24.4" clip-path="url(#terminal-line-34)">• </text><text class="terminal-r7" x="73.2" y="849.6" textLength="134.2" clip-path="url(#terminal-line-34)">/connectors</text><text class="terminal-r1" x="207.4" y="849.6" textLength="24.4" clip-path="url(#terminal-line-34)">, </text><text class="terminal-r7" x="231.8" y="849.6" textLength="48.8" clip-path="url(#terminal-line-34)">/mcp</text><text class="terminal-r1" x="280.6" y="849.6" textLength="939.4" clip-path="url(#terminal-line-34)">: Display available MCP servers and connectors. Pass a name to list its tools</text><text class="terminal-r1" x="1464" y="849.6" textLength="12.2" clip-path="url(#terminal-line-34)"> +</text><text class="terminal-r4" x="24.4" y="874" textLength="12.2" clip-path="url(#terminal-line-35)">⎢</text><text class="terminal-r1" x="48.8" y="874" textLength="24.4" clip-path="url(#terminal-line-35)">• </text><text class="terminal-r7" x="73.2" y="874" textLength="73.2" clip-path="url(#terminal-line-35)">/voice</text><text class="terminal-r1" x="146.4" y="874" textLength="317.2" clip-path="url(#terminal-line-35)">: Configure voice settings</text><text class="terminal-r1" x="1464" y="874" textLength="12.2" clip-path="url(#terminal-line-35)"> +</text><text class="terminal-r4" x="24.4" y="898.4" textLength="12.2" clip-path="url(#terminal-line-36)">⎢</text><text class="terminal-r1" x="48.8" y="898.4" textLength="24.4" clip-path="url(#terminal-line-36)">• </text><text class="terminal-r7" x="73.2" y="898.4" textLength="122" clip-path="url(#terminal-line-36)">/leanstall</text><text class="terminal-r1" x="195.2" y="898.4" textLength="463.6" clip-path="url(#terminal-line-36)">: Install the Lean 4 agent (leanstral)</text><text class="terminal-r1" x="1464" y="898.4" textLength="12.2" clip-path="url(#terminal-line-36)"> +</text><text class="terminal-r4" x="24.4" y="922.8" textLength="12.2" clip-path="url(#terminal-line-37)">⎢</text><text class="terminal-r1" x="48.8" y="922.8" textLength="24.4" clip-path="url(#terminal-line-37)">• </text><text class="terminal-r7" x="73.2" y="922.8" textLength="146.4" clip-path="url(#terminal-line-37)">/unleanstall</text><text class="terminal-r1" x="219.6" y="922.8" textLength="341.6" clip-path="url(#terminal-line-37)">: Uninstall the Lean 4 agent</text><text class="terminal-r1" x="1464" y="922.8" textLength="12.2" clip-path="url(#terminal-line-37)"> +</text><text class="terminal-r4" x="24.4" y="947.2" textLength="12.2" clip-path="url(#terminal-line-38)">⎢</text><text class="terminal-r1" x="48.8" y="947.2" textLength="24.4" clip-path="url(#terminal-line-38)">• </text><text class="terminal-r7" x="73.2" y="947.2" textLength="85.4" clip-path="url(#terminal-line-38)">/rewind</text><text class="terminal-r1" x="158.6" y="947.2" textLength="366" clip-path="url(#terminal-line-38)">: Rewind to a previous message</text><text class="terminal-r1" x="1464" y="947.2" textLength="12.2" clip-path="url(#terminal-line-38)"> +</text><text class="terminal-r4" x="24.4" y="971.6" textLength="12.2" clip-path="url(#terminal-line-39)">⎣</text><text class="terminal-r1" x="48.8" y="971.6" textLength="24.4" clip-path="url(#terminal-line-39)">• </text><text class="terminal-r7" x="73.2" y="971.6" textLength="183" clip-path="url(#terminal-line-39)">/data-retention</text><text class="terminal-r1" x="256.2" y="971.6" textLength="402.6" clip-path="url(#terminal-line-39)">: Show data retention information</text><text class="terminal-r1" x="1464" y="971.6" textLength="12.2" clip-path="url(#terminal-line-39)"> </text><text class="terminal-r1" x="1464" y="996" textLength="12.2" clip-path="url(#terminal-line-40)"> </text><text class="terminal-r1" x="1464" y="1020.4" textLength="12.2" clip-path="url(#terminal-line-41)"> -</text><text class="terminal-r5" x="0" y="1044.8" textLength="1464" clip-path="url(#terminal-line-42)">┌──────────────────────────────────────────────────────────────────────────────────────────────────────────── default ─┐</text><text class="terminal-r1" x="1464" y="1044.8" textLength="12.2" clip-path="url(#terminal-line-42)"> -</text><text class="terminal-r5" x="0" y="1069.2" textLength="12.2" clip-path="url(#terminal-line-43)">│</text><text class="terminal-r8" x="24.4" y="1069.2" textLength="12.2" clip-path="url(#terminal-line-43)">></text><text class="terminal-r5" x="1451.8" y="1069.2" textLength="12.2" clip-path="url(#terminal-line-43)">│</text><text class="terminal-r1" x="1464" y="1069.2" textLength="12.2" clip-path="url(#terminal-line-43)"> -</text><text class="terminal-r5" x="0" y="1093.6" textLength="12.2" clip-path="url(#terminal-line-44)">│</text><text class="terminal-r5" x="1451.8" y="1093.6" textLength="12.2" clip-path="url(#terminal-line-44)">│</text><text class="terminal-r1" x="1464" y="1093.6" textLength="12.2" clip-path="url(#terminal-line-44)"> -</text><text class="terminal-r5" x="0" y="1118" textLength="12.2" clip-path="url(#terminal-line-45)">│</text><text class="terminal-r5" x="1451.8" y="1118" textLength="12.2" clip-path="url(#terminal-line-45)">│</text><text class="terminal-r1" x="1464" y="1118" textLength="12.2" clip-path="url(#terminal-line-45)"> -</text><text class="terminal-r5" x="0" y="1142.4" textLength="1464" clip-path="url(#terminal-line-46)">└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘</text><text class="terminal-r1" x="1464" y="1142.4" textLength="12.2" clip-path="url(#terminal-line-46)"> -</text><text class="terminal-r5" x="0" y="1166.8" textLength="158.6" clip-path="url(#terminal-line-47)">/test/workdir</text><text class="terminal-r5" x="1256.6" y="1166.8" textLength="207.4" clip-path="url(#terminal-line-47)">0% of 200k tokens</text> +</text><text class="terminal-r4" x="0" y="1044.8" textLength="1464" clip-path="url(#terminal-line-42)">┌──────────────────────────────────────────────────────────────────────────────────────────────────────────── default ─┐</text><text class="terminal-r1" x="1464" y="1044.8" textLength="12.2" clip-path="url(#terminal-line-42)"> +</text><text class="terminal-r4" x="0" y="1069.2" textLength="12.2" clip-path="url(#terminal-line-43)">│</text><text class="terminal-r8" x="24.4" y="1069.2" textLength="12.2" clip-path="url(#terminal-line-43)">></text><text class="terminal-r4" x="1451.8" y="1069.2" textLength="12.2" clip-path="url(#terminal-line-43)">│</text><text class="terminal-r1" x="1464" y="1069.2" textLength="12.2" clip-path="url(#terminal-line-43)"> +</text><text class="terminal-r4" x="0" y="1093.6" textLength="12.2" clip-path="url(#terminal-line-44)">│</text><text class="terminal-r4" x="1451.8" y="1093.6" textLength="12.2" clip-path="url(#terminal-line-44)">│</text><text class="terminal-r1" x="1464" y="1093.6" textLength="12.2" clip-path="url(#terminal-line-44)"> +</text><text class="terminal-r4" x="0" y="1118" textLength="12.2" clip-path="url(#terminal-line-45)">│</text><text class="terminal-r4" x="1451.8" y="1118" textLength="12.2" clip-path="url(#terminal-line-45)">│</text><text class="terminal-r1" x="1464" y="1118" textLength="12.2" clip-path="url(#terminal-line-45)"> +</text><text class="terminal-r4" x="0" y="1142.4" textLength="1464" clip-path="url(#terminal-line-46)">└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘</text><text class="terminal-r1" x="1464" y="1142.4" textLength="12.2" clip-path="url(#terminal-line-46)"> +</text><text class="terminal-r4" x="0" y="1166.8" textLength="158.6" clip-path="url(#terminal-line-47)">/test/workdir</text><text class="terminal-r4" x="1256.6" y="1166.8" textLength="207.4" clip-path="url(#terminal-line-47)">0% of 200k tokens</text> </g> </g> </svg> diff --git a/tests/snapshots/__snapshots__/test_ui_snapshot_teleport/test_snapshot_teleport_command_visible_for_pro_account.svg b/tests/snapshots/__snapshots__/test_ui_snapshot_teleport/test_snapshot_teleport_command_visible_for_pro_account.svg index 923ef43..53f48b7 100644 --- a/tests/snapshots/__snapshots__/test_ui_snapshot_teleport/test_snapshot_teleport_command_visible_for_pro_account.svg +++ b/tests/snapshots/__snapshots__/test_ui_snapshot_teleport/test_snapshot_teleport_command_visible_for_pro_account.svg @@ -33,13 +33,12 @@ } .terminal-r1 { fill: #c5c8c6 } -.terminal-r2 { fill: #68a0b3 } -.terminal-r3 { fill: #4b4e55 } -.terminal-r4 { fill: #9a9b99 } -.terminal-r5 { fill: #608ab1;font-weight: bold } +.terminal-r2 { fill: #9a9b99 } +.terminal-r3 { fill: #608ab1;font-weight: bold } +.terminal-r4 { fill: #98a84b;font-weight: bold } +.terminal-r5 { fill: #4b4e55 } .terminal-r6 { fill: #292929 } -.terminal-r7 { fill: #98a84b;font-weight: bold } -.terminal-r8 { fill: #ff8205;font-weight: bold } +.terminal-r7 { fill: #ff8205;font-weight: bold } </style> <defs> @@ -197,56 +196,56 @@ </g> <g transform="translate(9, 41)" clip-path="url(#terminal-clip-terminal)"> - <rect fill="#4b4e55" x="1451.8" y="50.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="74.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="123.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="147.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="172.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="196.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="221.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="294.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="343.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="367.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="391.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="416.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="440.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="465.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="489.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="513.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="538.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="562.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="587.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="611.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="635.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="660.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="684.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="709.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="733.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="757.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="782.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="806.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="831.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="855.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="879.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="904.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="928.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="953.1" width="12.2" height="24.65" shape-rendering="crispEdges"/> + <rect fill="#4b4e55" x="1451.8" y="74.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="99.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="123.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="147.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="172.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="196.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="221.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="245.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="269.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="294.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="318.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="343.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="367.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="391.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="416.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="440.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="465.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="489.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="513.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="538.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="562.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="587.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="611.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="635.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="660.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="684.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="709.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="733.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="757.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="782.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="806.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="831.1" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="855.5" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="879.9" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="904.3" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="928.7" width="12.2" height="24.65" shape-rendering="crispEdges"/><rect fill="#4b4e55" x="1451.8" y="953.1" width="12.2" height="24.65" shape-rendering="crispEdges"/> <g class="terminal-matrix"> - <text class="terminal-r1" x="0" y="20" textLength="134.2" clip-path="url(#terminal-line-0)">  ⠉⠒⠣⠤⠵⠤⠬⠮⠆</text><text class="terminal-r1" x="170.8" y="20" textLength="61" clip-path="url(#terminal-line-0)">Type </text><text class="terminal-r2" x="231.8" y="20" textLength="61" clip-path="url(#terminal-line-0)">/help</text><text class="terminal-r1" x="292.8" y="20" textLength="256.2" clip-path="url(#terminal-line-0)"> for more information</text><text class="terminal-r1" x="1464" y="20" textLength="12.2" clip-path="url(#terminal-line-0)"> -</text><text class="terminal-r3" x="1451.8" y="44.4" textLength="12.2" clip-path="url(#terminal-line-1)">▁</text><text class="terminal-r1" x="1464" y="44.4" textLength="12.2" clip-path="url(#terminal-line-1)"> -</text><text class="terminal-r4" x="24.4" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)">⎢</text><text class="terminal-r5" x="48.8" y="68.8" textLength="219.6" clip-path="url(#terminal-line-2)">Keyboard Shortcuts</text><text class="terminal-r1" x="1464" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)"> -</text><text class="terminal-r4" x="24.4" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)">⎢</text><text class="terminal-r1" x="48.8" y="93.2" textLength="24.4" clip-path="url(#terminal-line-3)">• </text><text class="terminal-r7" x="73.2" y="93.2" textLength="61" clip-path="url(#terminal-line-3)">Enter</text><text class="terminal-r1" x="134.2" y="93.2" textLength="183" clip-path="url(#terminal-line-3)"> Submit message</text><text class="terminal-r1" x="1464" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)"> -</text><text class="terminal-r4" x="24.4" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)">⎢</text><text class="terminal-r1" x="48.8" y="117.6" textLength="24.4" clip-path="url(#terminal-line-4)">• </text><text class="terminal-r7" x="73.2" y="117.6" textLength="73.2" clip-path="url(#terminal-line-4)">Ctrl+J</text><text class="terminal-r1" x="146.4" y="117.6" textLength="36.6" clip-path="url(#terminal-line-4)"> / </text><text class="terminal-r7" x="183" y="117.6" textLength="134.2" clip-path="url(#terminal-line-4)">Shift+Enter</text><text class="terminal-r1" x="317.2" y="117.6" textLength="183" clip-path="url(#terminal-line-4)"> Insert newline</text><text class="terminal-r1" x="1464" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)"> -</text><text class="terminal-r4" x="24.4" y="142" textLength="12.2" clip-path="url(#terminal-line-5)">⎢</text><text class="terminal-r1" x="48.8" y="142" textLength="24.4" clip-path="url(#terminal-line-5)">• </text><text class="terminal-r7" x="73.2" y="142" textLength="73.2" clip-path="url(#terminal-line-5)">Escape</text><text class="terminal-r1" x="146.4" y="142" textLength="402.6" clip-path="url(#terminal-line-5)"> Interrupt agent or close dialogs</text><text class="terminal-r1" x="1464" y="142" textLength="12.2" clip-path="url(#terminal-line-5)"> -</text><text class="terminal-r4" x="24.4" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)">⎢</text><text class="terminal-r1" x="48.8" y="166.4" textLength="24.4" clip-path="url(#terminal-line-6)">• </text><text class="terminal-r7" x="73.2" y="166.4" textLength="73.2" clip-path="url(#terminal-line-6)">Ctrl+C</text><text class="terminal-r1" x="146.4" y="166.4" textLength="463.6" clip-path="url(#terminal-line-6)"> Quit (or clear input if text present)</text><text class="terminal-r1" x="1464" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)"> -</text><text class="terminal-r4" x="24.4" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)">⎢</text><text class="terminal-r1" x="48.8" y="190.8" textLength="24.4" clip-path="url(#terminal-line-7)">• </text><text class="terminal-r7" x="73.2" y="190.8" textLength="73.2" clip-path="url(#terminal-line-7)">Ctrl+G</text><text class="terminal-r1" x="146.4" y="190.8" textLength="366" clip-path="url(#terminal-line-7)"> Edit input in external editor</text><text class="terminal-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)"> -</text><text class="terminal-r4" x="24.4" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)">⎢</text><text class="terminal-r1" x="48.8" y="215.2" textLength="24.4" clip-path="url(#terminal-line-8)">• </text><text class="terminal-r7" x="73.2" y="215.2" textLength="73.2" clip-path="url(#terminal-line-8)">Ctrl+O</text><text class="terminal-r1" x="146.4" y="215.2" textLength="292.8" clip-path="url(#terminal-line-8)"> Toggle tool output view</text><text class="terminal-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)"> -</text><text class="terminal-r4" x="24.4" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">⎢</text><text class="terminal-r1" x="48.8" y="239.6" textLength="24.4" clip-path="url(#terminal-line-9)">• </text><text class="terminal-r7" x="73.2" y="239.6" textLength="109.8" clip-path="url(#terminal-line-9)">Shift+Tab</text><text class="terminal-r1" x="183" y="239.6" textLength="305" clip-path="url(#terminal-line-9)"> Toggle auto-approve mode</text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)"> -</text><text class="terminal-r4" x="24.4" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">⎢</text><text class="terminal-r1" x="48.8" y="264" textLength="24.4" clip-path="url(#terminal-line-10)">• </text><text class="terminal-r7" x="73.2" y="264" textLength="73.2" clip-path="url(#terminal-line-10)">Alt+↑↓</text><text class="terminal-r1" x="146.4" y="264" textLength="36.6" clip-path="url(#terminal-line-10)"> / </text><text class="terminal-r7" x="183" y="264" textLength="97.6" clip-path="url(#terminal-line-10)">Ctrl+P/N</text><text class="terminal-r1" x="280.6" y="264" textLength="390.4" clip-path="url(#terminal-line-10)"> Rewind to previous/next message</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> -</text><text class="terminal-r4" x="24.4" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">⎢</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> -</text><text class="terminal-r4" x="24.4" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">⎢</text><text class="terminal-r5" x="48.8" y="312.8" textLength="195.2" clip-path="url(#terminal-line-12)">Special Features</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> -</text><text class="terminal-r4" x="24.4" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">⎢</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> -</text><text class="terminal-r4" x="24.4" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">⎢</text><text class="terminal-r1" x="48.8" y="361.6" textLength="24.4" clip-path="url(#terminal-line-14)">• </text><text class="terminal-r7" x="73.2" y="361.6" textLength="122" clip-path="url(#terminal-line-14)">!<command></text><text class="terminal-r1" x="195.2" y="361.6" textLength="366" clip-path="url(#terminal-line-14)"> Execute bash command directly</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> -</text><text class="terminal-r4" x="24.4" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">⎢</text><text class="terminal-r1" x="48.8" y="386" textLength="24.4" clip-path="url(#terminal-line-15)">• </text><text class="terminal-r7" x="73.2" y="386" textLength="170.8" clip-path="url(#terminal-line-15)">@path/to/file/</text><text class="terminal-r1" x="244" y="386" textLength="305" clip-path="url(#terminal-line-15)"> Autocompletes file paths</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> -</text><text class="terminal-r4" x="24.4" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">⎢</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> -</text><text class="terminal-r4" x="24.4" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">⎢</text><text class="terminal-r5" x="48.8" y="434.8" textLength="97.6" clip-path="url(#terminal-line-17)">Commands</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> -</text><text class="terminal-r4" x="24.4" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">⎢</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> -</text><text class="terminal-r4" x="24.4" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">⎢</text><text class="terminal-r1" x="48.8" y="483.6" textLength="24.4" clip-path="url(#terminal-line-19)">• </text><text class="terminal-r7" x="73.2" y="483.6" textLength="61" clip-path="url(#terminal-line-19)">/help</text><text class="terminal-r1" x="134.2" y="483.6" textLength="231.8" clip-path="url(#terminal-line-19)">: Show help message</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> -</text><text class="terminal-r4" x="24.4" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">⎢</text><text class="terminal-r1" x="48.8" y="508" textLength="24.4" clip-path="url(#terminal-line-20)">• </text><text class="terminal-r7" x="73.2" y="508" textLength="85.4" clip-path="url(#terminal-line-20)">/config</text><text class="terminal-r1" x="158.6" y="508" textLength="268.4" clip-path="url(#terminal-line-20)">: Edit config settings</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> -</text><text class="terminal-r4" x="24.4" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">⎢</text><text class="terminal-r1" x="48.8" y="532.4" textLength="24.4" clip-path="url(#terminal-line-21)">• </text><text class="terminal-r7" x="73.2" y="532.4" textLength="73.2" clip-path="url(#terminal-line-21)">/model</text><text class="terminal-r1" x="146.4" y="532.4" textLength="256.2" clip-path="url(#terminal-line-21)">: Select active model</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> -</text><text class="terminal-r4" x="24.4" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">⎢</text><text class="terminal-r1" x="48.8" y="556.8" textLength="24.4" clip-path="url(#terminal-line-22)">• </text><text class="terminal-r7" x="73.2" y="556.8" textLength="109.8" clip-path="url(#terminal-line-22)">/thinking</text><text class="terminal-r1" x="183" y="556.8" textLength="280.6" clip-path="url(#terminal-line-22)">: Select thinking level</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> -</text><text class="terminal-r4" x="24.4" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">⎢</text><text class="terminal-r1" x="48.8" y="581.2" textLength="24.4" clip-path="url(#terminal-line-23)">• </text><text class="terminal-r7" x="73.2" y="581.2" textLength="85.4" clip-path="url(#terminal-line-23)">/reload</text><text class="terminal-r1" x="158.6" y="581.2" textLength="780.8" clip-path="url(#terminal-line-23)">: Reload configuration, agent instructions, and skills from disk</text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> -</text><text class="terminal-r4" x="24.4" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">⎢</text><text class="terminal-r1" x="48.8" y="605.6" textLength="24.4" clip-path="url(#terminal-line-24)">• </text><text class="terminal-r7" x="73.2" y="605.6" textLength="73.2" clip-path="url(#terminal-line-24)">/clear</text><text class="terminal-r1" x="146.4" y="605.6" textLength="341.6" clip-path="url(#terminal-line-24)">: Clear conversation history</text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)"> -</text><text class="terminal-r4" x="24.4" y="630" textLength="12.2" clip-path="url(#terminal-line-25)">⎢</text><text class="terminal-r1" x="48.8" y="630" textLength="24.4" clip-path="url(#terminal-line-25)">• </text><text class="terminal-r7" x="73.2" y="630" textLength="61" clip-path="url(#terminal-line-25)">/copy</text><text class="terminal-r1" x="134.2" y="630" textLength="561.2" clip-path="url(#terminal-line-25)">: Copy the last agent message to the clipboard</text><text class="terminal-r1" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-line-25)"> -</text><text class="terminal-r4" x="24.4" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)">⎢</text><text class="terminal-r1" x="48.8" y="654.4" textLength="24.4" clip-path="url(#terminal-line-26)">• </text><text class="terminal-r7" x="73.2" y="654.4" textLength="48.8" clip-path="url(#terminal-line-26)">/log</text><text class="terminal-r1" x="122" y="654.4" textLength="524.6" clip-path="url(#terminal-line-26)">: Show path to current interaction log file</text><text class="terminal-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)"> -</text><text class="terminal-r4" x="24.4" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)">⎢</text><text class="terminal-r1" x="48.8" y="678.8" textLength="24.4" clip-path="url(#terminal-line-27)">• </text><text class="terminal-r7" x="73.2" y="678.8" textLength="73.2" clip-path="url(#terminal-line-27)">/debug</text><text class="terminal-r1" x="146.4" y="678.8" textLength="268.4" clip-path="url(#terminal-line-27)">: Toggle debug console</text><text class="terminal-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)"> -</text><text class="terminal-r4" x="24.4" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)">⎢</text><text class="terminal-r1" x="48.8" y="703.2" textLength="24.4" clip-path="url(#terminal-line-28)">• </text><text class="terminal-r7" x="73.2" y="703.2" textLength="97.6" clip-path="url(#terminal-line-28)">/compact</text><text class="terminal-r1" x="170.8" y="703.2" textLength="1171.2" clip-path="url(#terminal-line-28)">: Compact conversation history by summarizing. Optionally pass instructions to guide the summary</text><text class="terminal-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)"> -</text><text class="terminal-r4" x="24.4" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)">⎢</text><text class="terminal-r1" x="48.8" y="727.6" textLength="24.4" clip-path="url(#terminal-line-29)">• </text><text class="terminal-r7" x="73.2" y="727.6" textLength="61" clip-path="url(#terminal-line-29)">/exit</text><text class="terminal-r1" x="134.2" y="727.6" textLength="268.4" clip-path="url(#terminal-line-29)">: Exit the application</text><text class="terminal-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)"> -</text><text class="terminal-r4" x="24.4" y="752" textLength="12.2" clip-path="url(#terminal-line-30)">⎢</text><text class="terminal-r1" x="48.8" y="752" textLength="24.4" clip-path="url(#terminal-line-30)">• </text><text class="terminal-r7" x="73.2" y="752" textLength="85.4" clip-path="url(#terminal-line-30)">/status</text><text class="terminal-r1" x="158.6" y="752" textLength="317.2" clip-path="url(#terminal-line-30)">: Display agent statistics</text><text class="terminal-r1" x="1464" y="752" textLength="12.2" clip-path="url(#terminal-line-30)"> -</text><text class="terminal-r4" x="24.4" y="776.4" textLength="12.2" clip-path="url(#terminal-line-31)">⎢</text><text class="terminal-r1" x="48.8" y="776.4" textLength="24.4" clip-path="url(#terminal-line-31)">• </text><text class="terminal-r7" x="73.2" y="776.4" textLength="109.8" clip-path="url(#terminal-line-31)">/teleport</text><text class="terminal-r1" x="183" y="776.4" textLength="378.2" clip-path="url(#terminal-line-31)">: Teleport session to Vibe Code</text><text class="terminal-r1" x="1464" y="776.4" textLength="12.2" clip-path="url(#terminal-line-31)"> -</text><text class="terminal-r4" x="24.4" y="800.8" textLength="12.2" clip-path="url(#terminal-line-32)">⎢</text><text class="terminal-r1" x="48.8" y="800.8" textLength="24.4" clip-path="url(#terminal-line-32)">• </text><text class="terminal-r7" x="73.2" y="800.8" textLength="146.4" clip-path="url(#terminal-line-32)">/proxy-setup</text><text class="terminal-r1" x="219.6" y="800.8" textLength="561.2" clip-path="url(#terminal-line-32)">: Configure proxy and SSL certificate settings</text><text class="terminal-r1" x="1464" y="800.8" textLength="12.2" clip-path="url(#terminal-line-32)"> -</text><text class="terminal-r4" x="24.4" y="825.2" textLength="12.2" clip-path="url(#terminal-line-33)">⎢</text><text class="terminal-r1" x="48.8" y="825.2" textLength="24.4" clip-path="url(#terminal-line-33)">• </text><text class="terminal-r7" x="73.2" y="825.2" textLength="109.8" clip-path="url(#terminal-line-33)">/continue</text><text class="terminal-r1" x="183" y="825.2" textLength="24.4" clip-path="url(#terminal-line-33)">, </text><text class="terminal-r7" x="207.4" y="825.2" textLength="85.4" clip-path="url(#terminal-line-33)">/resume</text><text class="terminal-r1" x="292.8" y="825.2" textLength="402.6" clip-path="url(#terminal-line-33)">: Browse and resume past sessions</text><text class="terminal-r1" x="1464" y="825.2" textLength="12.2" clip-path="url(#terminal-line-33)"> -</text><text class="terminal-r4" x="24.4" y="849.6" textLength="12.2" clip-path="url(#terminal-line-34)">⎢</text><text class="terminal-r1" x="48.8" y="849.6" textLength="24.4" clip-path="url(#terminal-line-34)">• </text><text class="terminal-r7" x="73.2" y="849.6" textLength="134.2" clip-path="url(#terminal-line-34)">/connectors</text><text class="terminal-r1" x="207.4" y="849.6" textLength="24.4" clip-path="url(#terminal-line-34)">, </text><text class="terminal-r7" x="231.8" y="849.6" textLength="48.8" clip-path="url(#terminal-line-34)">/mcp</text><text class="terminal-r1" x="280.6" y="849.6" textLength="939.4" clip-path="url(#terminal-line-34)">: Display available MCP servers and connectors. Pass a name to list its tools</text><text class="terminal-r1" x="1464" y="849.6" textLength="12.2" clip-path="url(#terminal-line-34)"> -</text><text class="terminal-r4" x="24.4" y="874" textLength="12.2" clip-path="url(#terminal-line-35)">⎢</text><text class="terminal-r1" x="48.8" y="874" textLength="24.4" clip-path="url(#terminal-line-35)">• </text><text class="terminal-r7" x="73.2" y="874" textLength="73.2" clip-path="url(#terminal-line-35)">/voice</text><text class="terminal-r1" x="146.4" y="874" textLength="317.2" clip-path="url(#terminal-line-35)">: Configure voice settings</text><text class="terminal-r1" x="1464" y="874" textLength="12.2" clip-path="url(#terminal-line-35)"> -</text><text class="terminal-r4" x="24.4" y="898.4" textLength="12.2" clip-path="url(#terminal-line-36)">⎢</text><text class="terminal-r1" x="48.8" y="898.4" textLength="24.4" clip-path="url(#terminal-line-36)">• </text><text class="terminal-r7" x="73.2" y="898.4" textLength="122" clip-path="url(#terminal-line-36)">/leanstall</text><text class="terminal-r1" x="195.2" y="898.4" textLength="463.6" clip-path="url(#terminal-line-36)">: Install the Lean 4 agent (leanstral)</text><text class="terminal-r1" x="1464" y="898.4" textLength="12.2" clip-path="url(#terminal-line-36)"> -</text><text class="terminal-r4" x="24.4" y="922.8" textLength="12.2" clip-path="url(#terminal-line-37)">⎢</text><text class="terminal-r1" x="48.8" y="922.8" textLength="24.4" clip-path="url(#terminal-line-37)">• </text><text class="terminal-r7" x="73.2" y="922.8" textLength="146.4" clip-path="url(#terminal-line-37)">/unleanstall</text><text class="terminal-r1" x="219.6" y="922.8" textLength="341.6" clip-path="url(#terminal-line-37)">: Uninstall the Lean 4 agent</text><text class="terminal-r1" x="1464" y="922.8" textLength="12.2" clip-path="url(#terminal-line-37)"> -</text><text class="terminal-r4" x="24.4" y="947.2" textLength="12.2" clip-path="url(#terminal-line-38)">⎢</text><text class="terminal-r1" x="48.8" y="947.2" textLength="24.4" clip-path="url(#terminal-line-38)">• </text><text class="terminal-r7" x="73.2" y="947.2" textLength="85.4" clip-path="url(#terminal-line-38)">/rewind</text><text class="terminal-r1" x="158.6" y="947.2" textLength="366" clip-path="url(#terminal-line-38)">: Rewind to a previous message</text><text class="terminal-r1" x="1464" y="947.2" textLength="12.2" clip-path="url(#terminal-line-38)"> -</text><text class="terminal-r4" x="24.4" y="971.6" textLength="12.2" clip-path="url(#terminal-line-39)">⎣</text><text class="terminal-r1" x="48.8" y="971.6" textLength="24.4" clip-path="url(#terminal-line-39)">• </text><text class="terminal-r7" x="73.2" y="971.6" textLength="183" clip-path="url(#terminal-line-39)">/data-retention</text><text class="terminal-r1" x="256.2" y="971.6" textLength="402.6" clip-path="url(#terminal-line-39)">: Show data retention information</text><text class="terminal-r1" x="1464" y="971.6" textLength="12.2" clip-path="url(#terminal-line-39)"> + <text class="terminal-r1" x="1464" y="20" textLength="12.2" clip-path="url(#terminal-line-0)"> +</text><text class="terminal-r2" x="24.4" y="44.4" textLength="12.2" clip-path="url(#terminal-line-1)">⎢</text><text class="terminal-r3" x="48.8" y="44.4" textLength="219.6" clip-path="url(#terminal-line-1)">Keyboard Shortcuts</text><text class="terminal-r1" x="1464" y="44.4" textLength="12.2" clip-path="url(#terminal-line-1)"> +</text><text class="terminal-r2" x="24.4" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)">⎢</text><text class="terminal-r1" x="48.8" y="68.8" textLength="24.4" clip-path="url(#terminal-line-2)">• </text><text class="terminal-r4" x="73.2" y="68.8" textLength="61" clip-path="url(#terminal-line-2)">Enter</text><text class="terminal-r1" x="134.2" y="68.8" textLength="183" clip-path="url(#terminal-line-2)"> Submit message</text><text class="terminal-r5" x="1451.8" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)">▂</text><text class="terminal-r1" x="1464" y="68.8" textLength="12.2" clip-path="url(#terminal-line-2)"> +</text><text class="terminal-r2" x="24.4" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)">⎢</text><text class="terminal-r1" x="48.8" y="93.2" textLength="24.4" clip-path="url(#terminal-line-3)">• </text><text class="terminal-r4" x="73.2" y="93.2" textLength="73.2" clip-path="url(#terminal-line-3)">Ctrl+J</text><text class="terminal-r1" x="146.4" y="93.2" textLength="36.6" clip-path="url(#terminal-line-3)"> / </text><text class="terminal-r4" x="183" y="93.2" textLength="134.2" clip-path="url(#terminal-line-3)">Shift+Enter</text><text class="terminal-r1" x="317.2" y="93.2" textLength="183" clip-path="url(#terminal-line-3)"> Insert newline</text><text class="terminal-r1" x="1464" y="93.2" textLength="12.2" clip-path="url(#terminal-line-3)"> +</text><text class="terminal-r2" x="24.4" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)">⎢</text><text class="terminal-r1" x="48.8" y="117.6" textLength="24.4" clip-path="url(#terminal-line-4)">• </text><text class="terminal-r4" x="73.2" y="117.6" textLength="73.2" clip-path="url(#terminal-line-4)">Escape</text><text class="terminal-r1" x="146.4" y="117.6" textLength="402.6" clip-path="url(#terminal-line-4)"> Interrupt agent or close dialogs</text><text class="terminal-r1" x="1464" y="117.6" textLength="12.2" clip-path="url(#terminal-line-4)"> +</text><text class="terminal-r2" x="24.4" y="142" textLength="12.2" clip-path="url(#terminal-line-5)">⎢</text><text class="terminal-r1" x="48.8" y="142" textLength="24.4" clip-path="url(#terminal-line-5)">• </text><text class="terminal-r4" x="73.2" y="142" textLength="73.2" clip-path="url(#terminal-line-5)">Ctrl+C</text><text class="terminal-r1" x="146.4" y="142" textLength="463.6" clip-path="url(#terminal-line-5)"> Quit (or clear input if text present)</text><text class="terminal-r1" x="1464" y="142" textLength="12.2" clip-path="url(#terminal-line-5)"> +</text><text class="terminal-r2" x="24.4" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)">⎢</text><text class="terminal-r1" x="48.8" y="166.4" textLength="24.4" clip-path="url(#terminal-line-6)">• </text><text class="terminal-r4" x="73.2" y="166.4" textLength="73.2" clip-path="url(#terminal-line-6)">Ctrl+G</text><text class="terminal-r1" x="146.4" y="166.4" textLength="366" clip-path="url(#terminal-line-6)"> Edit input in external editor</text><text class="terminal-r1" x="1464" y="166.4" textLength="12.2" clip-path="url(#terminal-line-6)"> +</text><text class="terminal-r2" x="24.4" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)">⎢</text><text class="terminal-r1" x="48.8" y="190.8" textLength="24.4" clip-path="url(#terminal-line-7)">• </text><text class="terminal-r4" x="73.2" y="190.8" textLength="73.2" clip-path="url(#terminal-line-7)">Ctrl+O</text><text class="terminal-r1" x="146.4" y="190.8" textLength="292.8" clip-path="url(#terminal-line-7)"> Toggle tool output view</text><text class="terminal-r1" x="1464" y="190.8" textLength="12.2" clip-path="url(#terminal-line-7)"> +</text><text class="terminal-r2" x="24.4" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)">⎢</text><text class="terminal-r1" x="48.8" y="215.2" textLength="24.4" clip-path="url(#terminal-line-8)">• </text><text class="terminal-r4" x="73.2" y="215.2" textLength="109.8" clip-path="url(#terminal-line-8)">Shift+Tab</text><text class="terminal-r1" x="183" y="215.2" textLength="512.4" clip-path="url(#terminal-line-8)"> Cycle through agents (default, plan, ...)</text><text class="terminal-r1" x="1464" y="215.2" textLength="12.2" clip-path="url(#terminal-line-8)"> +</text><text class="terminal-r2" x="24.4" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)">⎢</text><text class="terminal-r1" x="48.8" y="239.6" textLength="24.4" clip-path="url(#terminal-line-9)">• </text><text class="terminal-r4" x="73.2" y="239.6" textLength="73.2" clip-path="url(#terminal-line-9)">Alt+↑↓</text><text class="terminal-r1" x="146.4" y="239.6" textLength="36.6" clip-path="url(#terminal-line-9)"> / </text><text class="terminal-r4" x="183" y="239.6" textLength="97.6" clip-path="url(#terminal-line-9)">Ctrl+P/N</text><text class="terminal-r1" x="280.6" y="239.6" textLength="390.4" clip-path="url(#terminal-line-9)"> Rewind to previous/next message</text><text class="terminal-r1" x="1464" y="239.6" textLength="12.2" clip-path="url(#terminal-line-9)"> +</text><text class="terminal-r2" x="24.4" y="264" textLength="12.2" clip-path="url(#terminal-line-10)">⎢</text><text class="terminal-r1" x="1464" y="264" textLength="12.2" clip-path="url(#terminal-line-10)"> +</text><text class="terminal-r2" x="24.4" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)">⎢</text><text class="terminal-r3" x="48.8" y="288.4" textLength="195.2" clip-path="url(#terminal-line-11)">Special Features</text><text class="terminal-r1" x="1464" y="288.4" textLength="12.2" clip-path="url(#terminal-line-11)"> +</text><text class="terminal-r2" x="24.4" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)">⎢</text><text class="terminal-r1" x="1464" y="312.8" textLength="12.2" clip-path="url(#terminal-line-12)"> +</text><text class="terminal-r2" x="24.4" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)">⎢</text><text class="terminal-r1" x="48.8" y="337.2" textLength="24.4" clip-path="url(#terminal-line-13)">• </text><text class="terminal-r4" x="73.2" y="337.2" textLength="122" clip-path="url(#terminal-line-13)">!<command></text><text class="terminal-r1" x="195.2" y="337.2" textLength="366" clip-path="url(#terminal-line-13)"> Execute bash command directly</text><text class="terminal-r1" x="1464" y="337.2" textLength="12.2" clip-path="url(#terminal-line-13)"> +</text><text class="terminal-r2" x="24.4" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)">⎢</text><text class="terminal-r1" x="48.8" y="361.6" textLength="24.4" clip-path="url(#terminal-line-14)">• </text><text class="terminal-r4" x="73.2" y="361.6" textLength="170.8" clip-path="url(#terminal-line-14)">@path/to/file/</text><text class="terminal-r1" x="244" y="361.6" textLength="305" clip-path="url(#terminal-line-14)"> Autocompletes file paths</text><text class="terminal-r1" x="1464" y="361.6" textLength="12.2" clip-path="url(#terminal-line-14)"> +</text><text class="terminal-r2" x="24.4" y="386" textLength="12.2" clip-path="url(#terminal-line-15)">⎢</text><text class="terminal-r1" x="1464" y="386" textLength="12.2" clip-path="url(#terminal-line-15)"> +</text><text class="terminal-r2" x="24.4" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)">⎢</text><text class="terminal-r3" x="48.8" y="410.4" textLength="97.6" clip-path="url(#terminal-line-16)">Commands</text><text class="terminal-r1" x="1464" y="410.4" textLength="12.2" clip-path="url(#terminal-line-16)"> +</text><text class="terminal-r2" x="24.4" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)">⎢</text><text class="terminal-r1" x="1464" y="434.8" textLength="12.2" clip-path="url(#terminal-line-17)"> +</text><text class="terminal-r2" x="24.4" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)">⎢</text><text class="terminal-r1" x="48.8" y="459.2" textLength="24.4" clip-path="url(#terminal-line-18)">• </text><text class="terminal-r4" x="73.2" y="459.2" textLength="61" clip-path="url(#terminal-line-18)">/help</text><text class="terminal-r1" x="134.2" y="459.2" textLength="231.8" clip-path="url(#terminal-line-18)">: Show help message</text><text class="terminal-r1" x="1464" y="459.2" textLength="12.2" clip-path="url(#terminal-line-18)"> +</text><text class="terminal-r2" x="24.4" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)">⎢</text><text class="terminal-r1" x="48.8" y="483.6" textLength="24.4" clip-path="url(#terminal-line-19)">• </text><text class="terminal-r4" x="73.2" y="483.6" textLength="85.4" clip-path="url(#terminal-line-19)">/config</text><text class="terminal-r1" x="158.6" y="483.6" textLength="268.4" clip-path="url(#terminal-line-19)">: Edit config settings</text><text class="terminal-r1" x="1464" y="483.6" textLength="12.2" clip-path="url(#terminal-line-19)"> +</text><text class="terminal-r2" x="24.4" y="508" textLength="12.2" clip-path="url(#terminal-line-20)">⎢</text><text class="terminal-r1" x="48.8" y="508" textLength="24.4" clip-path="url(#terminal-line-20)">• </text><text class="terminal-r4" x="73.2" y="508" textLength="73.2" clip-path="url(#terminal-line-20)">/model</text><text class="terminal-r1" x="146.4" y="508" textLength="256.2" clip-path="url(#terminal-line-20)">: Select active model</text><text class="terminal-r1" x="1464" y="508" textLength="12.2" clip-path="url(#terminal-line-20)"> +</text><text class="terminal-r2" x="24.4" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)">⎢</text><text class="terminal-r1" x="48.8" y="532.4" textLength="24.4" clip-path="url(#terminal-line-21)">• </text><text class="terminal-r4" x="73.2" y="532.4" textLength="109.8" clip-path="url(#terminal-line-21)">/thinking</text><text class="terminal-r1" x="183" y="532.4" textLength="280.6" clip-path="url(#terminal-line-21)">: Select thinking level</text><text class="terminal-r1" x="1464" y="532.4" textLength="12.2" clip-path="url(#terminal-line-21)"> +</text><text class="terminal-r2" x="24.4" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)">⎢</text><text class="terminal-r1" x="48.8" y="556.8" textLength="24.4" clip-path="url(#terminal-line-22)">• </text><text class="terminal-r4" x="73.2" y="556.8" textLength="85.4" clip-path="url(#terminal-line-22)">/reload</text><text class="terminal-r1" x="158.6" y="556.8" textLength="780.8" clip-path="url(#terminal-line-22)">: Reload configuration, agent instructions, and skills from disk</text><text class="terminal-r1" x="1464" y="556.8" textLength="12.2" clip-path="url(#terminal-line-22)"> +</text><text class="terminal-r2" x="24.4" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)">⎢</text><text class="terminal-r1" x="48.8" y="581.2" textLength="24.4" clip-path="url(#terminal-line-23)">• </text><text class="terminal-r4" x="73.2" y="581.2" textLength="73.2" clip-path="url(#terminal-line-23)">/clear</text><text class="terminal-r1" x="146.4" y="581.2" textLength="341.6" clip-path="url(#terminal-line-23)">: Clear conversation history</text><text class="terminal-r1" x="1464" y="581.2" textLength="12.2" clip-path="url(#terminal-line-23)"> +</text><text class="terminal-r2" x="24.4" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)">⎢</text><text class="terminal-r1" x="48.8" y="605.6" textLength="24.4" clip-path="url(#terminal-line-24)">• </text><text class="terminal-r4" x="73.2" y="605.6" textLength="61" clip-path="url(#terminal-line-24)">/copy</text><text class="terminal-r1" x="134.2" y="605.6" textLength="561.2" clip-path="url(#terminal-line-24)">: Copy the last agent message to the clipboard</text><text class="terminal-r1" x="1464" y="605.6" textLength="12.2" clip-path="url(#terminal-line-24)"> +</text><text class="terminal-r2" x="24.4" y="630" textLength="12.2" clip-path="url(#terminal-line-25)">⎢</text><text class="terminal-r1" x="48.8" y="630" textLength="24.4" clip-path="url(#terminal-line-25)">• </text><text class="terminal-r4" x="73.2" y="630" textLength="48.8" clip-path="url(#terminal-line-25)">/log</text><text class="terminal-r1" x="122" y="630" textLength="524.6" clip-path="url(#terminal-line-25)">: Show path to current interaction log file</text><text class="terminal-r1" x="1464" y="630" textLength="12.2" clip-path="url(#terminal-line-25)"> +</text><text class="terminal-r2" x="24.4" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)">⎢</text><text class="terminal-r1" x="48.8" y="654.4" textLength="24.4" clip-path="url(#terminal-line-26)">• </text><text class="terminal-r4" x="73.2" y="654.4" textLength="73.2" clip-path="url(#terminal-line-26)">/debug</text><text class="terminal-r1" x="146.4" y="654.4" textLength="268.4" clip-path="url(#terminal-line-26)">: Toggle debug console</text><text class="terminal-r1" x="1464" y="654.4" textLength="12.2" clip-path="url(#terminal-line-26)"> +</text><text class="terminal-r2" x="24.4" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)">⎢</text><text class="terminal-r1" x="48.8" y="678.8" textLength="24.4" clip-path="url(#terminal-line-27)">• </text><text class="terminal-r4" x="73.2" y="678.8" textLength="97.6" clip-path="url(#terminal-line-27)">/compact</text><text class="terminal-r1" x="170.8" y="678.8" textLength="1171.2" clip-path="url(#terminal-line-27)">: Compact conversation history by summarizing. Optionally pass instructions to guide the summary</text><text class="terminal-r1" x="1464" y="678.8" textLength="12.2" clip-path="url(#terminal-line-27)"> +</text><text class="terminal-r2" x="24.4" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)">⎢</text><text class="terminal-r1" x="48.8" y="703.2" textLength="24.4" clip-path="url(#terminal-line-28)">• </text><text class="terminal-r4" x="73.2" y="703.2" textLength="61" clip-path="url(#terminal-line-28)">/exit</text><text class="terminal-r1" x="134.2" y="703.2" textLength="268.4" clip-path="url(#terminal-line-28)">: Exit the application</text><text class="terminal-r1" x="1464" y="703.2" textLength="12.2" clip-path="url(#terminal-line-28)"> +</text><text class="terminal-r2" x="24.4" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)">⎢</text><text class="terminal-r1" x="48.8" y="727.6" textLength="24.4" clip-path="url(#terminal-line-29)">• </text><text class="terminal-r4" x="73.2" y="727.6" textLength="85.4" clip-path="url(#terminal-line-29)">/status</text><text class="terminal-r1" x="158.6" y="727.6" textLength="317.2" clip-path="url(#terminal-line-29)">: Display agent statistics</text><text class="terminal-r1" x="1464" y="727.6" textLength="12.2" clip-path="url(#terminal-line-29)"> +</text><text class="terminal-r2" x="24.4" y="752" textLength="12.2" clip-path="url(#terminal-line-30)">⎢</text><text class="terminal-r1" x="48.8" y="752" textLength="24.4" clip-path="url(#terminal-line-30)">• </text><text class="terminal-r4" x="73.2" y="752" textLength="109.8" clip-path="url(#terminal-line-30)">/teleport</text><text class="terminal-r1" x="183" y="752" textLength="378.2" clip-path="url(#terminal-line-30)">: Teleport session to Vibe Code</text><text class="terminal-r1" x="1464" y="752" textLength="12.2" clip-path="url(#terminal-line-30)"> +</text><text class="terminal-r2" x="24.4" y="776.4" textLength="12.2" clip-path="url(#terminal-line-31)">⎢</text><text class="terminal-r1" x="48.8" y="776.4" textLength="24.4" clip-path="url(#terminal-line-31)">• </text><text class="terminal-r4" x="73.2" y="776.4" textLength="146.4" clip-path="url(#terminal-line-31)">/proxy-setup</text><text class="terminal-r1" x="219.6" y="776.4" textLength="561.2" clip-path="url(#terminal-line-31)">: Configure proxy and SSL certificate settings</text><text class="terminal-r1" x="1464" y="776.4" textLength="12.2" clip-path="url(#terminal-line-31)"> +</text><text class="terminal-r2" x="24.4" y="800.8" textLength="12.2" clip-path="url(#terminal-line-32)">⎢</text><text class="terminal-r1" x="48.8" y="800.8" textLength="24.4" clip-path="url(#terminal-line-32)">• </text><text class="terminal-r4" x="73.2" y="800.8" textLength="109.8" clip-path="url(#terminal-line-32)">/continue</text><text class="terminal-r1" x="183" y="800.8" textLength="24.4" clip-path="url(#terminal-line-32)">, </text><text class="terminal-r4" x="207.4" y="800.8" textLength="85.4" clip-path="url(#terminal-line-32)">/resume</text><text class="terminal-r1" x="292.8" y="800.8" textLength="402.6" clip-path="url(#terminal-line-32)">: Browse and resume past sessions</text><text class="terminal-r1" x="1464" y="800.8" textLength="12.2" clip-path="url(#terminal-line-32)"> +</text><text class="terminal-r2" x="24.4" y="825.2" textLength="12.2" clip-path="url(#terminal-line-33)">⎢</text><text class="terminal-r1" x="48.8" y="825.2" textLength="24.4" clip-path="url(#terminal-line-33)">• </text><text class="terminal-r4" x="73.2" y="825.2" textLength="85.4" clip-path="url(#terminal-line-33)">/rename</text><text class="terminal-r1" x="158.6" y="825.2" textLength="341.6" clip-path="url(#terminal-line-33)">: Rename the current session</text><text class="terminal-r1" x="1464" y="825.2" textLength="12.2" clip-path="url(#terminal-line-33)"> +</text><text class="terminal-r2" x="24.4" y="849.6" textLength="12.2" clip-path="url(#terminal-line-34)">⎢</text><text class="terminal-r1" x="48.8" y="849.6" textLength="24.4" clip-path="url(#terminal-line-34)">• </text><text class="terminal-r4" x="73.2" y="849.6" textLength="134.2" clip-path="url(#terminal-line-34)">/connectors</text><text class="terminal-r1" x="207.4" y="849.6" textLength="24.4" clip-path="url(#terminal-line-34)">, </text><text class="terminal-r4" x="231.8" y="849.6" textLength="48.8" clip-path="url(#terminal-line-34)">/mcp</text><text class="terminal-r1" x="280.6" y="849.6" textLength="939.4" clip-path="url(#terminal-line-34)">: Display available MCP servers and connectors. Pass a name to list its tools</text><text class="terminal-r1" x="1464" y="849.6" textLength="12.2" clip-path="url(#terminal-line-34)"> +</text><text class="terminal-r2" x="24.4" y="874" textLength="12.2" clip-path="url(#terminal-line-35)">⎢</text><text class="terminal-r1" x="48.8" y="874" textLength="24.4" clip-path="url(#terminal-line-35)">• </text><text class="terminal-r4" x="73.2" y="874" textLength="73.2" clip-path="url(#terminal-line-35)">/voice</text><text class="terminal-r1" x="146.4" y="874" textLength="317.2" clip-path="url(#terminal-line-35)">: Configure voice settings</text><text class="terminal-r1" x="1464" y="874" textLength="12.2" clip-path="url(#terminal-line-35)"> +</text><text class="terminal-r2" x="24.4" y="898.4" textLength="12.2" clip-path="url(#terminal-line-36)">⎢</text><text class="terminal-r1" x="48.8" y="898.4" textLength="24.4" clip-path="url(#terminal-line-36)">• </text><text class="terminal-r4" x="73.2" y="898.4" textLength="122" clip-path="url(#terminal-line-36)">/leanstall</text><text class="terminal-r1" x="195.2" y="898.4" textLength="463.6" clip-path="url(#terminal-line-36)">: Install the Lean 4 agent (leanstral)</text><text class="terminal-r1" x="1464" y="898.4" textLength="12.2" clip-path="url(#terminal-line-36)"> +</text><text class="terminal-r2" x="24.4" y="922.8" textLength="12.2" clip-path="url(#terminal-line-37)">⎢</text><text class="terminal-r1" x="48.8" y="922.8" textLength="24.4" clip-path="url(#terminal-line-37)">• </text><text class="terminal-r4" x="73.2" y="922.8" textLength="146.4" clip-path="url(#terminal-line-37)">/unleanstall</text><text class="terminal-r1" x="219.6" y="922.8" textLength="341.6" clip-path="url(#terminal-line-37)">: Uninstall the Lean 4 agent</text><text class="terminal-r1" x="1464" y="922.8" textLength="12.2" clip-path="url(#terminal-line-37)"> +</text><text class="terminal-r2" x="24.4" y="947.2" textLength="12.2" clip-path="url(#terminal-line-38)">⎢</text><text class="terminal-r1" x="48.8" y="947.2" textLength="24.4" clip-path="url(#terminal-line-38)">• </text><text class="terminal-r4" x="73.2" y="947.2" textLength="85.4" clip-path="url(#terminal-line-38)">/rewind</text><text class="terminal-r1" x="158.6" y="947.2" textLength="366" clip-path="url(#terminal-line-38)">: Rewind to a previous message</text><text class="terminal-r1" x="1464" y="947.2" textLength="12.2" clip-path="url(#terminal-line-38)"> +</text><text class="terminal-r2" x="24.4" y="971.6" textLength="12.2" clip-path="url(#terminal-line-39)">⎣</text><text class="terminal-r1" x="48.8" y="971.6" textLength="24.4" clip-path="url(#terminal-line-39)">• </text><text class="terminal-r4" x="73.2" y="971.6" textLength="183" clip-path="url(#terminal-line-39)">/data-retention</text><text class="terminal-r1" x="256.2" y="971.6" textLength="402.6" clip-path="url(#terminal-line-39)">: Show data retention information</text><text class="terminal-r1" x="1464" y="971.6" textLength="12.2" clip-path="url(#terminal-line-39)"> </text><text class="terminal-r1" x="1464" y="996" textLength="12.2" clip-path="url(#terminal-line-40)"> </text><text class="terminal-r1" x="1464" y="1020.4" textLength="12.2" clip-path="url(#terminal-line-41)"> -</text><text class="terminal-r4" x="0" y="1044.8" textLength="1464" clip-path="url(#terminal-line-42)">┌──────────────────────────────────────────────────────────────────────────────────────────────────────────── default ─┐</text><text class="terminal-r1" x="1464" y="1044.8" textLength="12.2" clip-path="url(#terminal-line-42)"> -</text><text class="terminal-r4" x="0" y="1069.2" textLength="12.2" clip-path="url(#terminal-line-43)">│</text><text class="terminal-r8" x="24.4" y="1069.2" textLength="12.2" clip-path="url(#terminal-line-43)">></text><text class="terminal-r4" x="1451.8" y="1069.2" textLength="12.2" clip-path="url(#terminal-line-43)">│</text><text class="terminal-r1" x="1464" y="1069.2" textLength="12.2" clip-path="url(#terminal-line-43)"> -</text><text class="terminal-r4" x="0" y="1093.6" textLength="12.2" clip-path="url(#terminal-line-44)">│</text><text class="terminal-r4" x="1451.8" y="1093.6" textLength="12.2" clip-path="url(#terminal-line-44)">│</text><text class="terminal-r1" x="1464" y="1093.6" textLength="12.2" clip-path="url(#terminal-line-44)"> -</text><text class="terminal-r4" x="0" y="1118" textLength="12.2" clip-path="url(#terminal-line-45)">│</text><text class="terminal-r4" x="1451.8" y="1118" textLength="12.2" clip-path="url(#terminal-line-45)">│</text><text class="terminal-r1" x="1464" y="1118" textLength="12.2" clip-path="url(#terminal-line-45)"> -</text><text class="terminal-r4" x="0" y="1142.4" textLength="1464" clip-path="url(#terminal-line-46)">└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘</text><text class="terminal-r1" x="1464" y="1142.4" textLength="12.2" clip-path="url(#terminal-line-46)"> -</text><text class="terminal-r4" x="0" y="1166.8" textLength="158.6" clip-path="url(#terminal-line-47)">/test/workdir</text><text class="terminal-r4" x="1256.6" y="1166.8" textLength="207.4" clip-path="url(#terminal-line-47)">0% of 200k tokens</text> +</text><text class="terminal-r2" x="0" y="1044.8" textLength="1464" clip-path="url(#terminal-line-42)">┌──────────────────────────────────────────────────────────────────────────────────────────────────────────── default ─┐</text><text class="terminal-r1" x="1464" y="1044.8" textLength="12.2" clip-path="url(#terminal-line-42)"> +</text><text class="terminal-r2" x="0" y="1069.2" textLength="12.2" clip-path="url(#terminal-line-43)">│</text><text class="terminal-r7" x="24.4" y="1069.2" textLength="12.2" clip-path="url(#terminal-line-43)">></text><text class="terminal-r2" x="1451.8" y="1069.2" textLength="12.2" clip-path="url(#terminal-line-43)">│</text><text class="terminal-r1" x="1464" y="1069.2" textLength="12.2" clip-path="url(#terminal-line-43)"> +</text><text class="terminal-r2" x="0" y="1093.6" textLength="12.2" clip-path="url(#terminal-line-44)">│</text><text class="terminal-r2" x="1451.8" y="1093.6" textLength="12.2" clip-path="url(#terminal-line-44)">│</text><text class="terminal-r1" x="1464" y="1093.6" textLength="12.2" clip-path="url(#terminal-line-44)"> +</text><text class="terminal-r2" x="0" y="1118" textLength="12.2" clip-path="url(#terminal-line-45)">│</text><text class="terminal-r2" x="1451.8" y="1118" textLength="12.2" clip-path="url(#terminal-line-45)">│</text><text class="terminal-r1" x="1464" y="1118" textLength="12.2" clip-path="url(#terminal-line-45)"> +</text><text class="terminal-r2" x="0" y="1142.4" textLength="1464" clip-path="url(#terminal-line-46)">└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘</text><text class="terminal-r1" x="1464" y="1142.4" textLength="12.2" clip-path="url(#terminal-line-46)"> +</text><text class="terminal-r2" x="0" y="1166.8" textLength="158.6" clip-path="url(#terminal-line-47)">/test/workdir</text><text class="terminal-r2" x="1256.6" y="1166.8" textLength="207.4" clip-path="url(#terminal-line-47)">0% of 200k tokens</text> </g> </g> </svg> diff --git a/tests/test_agent_observer_streaming.py b/tests/test_agent_observer_streaming.py index d1a43e2..32784b3 100644 --- a/tests/test_agent_observer_streaming.py +++ b/tests/test_agent_observer_streaming.py @@ -536,6 +536,51 @@ async def test_context_too_long_non_streaming(observer_capture) -> None: assert agent.session_logger.save_interaction.await_count == 1 +class _NonRetryableError(Exception): + # Mimics Temporal's ``ApplicationError(non_retryable=True)`` without + # pulling temporalio into vibe's test deps. The wrap-site check relies + # on the truthy ``non_retryable`` attribute, not the concrete type. + non_retryable = True + + +@pytest.mark.asyncio +async def test_non_retryable_passes_through_streaming(observer_capture) -> None: + observed, observer = observer_capture + backend = FakeBackend(exception_to_raise=_NonRetryableError("auth failed")) + agent = build_test_agent_loop( + config=make_config(), + backend=backend, + message_observer=observer, + enable_streaming=True, + ) + agent.session_logger.save_interaction = AsyncMock(return_value=None) + + with pytest.raises(_NonRetryableError, match="auth failed"): + [_ async for _ in agent.act("Trigger non-retryable failure while streaming")] + + assert [role for role, _ in observed] == [Role.system, Role.user] + assert agent.session_logger.save_interaction.await_count == 1 + + +@pytest.mark.asyncio +async def test_non_retryable_passes_through_non_streaming(observer_capture) -> None: + observed, observer = observer_capture + backend = FakeBackend(exception_to_raise=_NonRetryableError("auth failed")) + agent = build_test_agent_loop( + config=make_config(), + backend=backend, + message_observer=observer, + enable_streaming=False, + ) + agent.session_logger.save_interaction = AsyncMock(return_value=None) + + with pytest.raises(_NonRetryableError, match="auth failed"): + [_ async for _ in agent.act("Trigger non-retryable failure without streaming")] + + assert [role for role, _ in observed] == [Role.system, Role.user] + assert agent.session_logger.save_interaction.await_count == 1 + + def _snapshot_events(events: list) -> list[tuple[str, str]]: return [ (type(e).__name__, e.content) diff --git a/tests/test_agent_stats.py b/tests/test_agent_stats.py index 7050892..c0c5afc 100644 --- a/tests/test_agent_stats.py +++ b/tests/test_agent_stats.py @@ -627,6 +627,7 @@ class TestClearHistoryFullReset: assert agent.session_id != original_session_id assert agent.session_id == agent.session_logger.session_id + assert agent.parent_session_id is None class TestClearHistoryObserverBugfix: diff --git a/tests/test_approve_always_permanent.py b/tests/test_approve_always_permanent.py new file mode 100644 index 0000000..174effc --- /dev/null +++ b/tests/test_approve_always_permanent.py @@ -0,0 +1,120 @@ +from __future__ import annotations + +from pathlib import Path +import tomllib + +from tests.conftest import build_test_agent_loop, build_test_vibe_config +from vibe.core.tools.permissions import PermissionScope, RequiredPermission + + +def _read_persisted_config(config_dir: Path) -> dict: + config_file = config_dir / "config.toml" + with config_file.open("rb") as f: + return tomllib.load(f) + + +class TestApproveAlwaysPermanentNoGranularPermissions: + def test_sets_tool_permission_always_in_config(self, config_dir: Path): + agent = build_test_agent_loop() + + agent.approve_always("bash", None, save_permanently=True) + + persisted = _read_persisted_config(config_dir) + assert persisted["tools"]["bash"]["permission"] == "always" + + def test_session_only_does_not_persist(self, config_dir: Path): + agent = build_test_agent_loop() + + agent.approve_always("bash", None, save_permanently=False) + + assert agent.config.tools["bash"]["permission"] == "always" + persisted = _read_persisted_config(config_dir) + assert "bash" not in persisted.get("tools", {}) + + +class TestApproveAlwaysPermanentWithGranularPermissions: + def _make_permissions(self) -> list[RequiredPermission]: + return [ + RequiredPermission( + scope=PermissionScope.COMMAND_PATTERN, + invocation_pattern="npm install foo", + session_pattern="npm install *", + label="npm install *", + ) + ] + + def test_persists_allowlist_to_config(self, config_dir: Path): + agent = build_test_agent_loop() + perms = self._make_permissions() + + agent.approve_always("bash", perms, save_permanently=True) + + persisted = _read_persisted_config(config_dir) + assert persisted["tools"]["bash"]["allowlist"] == ["npm install *"] + + def test_also_adds_session_rules(self, config_dir: Path): + agent = build_test_agent_loop() + perms = self._make_permissions() + + agent.approve_always("bash", perms, save_permanently=True) + + assert len(agent._session_rules) == 1 + rule = agent._session_rules[0] + assert rule.tool_name == "bash" + assert rule.scope == PermissionScope.COMMAND_PATTERN + assert rule.session_pattern == "npm install *" + + def test_session_only_does_not_persist_allowlist(self, config_dir: Path): + agent = build_test_agent_loop() + perms = self._make_permissions() + + agent.approve_always("bash", perms, save_permanently=False) + + assert len(agent._session_rules) == 1 + persisted = _read_persisted_config(config_dir) + assert "bash" not in persisted.get("tools", {}) + + def test_does_not_duplicate_existing_allowlist_entries(self, config_dir: Path): + config = build_test_vibe_config( + tools={"bash": {"allowlist": ["npm install *"]}} + ) + agent = build_test_agent_loop(config=config) + perms = self._make_permissions() + + agent.approve_always("bash", perms, save_permanently=True) + + persisted = _read_persisted_config(config_dir) + # Pattern already existed -- nothing new should be written + assert persisted.get("tools", {}).get("bash", {}).get("allowlist") is None + + def test_appends_new_patterns_to_existing_allowlist(self, config_dir: Path): + config = build_test_vibe_config(tools={"bash": {"allowlist": ["git *"]}}) + agent = build_test_agent_loop(config=config) + perms = self._make_permissions() + + agent.approve_always("bash", perms, save_permanently=True) + + persisted = _read_persisted_config(config_dir) + assert persisted["tools"]["bash"]["allowlist"] == ["git *", "npm install *"] + + def test_multiple_permissions_persisted(self, config_dir: Path): + agent = build_test_agent_loop() + perms = [ + RequiredPermission( + scope=PermissionScope.COMMAND_PATTERN, + invocation_pattern="npm install foo", + session_pattern="npm install *", + label="npm install *", + ), + RequiredPermission( + scope=PermissionScope.OUTSIDE_DIRECTORY, + invocation_pattern="/tmp/newdir", + session_pattern="/tmp/*", + label="/tmp/*", + ), + ] + + agent.approve_always("bash", perms, save_permanently=True) + + persisted = _read_persisted_config(config_dir) + assert persisted["tools"]["bash"]["allowlist"] == ["/tmp/*", "npm install *"] diff --git a/tests/test_cli_programmatic_preload.py b/tests/test_cli_programmatic_preload.py index 9777436..e0c186a 100644 --- a/tests/test_cli_programmatic_preload.py +++ b/tests/test_cli_programmatic_preload.py @@ -80,9 +80,7 @@ def test_run_programmatic_preload_streaming_is_batched( new_session = [ e for e in telemetry_events if e.get("event_name") == "vibe.new_session" ] - assert len(new_session) == 1 - assert new_session[0]["properties"]["entrypoint"] == "programmatic" - assert "version" in new_session[0]["properties"] + assert len(new_session) == 0 assert ( spy.emitted[0][1] == "You are Vibe, a super useful programming assistant." diff --git a/tests/tools/test_connectors.py b/tests/tools/test_connectors.py index fa7b6ee..7bf8ab5 100644 --- a/tests/tools/test_connectors.py +++ b/tests/tools/test_connectors.py @@ -1,11 +1,12 @@ from __future__ import annotations import os -from typing import cast +from typing import Any, cast from unittest.mock import AsyncMock, patch import httpx import pytest +import respx from tests.stubs.fake_connector_registry import FakeConnectorRegistry from tests.stubs.fake_mcp_registry import FakeMCPRegistry @@ -13,6 +14,7 @@ from vibe.core.config import ConnectorConfig, VibeConfig from vibe.core.tools.base import BaseToolConfig, ToolError from vibe.core.tools.connectors import CONNECTORS_ENV_VAR from vibe.core.tools.connectors.connector_registry import ( + ConnectorRegistry, RemoteTool, _connector_error_message, _normalize_name, @@ -497,3 +499,206 @@ class TestConnectorDisableFiltering: ) assert "connector_wiki_search" in tm.available_tools assert "connector_mail_send" not in tm.available_tools + + +# --------------------------------------------------------------------------- +# Bootstrap-based discovery (ConnectorRegistry._discover_all via httpx) +# --------------------------------------------------------------------------- + +_BOOTSTRAP_URL = "https://api.mistral.ai/v1/connectors/bootstrap" + + +def _make_bootstrap_response( + connectors: list[dict[str, Any]] | None = None, +) -> dict[str, Any]: + return {"connectors": connectors or [], "errors": None} + + +def _make_connector_payload( + *, + connector_id: str = "conn-1", + name: str = "wiki", + is_ready: bool = True, + tools: list[dict[str, Any]] | None = None, + bootstrap_errors: list[str] | None = None, +) -> dict[str, Any]: + return { + "id": connector_id, + "name": name, + "display_name": name, + "description": name, + "status": {"is_ready": is_ready}, + "tools": tools or [], + "bootstrap_errors": bootstrap_errors, + } + + +def _make_tool_payload( + name: str = "search", description: str = "Search docs" +) -> dict[str, Any]: + return { + "name": name, + "description": description, + "inputSchema": {"type": "object", "properties": {"query": {"type": "string"}}}, + } + + +class TestBootstrapDiscovery: + @respx.mock + @pytest.mark.asyncio + async def test_discovers_tools_from_bootstrap(self) -> None: + payload = _make_bootstrap_response([ + _make_connector_payload( + name="wiki", + tools=[_make_tool_payload("search"), _make_tool_payload("read")], + ) + ]) + respx.get(_BOOTSTRAP_URL).mock(return_value=httpx.Response(200, json=payload)) + + registry = ConnectorRegistry(api_key="test-key") + tools = await registry.get_tools_async() + + assert "connector_wiki_search" in tools + assert "connector_wiki_read" in tools + assert registry.connector_count == 1 + assert registry.is_connected("wiki") + + @respx.mock + @pytest.mark.asyncio + async def test_skips_not_ready_connectors(self) -> None: + payload = _make_bootstrap_response([ + _make_connector_payload( + name="broken", is_ready=False, bootstrap_errors=["auth failed"] + ), + _make_connector_payload(name="healthy", tools=[_make_tool_payload("ping")]), + ]) + respx.get(_BOOTSTRAP_URL).mock(return_value=httpx.Response(200, json=payload)) + + registry = ConnectorRegistry(api_key="test-key") + tools = await registry.get_tools_async() + + assert "connector_healthy_ping" in tools + assert not any("broken" in name for name in tools) + assert not registry.is_connected("broken") + assert registry.is_connected("healthy") + + @respx.mock + @pytest.mark.asyncio + async def test_handles_bootstrap_http_error(self) -> None: + respx.get(_BOOTSTRAP_URL).mock( + return_value=httpx.Response(500, text="Internal Server Error") + ) + + registry = ConnectorRegistry(api_key="test-key") + tools = await registry.get_tools_async() + + assert tools == {} + assert registry.connector_count == 0 + + @respx.mock + @pytest.mark.asyncio + async def test_deduplicates_connector_aliases(self) -> None: + payload = _make_bootstrap_response([ + _make_connector_payload( + connector_id="c-1", name="mcp", tools=[_make_tool_payload("a")] + ), + _make_connector_payload( + connector_id="c-2", name="mcp", tools=[_make_tool_payload("b")] + ), + ]) + respx.get(_BOOTSTRAP_URL).mock(return_value=httpx.Response(200, json=payload)) + + registry = ConnectorRegistry(api_key="test-key") + tools = await registry.get_tools_async() + + assert "connector_mcp_a" in tools + assert "connector_mcp_2_b" in tools + assert registry.connector_count == 2 + + @respx.mock + @pytest.mark.asyncio + async def test_skips_connector_without_id(self) -> None: + payload = _make_bootstrap_response([ + {"name": "broken", "status": {"is_ready": True}, "tools": []}, + _make_connector_payload(name="valid", tools=[_make_tool_payload("ping")]), + ]) + respx.get(_BOOTSTRAP_URL).mock(return_value=httpx.Response(200, json=payload)) + + registry = ConnectorRegistry(api_key="test-key") + tools = await registry.get_tools_async() + + assert "connector_valid_ping" in tools + assert registry.connector_count == 1 + + @respx.mock + @pytest.mark.asyncio + async def test_empty_connectors_list(self) -> None: + payload = _make_bootstrap_response([]) + respx.get(_BOOTSTRAP_URL).mock(return_value=httpx.Response(200, json=payload)) + + registry = ConnectorRegistry(api_key="test-key") + tools = await registry.get_tools_async() + + assert tools == {} + assert registry.connector_count == 0 + + @respx.mock + @pytest.mark.asyncio + async def test_uses_custom_server_url(self) -> None: + custom_url = "https://custom.api.example.com/v1/connectors/bootstrap" + payload = _make_bootstrap_response([ + _make_connector_payload(tools=[_make_tool_payload("ping")]) + ]) + respx.get(custom_url).mock(return_value=httpx.Response(200, json=payload)) + + registry = ConnectorRegistry( + api_key="test-key", server_url="https://custom.api.example.com" + ) + tools = await registry.get_tools_async() + + assert "connector_wiki_ping" in tools + + @respx.mock + @pytest.mark.asyncio + async def test_caches_after_first_call(self) -> None: + payload = _make_bootstrap_response([ + _make_connector_payload(tools=[_make_tool_payload("search")]) + ]) + route = respx.get(_BOOTSTRAP_URL).mock( + return_value=httpx.Response(200, json=payload) + ) + + registry = ConnectorRegistry(api_key="test-key") + await registry.get_tools_async() + await registry.get_tools_async() + + assert route.call_count == 1 + + @respx.mock + @pytest.mark.asyncio + async def test_refresh_connector_updates_cache(self) -> None: + payload = _make_bootstrap_response([ + _make_connector_payload( + connector_id="c-1", name="wiki", tools=[_make_tool_payload("search")] + ) + ]) + respx.get(_BOOTSTRAP_URL).mock(return_value=httpx.Response(200, json=payload)) + + registry = ConnectorRegistry(api_key="test-key") + await registry.get_tools_async() + + # Refresh returns updated tools from a second bootstrap call + refresh_payload = _make_bootstrap_response([ + _make_connector_payload( + connector_id="c-1", + name="wiki", + tools=[_make_tool_payload("search"), _make_tool_payload("write")], + ) + ]) + respx.get(_BOOTSTRAP_URL).mock( + return_value=httpx.Response(200, json=refresh_payload) + ) + + refreshed = await registry.refresh_connector_async("wiki") + assert "connector_wiki_search" in refreshed + assert "connector_wiki_write" in refreshed diff --git a/tests/tools/test_ui_bash_execution.py b/tests/tools/test_ui_bash_execution.py index f537cd9..3f53aa8 100644 --- a/tests/tools/test_ui_bash_execution.py +++ b/tests/tools/test_ui_bash_execution.py @@ -16,6 +16,18 @@ from vibe.core.types import Role async def _wait_for_bash_output_message( vibe_app: VibeApp, pilot, timeout: float = 1.0 +) -> BashOutputMessage: + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + if message := next(iter(vibe_app.query(BashOutputMessage)), None): + if not message._pending: + return message + await pilot.pause(0.05) + raise TimeoutError(f"BashOutputMessage did not appear within {timeout}s") + + +async def _wait_for_pending_bash_message( + vibe_app: VibeApp, pilot, timeout: float = 1.0 ) -> BashOutputMessage: deadline = time.monotonic() + timeout while time.monotonic() < deadline: @@ -157,3 +169,71 @@ async def test_ui_sends_manual_command_output_to_next_agent_turn() -> None: assert user_messages[-2].content == injected_message.content assert user_messages[-2].injected is True assert user_messages[-1].content == "what did the command print?" + + +@pytest.mark.asyncio +async def test_ui_shows_command_immediately_in_pending_state(vibe_app: VibeApp) -> None: + """The command line should appear before the process finishes.""" + async with vibe_app.run_test() as pilot: + chat_input = vibe_app.query_one(ChatInputContainer) + chat_input.value = "!sleep 10" + + await pilot.press("enter") + message = await _wait_for_pending_bash_message(vibe_app, pilot) + assert message._pending is True + # command line is rendered + cmd_widget = message.query_one(".bash-command", Static) + assert str(cmd_widget.render()) == "sleep 10" + # no output container yet + assert not list(message.query(".bash-output")) + + # clean up: cancel the background task + if vibe_app._bash_task and not vibe_app._bash_task.done(): + vibe_app._bash_task.cancel() + + +@pytest.mark.asyncio +async def test_ui_streams_output_incrementally(vibe_app: VibeApp) -> None: + """Output should appear as the command produces it, not all at once.""" + async with vibe_app.run_test() as pilot: + chat_input = vibe_app.query_one(ChatInputContainer) + # print lines with a small delay so streaming has a chance to show partial output + chat_input.value = "!bash -lc 'echo first; echo second'" + + await pilot.press("enter") + message = await _wait_for_bash_output_message(vibe_app, pilot) + output_widget = message.query_one(".bash-output", Static) + rendered = str(output_widget.render()) + assert "first" in rendered + assert "second" in rendered + + +@pytest.mark.asyncio +async def test_ui_cancels_running_command_on_new_submit(vibe_app: VibeApp) -> None: + """Submitting new input while a bang command is running should cancel it.""" + async with vibe_app.run_test() as pilot: + chat_input = vibe_app.query_one(ChatInputContainer) + chat_input.value = "!sleep 30" + + await pilot.press("enter") + await _wait_for_pending_bash_message(vibe_app, pilot) + assert vibe_app._bash_task is not None + assert not vibe_app._bash_task.done() + + # submit a new command which should cancel the first one + chat_input.value = "!echo done" + await pilot.press("enter") + + # wait until we have two messages and the second is finished + deadline = time.monotonic() + 2.0 + while time.monotonic() < deadline: + all_msgs = list(vibe_app.query(BashOutputMessage)) + if len(all_msgs) == 2 and not all_msgs[1]._pending: + break + await pilot.pause(0.05) + + all_msgs = list(vibe_app.query(BashOutputMessage)) + assert len(all_msgs) == 2 + second = all_msgs[1] + output_widget = second.query_one(".bash-output", Static) + assert str(output_widget.render()) == "done" diff --git a/uv.lock b/uv.lock index 7d75168..e884f38 100644 --- a/uv.lock +++ b/uv.lock @@ -3,7 +3,7 @@ revision = 3 requires-python = ">=3.12" [options] -exclude-newer = "2026-04-23T08:55:32.991304Z" +exclude-newer = "2026-04-28T08:28:58.401523Z" exclude-newer-span = "P7D" [options.exclude-newer-package] @@ -42,24 +42,24 @@ wheels = [ [[package]] name = "anyio" -version = "4.12.1" +version = "4.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, + { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, ] [[package]] name = "attrs" -version = "25.4.0" +version = "26.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, ] [[package]] @@ -77,20 +77,20 @@ wheels = [ [[package]] name = "cachetools" -version = "7.0.2" +version = "7.0.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/c7/342b33cc6877eebc6c9bb45cb9f78e170e575839699f6f3cc96050176431/cachetools-7.0.2.tar.gz", hash = "sha256:7e7f09a4ca8b791d8bb4864afc71e9c17e607a28e6839ca1a644253c97dbeae0", size = 36983, upload-time = "2026-03-02T19:45:16.926Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/7b/1755ed2c6bfabd1d98b37ae73152f8dcf94aa40fee119d163c19ed484704/cachetools-7.0.6.tar.gz", hash = "sha256:e5d524d36d65703a87243a26ff08ad84f73352adbeafb1cde81e207b456aaf24", size = 37526, upload-time = "2026-04-20T19:02:23.289Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/04/4b6968e77c110f12da96fdbfcb39c6557c2e5e81bd7afcf8ed893d5bc588/cachetools-7.0.2-py3-none-any.whl", hash = "sha256:938dcad184827c5e94928c4fd5526e2b46692b7fb1ae94472da9131d0299343c", size = 13793, upload-time = "2026-03-02T19:45:15.495Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c4/cf76242a5da1410917107ff14551764aa405a5fd10cd10cf9a5ca8fa77f4/cachetools-7.0.6-py3-none-any.whl", hash = "sha256:4e94956cfdd3086f12042cdd29318f5ced3893014f7d0d059bf3ead3f85b7f8b", size = 13976, upload-time = "2026-04-20T19:02:21.187Z" }, ] [[package]] name = "certifi" -version = "2026.2.25" +version = "2026.4.22" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, + { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, ] [[package]] @@ -161,71 +161,87 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.4.4" +version = "3.4.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, - { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, - { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, - { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, - { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, - { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, - { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, - { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, - { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, - { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, - { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, - { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, - { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, - { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, - { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, - { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, - { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, - { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, - { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, - { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, - { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, - { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, - { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, - { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, - { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, - { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, - { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, - { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, - { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, - { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, - { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, - { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, - { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, - { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, - { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, - { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, - { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, - { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, - { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, - { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, - { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, - { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, - { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, - { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, - { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, - { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, + { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, + { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, + { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, + { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, + { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, ] [[package]] name = "click" -version = "8.3.1" +version = "8.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, + { url = "https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502, upload-time = "2026-04-22T15:11:25.044Z" }, ] [[package]] @@ -239,58 +255,55 @@ wheels = [ [[package]] name = "cryptography" -version = "46.0.3" +version = "47.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/b2/7ffa7fe8207a8c42147ffe70c3e360b228160c1d85dc3faff16aaa3244c0/cryptography-47.0.0.tar.gz", hash = "sha256:9f8e55fe4e63613a5e1cc5819030f27b97742d720203a087802ce4ce9ceb52bb", size = 830863, upload-time = "2026-04-24T19:54:57.056Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" }, - { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" }, - { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" }, - { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" }, - { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" }, - { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" }, - { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" }, - { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" }, - { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" }, - { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" }, - { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" }, - { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" }, - { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" }, - { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" }, - { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" }, - { url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload-time = "2025-10-15T23:17:19.982Z" }, - { url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" }, - { url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" }, - { url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" }, - { url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload-time = "2025-10-15T23:17:26.449Z" }, - { url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload-time = "2025-10-15T23:17:28.06Z" }, - { url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload-time = "2025-10-15T23:17:29.665Z" }, - { url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload-time = "2025-10-15T23:17:31.686Z" }, - { url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload-time = "2025-10-15T23:17:33.478Z" }, - { url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" }, - { url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" }, - { url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" }, - { url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload-time = "2025-10-15T23:17:40.888Z" }, - { url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload-time = "2025-10-15T23:17:42.769Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload-time = "2025-10-15T23:17:44.468Z" }, - { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" }, - { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" }, - { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" }, - { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" }, - { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" }, - { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" }, - { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" }, - { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" }, - { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" }, - { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" }, - { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" }, - { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" }, - { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" }, + { url = "https://files.pythonhosted.org/packages/a4/98/40dfe932134bdcae4f6ab5927c87488754bf9eb79297d7e0070b78dd58e9/cryptography-47.0.0-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:160ad728f128972d362e714054f6ba0067cab7fb350c5202a9ae8ae4ce3ef1a0", size = 7912214, upload-time = "2026-04-24T19:53:03.864Z" }, + { url = "https://files.pythonhosted.org/packages/34/c6/2733531243fba725f58611b918056b277692f1033373dcc8bd01af1c05d4/cryptography-47.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b9a8943e359b7615db1a3ba587994618e094ff3d6fa5a390c73d079ce18b3973", size = 4644617, upload-time = "2026-04-24T19:53:06.909Z" }, + { url = "https://files.pythonhosted.org/packages/00/e3/b27be1a670a9b87f855d211cf0e1174a5d721216b7616bd52d8581d912ed/cryptography-47.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5c15764f261394b22aef6b00252f5195f46f2ca300bec57149474e2538b31f8", size = 4668186, upload-time = "2026-04-24T19:53:09.053Z" }, + { url = "https://files.pythonhosted.org/packages/81/b9/8443cfe5d17d482d348cee7048acf502bb89a51b6382f06240fd290d4ca3/cryptography-47.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9c59ab0e0fa3a180a5a9c59f3a5abe3ef90d474bc56d7fadfbe80359491b615b", size = 4651244, upload-time = "2026-04-24T19:53:11.217Z" }, + { url = "https://files.pythonhosted.org/packages/5d/5e/13ed0cdd0eb88ba159d6dd5ebfece8cb901dbcf1ae5ac4072e28b55d3153/cryptography-47.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:34b4358b925a5ea3e14384ca781a2c0ef7ac219b57bb9eacc4457078e2b19f92", size = 5252906, upload-time = "2026-04-24T19:53:13.532Z" }, + { url = "https://files.pythonhosted.org/packages/64/16/ed058e1df0f33d440217cd120d41d5dda9dd215a80b8187f68483185af82/cryptography-47.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0024b87d47ae2399165a6bfb20d24888881eeab83ae2566d62467c5ff0030ce7", size = 4701842, upload-time = "2026-04-24T19:53:15.618Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3d30986b30fdbd9e969abbdf8ba00ed0618615144341faeb57f395a084fe/cryptography-47.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:1e47422b5557bb82d3fff997e8d92cff4e28b9789576984f08c248d2b3535d93", size = 4289313, upload-time = "2026-04-24T19:53:17.755Z" }, + { url = "https://files.pythonhosted.org/packages/df/fd/32db38e3ad0cb331f0691cb4c7a8a6f176f679124dee746b3af6633db4d9/cryptography-47.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:6f29f36582e6151d9686235e586dd35bb67491f024767d10b842e520dc6a07ac", size = 4650964, upload-time = "2026-04-24T19:53:20.062Z" }, + { url = "https://files.pythonhosted.org/packages/86/53/5395d944dfd48cb1f67917f533c609c34347185ef15eb4308024c876f274/cryptography-47.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:a9b761f012a943b7de0e828843c5688d0de94a0578d44d6c85a1bae32f87791f", size = 5207817, upload-time = "2026-04-24T19:53:22.498Z" }, + { url = "https://files.pythonhosted.org/packages/34/4f/e5711b28e1901f7d480a2b1b688b645aa4c77c73f10731ed17e7f7db3f0d/cryptography-47.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4e1de79e047e25d6e9f8cea71c86b4a53aced64134f0f003bbcbf3655fd172c8", size = 4701544, upload-time = "2026-04-24T19:53:24.356Z" }, + { url = "https://files.pythonhosted.org/packages/22/22/c8ddc25de3010fc8da447648f5a092c40e7a8fadf01dd6d255d9c0b9373d/cryptography-47.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef6b3634087f18d2155b1e8ce264e5345a753da2c5fa9815e7d41315c90f8318", size = 4783536, upload-time = "2026-04-24T19:53:26.665Z" }, + { url = "https://files.pythonhosted.org/packages/66/b6/d4a68f4ea999c6d89e8498579cba1c5fcba4276284de7773b17e4fa69293/cryptography-47.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:11dbb9f50a0f1bb9757b3d8c27c1101780efb8f0bdecfb12439c22a74d64c001", size = 4926106, upload-time = "2026-04-24T19:53:28.686Z" }, + { url = "https://files.pythonhosted.org/packages/54/ed/5f524db1fade9c013aa618e1c99c6ed05e8ffc9ceee6cda22fed22dda3f4/cryptography-47.0.0-cp311-abi3-win32.whl", hash = "sha256:7fda2f02c9015db3f42bb8a22324a454516ed10a8c29ca6ece6cdbb5efe2a203", size = 3258581, upload-time = "2026-04-24T19:53:31.058Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dc/1b901990b174786569029f67542b3edf72ac068b6c3c8683c17e6a2f5363/cryptography-47.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:f5c3296dab66202f1b18a91fa266be93d6aa0c2806ea3d67762c69f60adc71aa", size = 3775309, upload-time = "2026-04-24T19:53:33.054Z" }, + { url = "https://files.pythonhosted.org/packages/14/88/7aa18ad9c11bc87689affa5ce4368d884b517502d75739d475fc6f4a03c7/cryptography-47.0.0-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:be12cb6a204f77ed968bcefe68086eb061695b540a3dd05edac507a3111b25f0", size = 7904299, upload-time = "2026-04-24T19:53:35.003Z" }, + { url = "https://files.pythonhosted.org/packages/07/55/c18f75724544872f234678fdedc871391722cb34a2aee19faa9f63100bb2/cryptography-47.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2ebd84adf0728c039a3be2700289378e1c164afc6748df1a5ed456767bef9ba7", size = 4631180, upload-time = "2026-04-24T19:53:37.517Z" }, + { url = "https://files.pythonhosted.org/packages/ee/65/31a5cc0eaca99cec5bafffe155d407115d96136bb161e8b49e0ef73f09a7/cryptography-47.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f68d6fbc7fbbcfb0939fea72c3b96a9f9a6edfc0e1b1d29778a2066030418b1", size = 4653529, upload-time = "2026-04-24T19:53:39.775Z" }, + { url = "https://files.pythonhosted.org/packages/e5/bc/641c0519a495f3bfd0421b48d7cd325c4336578523ccd76ea322b6c29c7a/cryptography-47.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:6651d32eff255423503aa276739da98c30f26c40cbeffcc6048e0d54ef704c0c", size = 4638570, upload-time = "2026-04-24T19:53:42.129Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f2/300327b0a47f6dc94dd8b71b57052aefe178bb51745073d73d80604f11ab/cryptography-47.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:3fb8fa48075fad7193f2e5496135c6a76ac4b2aa5a38433df0a539296b377829", size = 5238019, upload-time = "2026-04-24T19:53:44.577Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5a/5b5cf994391d4bf9d9c7efd4c66aabe4d95227256627f8fea6cff7dfadbd/cryptography-47.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:11438c7518132d95f354fa01a4aa2f806d172a061a7bed18cf18cbdacdb204d7", size = 4686832, upload-time = "2026-04-24T19:53:47.015Z" }, + { url = "https://files.pythonhosted.org/packages/dc/2c/ae950e28fd6475c852fc21a44db3e6b5bcc1261d1e370f2b6e42fa800fef/cryptography-47.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:8c1a736bbb3288005796c3f7ccb9453360d7fed483b13b9f468aea5171432923", size = 4269301, upload-time = "2026-04-24T19:53:48.97Z" }, + { url = "https://files.pythonhosted.org/packages/67/fb/6a39782e150ffe5cc1b0018cb6ddc48bf7ca62b498d7539ffc8a758e977d/cryptography-47.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:f1557695e5c2b86e204f6ce9470497848634100787935ab7adc5397c54abd7ab", size = 4638110, upload-time = "2026-04-24T19:53:51.011Z" }, + { url = "https://files.pythonhosted.org/packages/8e/d7/0b3c71090a76e5c203164a47688b697635ece006dcd2499ab3a4dbd3f0bd/cryptography-47.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:f9a034b642b960767fb343766ae5ba6ad653f2e890ddd82955aef288ffea8736", size = 5194988, upload-time = "2026-04-24T19:53:52.962Z" }, + { url = "https://files.pythonhosted.org/packages/63/33/63a961498a9df51721ab578c5a2622661411fc520e00bd83b0cc64eb20c4/cryptography-47.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:b1c76fca783aa7698eb21eb14f9c4aa09452248ee54a627d125025a43f83e7a7", size = 4686563, upload-time = "2026-04-24T19:53:55.274Z" }, + { url = "https://files.pythonhosted.org/packages/b7/bf/5ee5b145248f92250de86145d1c1d6edebbd57a7fe7caa4dedb5d4cf06a1/cryptography-47.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4f7722c97826770bab8ae92959a2e7b20a5e9e9bf4deae68fd86c3ca457bab52", size = 4770094, upload-time = "2026-04-24T19:53:57.753Z" }, + { url = "https://files.pythonhosted.org/packages/92/43/21d220b2da5d517773894dacdcdb5c682c28d3fffce65548cb06e87d5501/cryptography-47.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:09f6d7bf6724f8db8b32f11eccf23efc8e759924bc5603800335cf8859a3ddbd", size = 4913811, upload-time = "2026-04-24T19:54:00.236Z" }, + { url = "https://files.pythonhosted.org/packages/31/98/dc4ad376ac5f1a1a7d4a83f7b0c6f2bcad36b5d2d8f30aeb482d3a7d9582/cryptography-47.0.0-cp314-cp314t-win32.whl", hash = "sha256:6eebcaf0df1d21ce1f90605c9b432dd2c4f4ab665ac29a40d5e3fc68f51b5e63", size = 3237158, upload-time = "2026-04-24T19:54:02.606Z" }, + { url = "https://files.pythonhosted.org/packages/bc/da/97f62d18306b5133468bc3f8cc73a3111e8cdc8cf8d3e69474d6e5fd2d1b/cryptography-47.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:51c9313e90bd1690ec5a75ed047c27c0b8e6c570029712943d6116ef9a90620b", size = 3758706, upload-time = "2026-04-24T19:54:04.433Z" }, + { url = "https://files.pythonhosted.org/packages/e0/34/a4fae8ae7c3bc227460c9ae43f56abf1b911da0ec29e0ebac53bb0a4b6b7/cryptography-47.0.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:14432c8a9bcb37009784f9594a62fae211a2ae9543e96c92b2a8e4c3cd5cd0c4", size = 7904072, upload-time = "2026-04-24T19:54:06.411Z" }, + { url = "https://files.pythonhosted.org/packages/01/64/d7b1e54fdb69f22d24a64bb3e88dc718b31c7fb10ef0b9691a3cf7eeea6e/cryptography-47.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:07efe86201817e7d3c18781ca9770bc0db04e1e48c994be384e4602bc38f8f27", size = 4635767, upload-time = "2026-04-24T19:54:08.519Z" }, + { url = "https://files.pythonhosted.org/packages/8b/7b/cca826391fb2a94efdcdfe4631eb69306ee1cff0b22f664a412c90713877/cryptography-47.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b45761c6ec22b7c726d6a829558777e32d0f1c8be7c3f3480f9c912d5ee8a10", size = 4654350, upload-time = "2026-04-24T19:54:10.795Z" }, + { url = "https://files.pythonhosted.org/packages/4c/65/4b57bcc823f42a991627c51c2f68c9fd6eb1393c1756aac876cba2accae2/cryptography-47.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:edd4da498015da5b9f26d38d3bfc2e90257bfa9cbed1f6767c282a0025ae649b", size = 4643394, upload-time = "2026-04-24T19:54:13.275Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c4/2c5fbeea70adbbca2bbae865e1d605d6a4a7f8dbd9d33eaf69645087f06c/cryptography-47.0.0-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9af828c0d5a65c70ec729cd7495a4bf1a67ecb66417b8f02ff125ab8a6326a74", size = 5225777, upload-time = "2026-04-24T19:54:15.18Z" }, + { url = "https://files.pythonhosted.org/packages/7e/b8/ac57107ef32749d2b244e36069bb688792a363aaaa3acc9e3cf84c130315/cryptography-47.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:256d07c78a04d6b276f5df935a9923275f53bd1522f214447fdf365494e2d515", size = 4688771, upload-time = "2026-04-24T19:54:17.835Z" }, + { url = "https://files.pythonhosted.org/packages/56/fc/9f1de22ff8be99d991f240a46863c52d475404c408886c5a38d2b5c3bb26/cryptography-47.0.0-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:5d0e362ff51041b0c0d219cc7d6924d7b8996f57ce5712bdcef71eb3c65a59cc", size = 4270753, upload-time = "2026-04-24T19:54:19.963Z" }, + { url = "https://files.pythonhosted.org/packages/00/68/d70c852797aa68e8e48d12e5a87170c43f67bb4a59403627259dd57d15de/cryptography-47.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:1581aef4219f7ca2849d0250edaa3866212fb74bf5667284f46aa92f9e65c1ca", size = 4642911, upload-time = "2026-04-24T19:54:21.818Z" }, + { url = "https://files.pythonhosted.org/packages/a5/51/661cbee74f594c5d97ff82d34f10d5551c085ca4668645f4606ebd22bd5d/cryptography-47.0.0-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:a49a3eb5341b9503fa3000a9a0db033161db90d47285291f53c2a9d2cd1b7f76", size = 5181411, upload-time = "2026-04-24T19:54:24.376Z" }, + { url = "https://files.pythonhosted.org/packages/94/87/f2b6c374a82cf076cfa1416992ac8e8ec94d79facc37aec87c1a5cb72352/cryptography-47.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2207a498b03275d0051589e326b79d4cf59985c99031b05bb292ac52631c37fe", size = 4688262, upload-time = "2026-04-24T19:54:26.946Z" }, + { url = "https://files.pythonhosted.org/packages/14/e2/8b7462f4acf21ec509616f0245018bb197194ab0b65c2ea21a0bdd53c0eb/cryptography-47.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7a02675e2fabd0c0fc04c868b8781863cbf1967691543c22f5470500ff840b31", size = 4775506, upload-time = "2026-04-24T19:54:28.926Z" }, + { url = "https://files.pythonhosted.org/packages/70/75/158e494e4c08dc05e039da5bb48553826bd26c23930cf8d3cd5f21fa8921/cryptography-47.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80887c5cbd1774683cb126f0ab4184567f080071d5acf62205acb354b4b753b7", size = 4912060, upload-time = "2026-04-24T19:54:30.869Z" }, + { url = "https://files.pythonhosted.org/packages/06/bd/0a9d3edbf5eadbac926d7b9b3cd0c4be584eeeae4a003d24d9eda4affbbd/cryptography-47.0.0-cp38-abi3-win32.whl", hash = "sha256:ed67ea4e0cfb5faa5bc7ecb6e2b8838f3807a03758eec239d6c21c8769355310", size = 3248487, upload-time = "2026-04-24T19:54:33.494Z" }, + { url = "https://files.pythonhosted.org/packages/60/80/5681af756d0da3a599b7bdb586fac5a1540f1bcefd2717a20e611ddade45/cryptography-47.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:835d2d7f47cdc53b3224e90810fb1d36ca94ea29cc1801fb4c1bc43876735769", size = 3755737, upload-time = "2026-04-24T19:54:35.408Z" }, ] [[package]] @@ -352,11 +365,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.25.0" +version = "3.29.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/77/18/a1fd2231c679dcb9726204645721b12498aeac28e1ad0601038f94b42556/filelock-3.25.0.tar.gz", hash = "sha256:8f00faf3abf9dc730a1ffe9c354ae5c04e079ab7d3a683b7c32da5dd05f26af3", size = 40158, upload-time = "2026-03-01T15:08:45.916Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/fe/997687a931ab51049acce6fa1f23e8f01216374ea81374ddee763c493db5/filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90", size = 57571, upload-time = "2026-04-19T15:39:10.068Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/0b/de6f54d4a8bedfe8645c41497f3c18d749f0bd3218170c667bf4b81d0cdd/filelock-3.25.0-py3-none-any.whl", hash = "sha256:5ccf8069f7948f494968fc0713c10e5c182a9c9d9eef3a636307a20c2490f047", size = 26427, upload-time = "2026-03-01T15:08:44.593Z" }, + { url = "https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258", size = 39812, upload-time = "2026-04-19T15:39:08.752Z" }, ] [[package]] @@ -373,14 +386,14 @@ wheels = [ [[package]] name = "gitpython" -version = "3.1.46" +version = "3.1.47" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "gitdb" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/b5/59d16470a1f0dfe8c793f9ef56fd3826093fc52b3bd96d6b9d6c26c7e27b/gitpython-3.1.46.tar.gz", hash = "sha256:400124c7d0ef4ea03f7310ac2fbf7151e09ff97f2a3288d64a440c584a29c37f", size = 215371, upload-time = "2026-01-01T15:37:32.073Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/bd/50db468e9b1310529a19fce651b3b0e753b5c07954d486cba31bbee9a5d5/gitpython-3.1.47.tar.gz", hash = "sha256:dba27f922bd2b42cb54c87a8ab3cb6beb6bf07f3d564e21ac848913a05a8a3cd", size = 216978, upload-time = "2026-04-22T02:44:44.059Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl", hash = "sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058", size = 208620, upload-time = "2026-01-01T15:37:30.574Z" }, + { url = "https://files.pythonhosted.org/packages/f2/c5/a1bc0996af85757903cf2bf444a7824e68e0035ce63fb41d6f76f9def68b/gitpython-3.1.47-py3-none-any.whl", hash = "sha256:489f590edfd6d20571b2c0e72c6a6ac6915ee8b8cd04572330e3842207a78905", size = 209547, upload-time = "2026-04-22T02:44:41.271Z" }, ] [[package]] @@ -394,28 +407,27 @@ wheels = [ [[package]] name = "google-auth" -version = "2.48.0" +version = "2.49.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "pyasn1-modules" }, - { name = "rsa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0c/41/242044323fbd746615884b1c16639749e73665b718209946ebad7ba8a813/google_auth-2.48.0.tar.gz", hash = "sha256:4f7e706b0cd3208a3d940a19a822c37a476ddba5450156c3e6624a71f7c841ce", size = 326522, upload-time = "2026-01-26T19:22:47.157Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/fc/e925290a1ad95c975c459e2df070fac2b90954e13a0370ac505dff78cb99/google_auth-2.49.2.tar.gz", hash = "sha256:c1ae38500e73065dcae57355adb6278cf8b5c8e391994ae9cbadbcb9631ab409", size = 333958, upload-time = "2026-04-10T00:41:21.888Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/1d/d6466de3a5249d35e832a52834115ca9d1d0de6abc22065f049707516d47/google_auth-2.48.0-py3-none-any.whl", hash = "sha256:2e2a537873d449434252a9632c28bfc268b0adb1e53f9fb62afc5333a975903f", size = 236499, upload-time = "2026-01-26T19:22:45.099Z" }, + { url = "https://files.pythonhosted.org/packages/73/76/d241a5c927433420507215df6cac1b1fa4ac0ba7a794df42a84326c68da8/google_auth-2.49.2-py3-none-any.whl", hash = "sha256:c2720924dfc82dedb962c9f52cabb2ab16714fd0a6a707e40561d217574ed6d5", size = 240638, upload-time = "2026-04-10T00:41:14.501Z" }, ] [[package]] name = "googleapis-common-protos" -version = "1.73.0" +version = "1.74.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/99/96/a0205167fa0154f4a542fd6925bdc63d039d88dab3588b875078107e6f06/googleapis_common_protos-1.73.0.tar.gz", hash = "sha256:778d07cd4fbeff84c6f7c72102f0daf98fa2bfd3fa8bea426edc545588da0b5a", size = 147323, upload-time = "2026-03-06T21:53:09.727Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/18/a746c8344152d368a5aac738d4c857012f2c5d1fd2eac7e17b647a7861bd/googleapis_common_protos-1.74.0.tar.gz", hash = "sha256:57971e4eeeba6aad1163c1f0fc88543f965bb49129b8bb55b2b7b26ecab084f1", size = 151254, upload-time = "2026-04-02T21:23:26.679Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/28/23eea8acd65972bbfe295ce3666b28ac510dfcb115fac089d3edb0feb00a/googleapis_common_protos-1.73.0-py3-none-any.whl", hash = "sha256:dfdaaa2e860f242046be561e6d6cb5c5f1541ae02cfbcb034371aadb2942b4e8", size = 297578, upload-time = "2026-03-06T21:52:33.933Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b0/be5d3329badb9230b765de6eea66b73abd5944bdeb5afb3562ddcd80ae84/googleapis_common_protos-1.74.0-py3-none-any.whl", hash = "sha256:702216f78610bb510e3f12ac3cafd281b7ac45cc5d86e90ad87e4d301a3426b5", size = 300743, upload-time = "2026-04-02T21:22:49.108Z" }, ] [[package]] @@ -478,20 +490,20 @@ wheels = [ [[package]] name = "identify" -version = "2.6.17" +version = "2.6.19" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/84/376a3b96e5a8d33a7aa2c5b3b31a4b3c364117184bf0b17418055f6ace66/identify-2.6.17.tar.gz", hash = "sha256:f816b0b596b204c9fdf076ded172322f2723cf958d02f9c3587504834c8ff04d", size = 99579, upload-time = "2026-03-01T20:04:12.702Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842", size = 99567, upload-time = "2026-04-17T18:39:50.265Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/66/71c1227dff78aaeb942fed29dd5651f2aec166cc7c9aeea3e8b26a539b7d/identify-2.6.17-py2.py3-none-any.whl", hash = "sha256:be5f8412d5ed4b20f2bd41a65f920990bdccaa6a4a18a08f1eefdcd0bdd885f0", size = 99382, upload-time = "2026-03-01T20:04:11.439Z" }, + { url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" }, ] [[package]] name = "idna" -version = "3.11" +version = "3.13" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" }, ] [[package]] @@ -529,11 +541,11 @@ wheels = [ [[package]] name = "jaraco-context" -version = "6.1.0" +version = "6.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cb/9c/a788f5bb29c61e456b8ee52ce76dbdd32fd72cd73dd67bc95f42c7a8d13c/jaraco_context-6.1.0.tar.gz", hash = "sha256:129a341b0a85a7db7879e22acd66902fda67882db771754574338898b2d5d86f", size = 15850, upload-time = "2026-01-13T02:53:53.847Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/50/4763cd07e722bb6285316d390a164bc7e479db9d90daa769f22578f698b4/jaraco_context-6.1.2.tar.gz", hash = "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3", size = 16801, upload-time = "2026-03-20T22:13:33.922Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/48/aa685dbf1024c7bd82bede569e3a85f82c32fd3d79ba5fea578f0159571a/jaraco_context-6.1.0-py3-none-any.whl", hash = "sha256:a43b5ed85815223d0d3cfdb6d7ca0d2bc8946f28f30b6f3216bda070f68badda", size = 7065, upload-time = "2026-01-13T02:53:53.031Z" }, + { url = "https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl", hash = "sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535", size = 7871, upload-time = "2026-03-20T22:13:32.808Z" }, ] [[package]] @@ -762,7 +774,7 @@ wheels = [ [[package]] name = "mcp" -version = "1.26.0" +version = "1.27.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -780,9 +792,9 @@ dependencies = [ { name = "typing-inspection" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/6d/62e76bbb8144d6ed86e202b5edd8a4cb631e7c8130f3f4893c3f90262b10/mcp-1.26.0.tar.gz", hash = "sha256:db6e2ef491eecc1a0d93711a76f28dec2e05999f93afd48795da1c1137142c66", size = 608005, upload-time = "2026-01-24T19:40:32.468Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/eb/c0cfc62075dc6e1ec1c64d352ae09ac051d9334311ed226f1f425312848a/mcp-1.27.0.tar.gz", hash = "sha256:d3dc35a7eec0d458c1da4976a48f982097ddaab87e278c5511d5a4a56e852b83", size = 607509, upload-time = "2026-04-02T14:48:08.88Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/d9/eaa1f80170d2b7c5ba23f3b59f766f3a0bb41155fbc32a69adfa1adaaef9/mcp-1.26.0-py3-none-any.whl", hash = "sha256:904a21c33c25aa98ddbeb47273033c435e595bbacfdb177f4bd87f6dceebe1ca", size = 233615, upload-time = "2026-01-24T19:40:30.652Z" }, + { url = "https://files.pythonhosted.org/packages/9c/46/f6b4ad632c67ef35209a66127e4bddc95759649dd595f71f13fba11bdf9a/mcp-1.27.0-py3-none-any.whl", hash = "sha256:5ce1fa81614958e267b21fb2aa34e0aea8e2c6ede60d52aba45fd47246b4d741", size = 215967, upload-time = "2026-04-02T14:48:07.24Z" }, ] [[package]] @@ -808,7 +820,7 @@ wheels = [ [[package]] name = "mistral-vibe" -version = "2.9.3" +version = "2.9.4" source = { editable = "." } dependencies = [ { name = "agent-client-protocol" }, @@ -877,7 +889,7 @@ requires-dist = [ { name = "anyio", specifier = ">=4.12.0" }, { name = "cachetools", specifier = ">=5.5.0" }, { name = "charset-normalizer", specifier = ">=3.4.4" }, - { name = "cryptography", specifier = ">=44.0.0,<=46.0.3" }, + { name = "cryptography", specifier = ">=44.0.0" }, { name = "gitpython", specifier = ">=3.1.46" }, { name = "giturlparse", specifier = ">=0.14.0" }, { name = "google-auth", specifier = ">=2.0.0" }, @@ -886,7 +898,7 @@ requires-dist = [ { name = "keyring", specifier = ">=25.6.0" }, { name = "markdownify", specifier = ">=1.2.2" }, { name = "mcp", specifier = ">=1.14.0" }, - { name = "mistralai", specifier = "==2.3.2" }, + { name = "mistralai", specifier = "==2.4.4" }, { name = "opentelemetry-api", specifier = ">=1.39.1" }, { name = "opentelemetry-exporter-otlp-proto-http", specifier = ">=1.39.1" }, { name = "opentelemetry-sdk", specifier = ">=1.39.1" }, @@ -935,7 +947,7 @@ dev = [ [[package]] name = "mistralai" -version = "2.3.2" +version = "2.4.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "eval-type-backport" }, @@ -947,52 +959,52 @@ dependencies = [ { name = "python-dateutil" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5c/08/f8fed64eab35ad1ded82f92bf834160302db19f5bbda5a904df7d4dbf3c4/mistralai-2.3.2.tar.gz", hash = "sha256:a02c7e90ac165e8680c849551ff5fe9788e9fc10b7dbe71817443dc63cc5e9c9", size = 394665, upload-time = "2026-04-10T14:05:36.31Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/f0/80dfabf224be4419c6c112f3950676f5af3dcde582d225c03ca0196a4b32/mistralai-2.4.4.tar.gz", hash = "sha256:cd8a27a230e5458b62237a6c4f7b52f5be86909fbc18694360ceb21dac932eda", size = 420936, upload-time = "2026-04-30T12:26:38.775Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/e0/9f246d18f8fe6e815f7cae88c437c986b342d3ff568607c8e0d53f4710d0/mistralai-2.3.2-py3-none-any.whl", hash = "sha256:dec05f446502289b76add9796d75465c070fc539b3c01d6f7d3297833bb64981", size = 935914, upload-time = "2026-04-10T14:05:37.845Z" }, + { url = "https://files.pythonhosted.org/packages/0f/74/0f6188190e79eef4363754c71414c1a791537b1e506cbee615bb581cb05f/mistralai-2.4.4-py3-none-any.whl", hash = "sha256:43dd3b1f0f4f960a723359165c4da0d035590b27c050028322934fe4f189f14e", size = 990545, upload-time = "2026-04-30T12:26:40.702Z" }, ] [[package]] name = "more-itertools" -version = "10.8.0" +version = "11.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431, upload-time = "2025-09-02T15:23:11.018Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/f7/139d22fef48ac78127d18e01d80cf1be40236ae489769d17f35c3d425293/more_itertools-11.0.2.tar.gz", hash = "sha256:392a9e1e362cbc106a2457d37cabf9b36e5e12efd4ebff1654630e76597df804", size = 144659, upload-time = "2026-04-09T15:01:33.297Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, + { url = "https://files.pythonhosted.org/packages/cb/98/6af411189d9413534c3eb691182bff1f5c6d44ed2f93f2edfe52a1bbceb8/more_itertools-11.0.2-py3-none-any.whl", hash = "sha256:6e35b35f818b01f691643c6c611bc0902f2e92b46c18fffa77ae1e7c46e912e4", size = 71939, upload-time = "2026-04-09T15:01:32.21Z" }, ] [[package]] name = "nh3" -version = "0.3.3" +version = "0.3.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cc/37/ab55eb2b05e334ff9a1ad52c556ace1f9c20a3f63613a165d384d5387657/nh3-0.3.3.tar.gz", hash = "sha256:185ed41b88c910b9ca8edc89ca3b4be688a12cb9de129d84befa2f74a0039fee", size = 18968, upload-time = "2026-02-14T09:35:15.664Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/5f/1d19bdc7d27238e37f3672cdc02cb77c56a4a86d140cd4f4f23c90df6e16/nh3-0.3.5.tar.gz", hash = "sha256:45855e14ff056064fec77133bfcf7cd691838168e5e17bbef075394954dc9dc8", size = 20743, upload-time = "2026-04-25T10:44:16.066Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/a4/834f0ebd80844ce67e1bdb011d6f844f61cdb4c1d7cdc56a982bc054cc00/nh3-0.3.3-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:21b058cd20d9f0919421a820a2843fdb5e1749c0bf57a6247ab8f4ba6723c9fc", size = 1428680, upload-time = "2026-02-14T09:34:33.015Z" }, - { url = "https://files.pythonhosted.org/packages/7f/1a/a7d72e750f74c6b71befbeebc4489579fe783466889d41f32e34acde0b6b/nh3-0.3.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4400a73c2a62859e769f9d36d1b5a7a5c65c4179d1dddd2f6f3095b2db0cbfc", size = 799003, upload-time = "2026-02-14T09:34:35.108Z" }, - { url = "https://files.pythonhosted.org/packages/58/d5/089eb6d65da139dc2223b83b2627e00872eccb5e1afdf5b1d76eb6ad3fcc/nh3-0.3.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1ef87f8e916321a88b45f2d597f29bd56e560ed4568a50f0f1305afab86b7189", size = 846818, upload-time = "2026-02-14T09:34:37Z" }, - { url = "https://files.pythonhosted.org/packages/9b/c6/44a0b65fc7b213a3a725f041ef986534b100e58cd1a2e00f0fd3c9603893/nh3-0.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a446eae598987f49ee97ac2f18eafcce4e62e7574bd1eb23782e4702e54e217d", size = 1012537, upload-time = "2026-02-14T09:34:38.515Z" }, - { url = "https://files.pythonhosted.org/packages/94/3a/91bcfcc0a61b286b8b25d39e288b9c0ba91c3290d402867d1cd705169844/nh3-0.3.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:0d5eb734a78ac364af1797fef718340a373f626a9ff6b4fb0b4badf7927e7b81", size = 1095435, upload-time = "2026-02-14T09:34:40.022Z" }, - { url = "https://files.pythonhosted.org/packages/fd/fd/4617a19d80cf9f958e65724ff5e97bc2f76f2f4c5194c740016606c87bd1/nh3-0.3.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:92a958e6f6d0100e025a5686aafd67e3c98eac67495728f8bb64fbeb3e474493", size = 1056344, upload-time = "2026-02-14T09:34:41.469Z" }, - { url = "https://files.pythonhosted.org/packages/bd/7d/5bcbbc56e71b7dda7ef1d6008098da9c5426d6334137ef32bb2b9c496984/nh3-0.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9ed40cf8449a59a03aa465114fedce1ff7ac52561688811d047917cc878b19ca", size = 1034533, upload-time = "2026-02-14T09:34:43.313Z" }, - { url = "https://files.pythonhosted.org/packages/3f/9c/054eff8a59a8b23b37f0f4ac84cdd688ee84cf5251664c0e14e5d30a8a67/nh3-0.3.3-cp314-cp314t-win32.whl", hash = "sha256:b50c3770299fb2a7c1113751501e8878d525d15160a4c05194d7fe62b758aad8", size = 608305, upload-time = "2026-02-14T09:34:44.622Z" }, - { url = "https://files.pythonhosted.org/packages/d7/b0/64667b8d522c7b859717a02b1a66ba03b529ca1df623964e598af8db1ed5/nh3-0.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:21a63ccb18ddad3f784bb775955839b8b80e347e597726f01e43ca1abcc5c808", size = 620633, upload-time = "2026-02-14T09:34:46.069Z" }, - { url = "https://files.pythonhosted.org/packages/91/b5/ae9909e4ddfd86ee076c4d6d62ba69e9b31061da9d2f722936c52df8d556/nh3-0.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f508ddd4e2433fdcb78c790fc2d24e3a349ba775e5fa904af89891321d4844a3", size = 607027, upload-time = "2026-02-14T09:34:47.91Z" }, - { url = "https://files.pythonhosted.org/packages/13/3e/aef8cf8e0419b530c95e96ae93a5078e9b36c1e6613eeb1df03a80d5194e/nh3-0.3.3-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e8ee96156f7dfc6e30ecda650e480c5ae0a7d38f0c6fafc3c1c655e2500421d9", size = 1448640, upload-time = "2026-02-14T09:34:49.316Z" }, - { url = "https://files.pythonhosted.org/packages/ca/43/d2011a4f6c0272cb122eeff40062ee06bb2b6e57eabc3a5e057df0d582df/nh3-0.3.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45fe0d6a607264910daec30360c8a3b5b1500fd832d21b2da608256287bcb92d", size = 839405, upload-time = "2026-02-14T09:34:50.779Z" }, - { url = "https://files.pythonhosted.org/packages/f8/f3/965048510c1caf2a34ed04411a46a04a06eb05563cd06f1aa57b71eb2bc8/nh3-0.3.3-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5bc1d4b30ba1ba896669d944b6003630592665974bd11a3dc2f661bde92798a7", size = 825849, upload-time = "2026-02-14T09:34:52.622Z" }, - { url = "https://files.pythonhosted.org/packages/78/99/b4bbc6ad16329d8db2c2c320423f00b549ca3b129c2b2f9136be2606dbb0/nh3-0.3.3-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f433a2dd66545aad4a720ad1b2150edcdca75bfff6f4e6f378ade1ec138d5e77", size = 1068303, upload-time = "2026-02-14T09:34:54.179Z" }, - { url = "https://files.pythonhosted.org/packages/3f/34/3420d97065aab1b35f3e93ce9c96c8ebd423ce86fe84dee3126790421a2a/nh3-0.3.3-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52e973cb742e95b9ae1b35822ce23992428750f4b46b619fe86eba4205255b30", size = 1029316, upload-time = "2026-02-14T09:34:56.186Z" }, - { url = "https://files.pythonhosted.org/packages/f1/9a/99eda757b14e596fdb2ca5f599a849d9554181aa899274d0d183faef4493/nh3-0.3.3-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c730617bdc15d7092dcc0469dc2826b914c8f874996d105b4bc3842a41c1cd9", size = 919944, upload-time = "2026-02-14T09:34:57.886Z" }, - { url = "https://files.pythonhosted.org/packages/6f/84/c0dc75c7fb596135f999e59a410d9f45bdabb989f1cb911f0016d22b747b/nh3-0.3.3-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e98fa3dbfd54e25487e36ba500bc29bca3a4cab4ffba18cfb1a35a2d02624297", size = 811461, upload-time = "2026-02-14T09:34:59.65Z" }, - { url = "https://files.pythonhosted.org/packages/7e/ec/b1bf57cab6230eec910e4863528dc51dcf21b57aaf7c88ee9190d62c9185/nh3-0.3.3-cp38-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:3a62b8ae7c235481715055222e54c682422d0495a5c73326807d4e44c5d14691", size = 840360, upload-time = "2026-02-14T09:35:01.444Z" }, - { url = "https://files.pythonhosted.org/packages/37/5e/326ae34e904dde09af1de51219a611ae914111f0970f2f111f4f0188f57e/nh3-0.3.3-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc305a2264868ec8fa16548296f803d8fd9c1fa66cd28b88b605b1bd06667c0b", size = 859872, upload-time = "2026-02-14T09:35:03.348Z" }, - { url = "https://files.pythonhosted.org/packages/09/38/7eba529ce17ab4d3790205da37deabb4cb6edcba15f27b8562e467f2fc97/nh3-0.3.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:90126a834c18af03bfd6ff9a027bfa6bbf0e238527bc780a24de6bd7cc1041e2", size = 1023550, upload-time = "2026-02-14T09:35:04.829Z" }, - { url = "https://files.pythonhosted.org/packages/05/a2/556fdecd37c3681b1edee2cf795a6799c6ed0a5551b2822636960d7e7651/nh3-0.3.3-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:24769a428e9e971e4ccfb24628f83aaa7dc3c8b41b130c8ddc1835fa1c924489", size = 1105212, upload-time = "2026-02-14T09:35:06.821Z" }, - { url = "https://files.pythonhosted.org/packages/dd/e3/5db0b0ad663234967d83702277094687baf7c498831a2d3ad3451c11770f/nh3-0.3.3-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:b7a18ee057761e455d58b9d31445c3e4b2594cff4ddb84d2e331c011ef46f462", size = 1069970, upload-time = "2026-02-14T09:35:08.504Z" }, - { url = "https://files.pythonhosted.org/packages/79/b2/2ea21b79c6e869581ce5f51549b6e185c4762233591455bf2a326fb07f3b/nh3-0.3.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5a4b2c1f3e6f3cbe7048e17f4fefad3f8d3e14cc0fd08fb8599e0d5653f6b181", size = 1047588, upload-time = "2026-02-14T09:35:09.911Z" }, - { url = "https://files.pythonhosted.org/packages/e2/92/2e434619e658c806d9c096eed2cdff9a883084299b7b19a3f0824eb8e63d/nh3-0.3.3-cp38-abi3-win32.whl", hash = "sha256:e974850b131fdffa75e7ad8e0d9c7a855b96227b093417fdf1bd61656e530f37", size = 616179, upload-time = "2026-02-14T09:35:11.366Z" }, - { url = "https://files.pythonhosted.org/packages/73/88/1ce287ef8649dc51365b5094bd3713b76454838140a32ab4f8349973883c/nh3-0.3.3-cp38-abi3-win_amd64.whl", hash = "sha256:2efd17c0355d04d39e6d79122b42662277ac10a17ea48831d90b46e5ef7e4fc0", size = 631159, upload-time = "2026-02-14T09:35:12.77Z" }, - { url = "https://files.pythonhosted.org/packages/31/f1/b4835dbde4fb06f29db89db027576d6014081cd278d9b6751facc3e69e43/nh3-0.3.3-cp38-abi3-win_arm64.whl", hash = "sha256:b838e619f483531483d26d889438e53a880510e832d2aafe73f93b7b1ac2bce2", size = 616645, upload-time = "2026-02-14T09:35:14.062Z" }, + { url = "https://files.pythonhosted.org/packages/63/b0/8587ac42a9627ab88e7e221601f1dfccbf4db80b2a29222ea63266dc9abc/nh3-0.3.5-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:23a312224875f72cd16bde417f49071451877e29ef646a60e50fcb69407cc18a", size = 1420126, upload-time = "2026-04-25T10:43:39.834Z" }, + { url = "https://files.pythonhosted.org/packages/c0/1b/1dbc4d0c43f12e8c1784ede17eaee6f061d4fbe5505757c65c49b2ceab95/nh3-0.3.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:387abd011e81959d5a35151a11350a0795c6edeb53ebfa02d2e882dc01299263", size = 793943, upload-time = "2026-04-25T10:43:41.363Z" }, + { url = "https://files.pythonhosted.org/packages/47/9f/d6758d7a14ee964bf439cc35ae4fa24a763a93399c8ef6f22bd11d532d29/nh3-0.3.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:48f45e3e914be93a596431aa143dedf1582557bf41a58153c296048d6e3798c9", size = 841150, upload-time = "2026-04-25T10:43:43.007Z" }, + { url = "https://files.pythonhosted.org/packages/b6/36/d5d1ae8374612c98f390e1ea7c610fa6c9716259a03bbf4d15b269f40073/nh3-0.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0a09f51806fd51b4fedbf9ea2b61fef388f19aef0d62fe51199d41648be14588", size = 1008415, upload-time = "2026-04-25T10:43:44.324Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8f/d13a9c3fd2d9c131a2a281737380e9379eb0f8c33fea24c2b923aaafbb15/nh3-0.3.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:c357f1d042c67f135a5e6babb2b0e3b9d9224ff4a3543240f597767b01384ffd", size = 1092706, upload-time = "2026-04-25T10:43:45.653Z" }, + { url = "https://files.pythonhosted.org/packages/bb/57/2f3add7f8680fcc896afa6a675cb2bab09982853ee8af40bad621f6b61c4/nh3-0.3.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:38748140bf76383ab7ce2dce0ad4cb663855d8fbc9098f7f3483673d09616a17", size = 1048346, upload-time = "2026-04-25T10:43:46.974Z" }, + { url = "https://files.pythonhosted.org/packages/c1/c3/2f9e4ffa82863074d1361bfe949bc46393d91b3411579dfbbd090b24cac5/nh3-0.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:84bdeb082544fbcb77a12c034dd77d7da0556fdc0727b787eb6214b958c15e29", size = 1029038, upload-time = "2026-04-25T10:43:48.569Z" }, + { url = "https://files.pythonhosted.org/packages/e8/10/2804deb3f3315184c9cae41702e293c87524b5a21f766b07d7fe3ffbcfbb/nh3-0.3.5-cp314-cp314t-win32.whl", hash = "sha256:c3aae321f67ae66cff2a627115f106a377d4475d10b0e13d97959a13486b9a88", size = 603263, upload-time = "2026-04-25T10:43:49.851Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a2/f6685248b49f7548fc9a8c335ab3a52f68610b72e8a61576447151e4e2e6/nh3-0.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c88605d8d468f7fc1b31e06129bc91d6c96f6c621776c9b504a0da9beac9df5f", size = 616866, upload-time = "2026-04-25T10:43:51.005Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b6/d8c9018635d4acfefde6b68470daa510eed715a350cbaa2f928ba0609f81/nh3-0.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:72c5bdedec27fa33de6a5326346ea8aa3fe54f6ac294d54c4b204fb66a9f1e79", size = 602566, upload-time = "2026-04-25T10:43:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/85/30/d162e99746a2fb1d98bb0ef23af3e201b156cf09f7de867c7390c8fe1c06/nh3-0.3.5-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:3bb854485c9b33e5bb143ff3e49e577073bc6bc320f0ff8fc316dd89c0d3c101", size = 1442393, upload-time = "2026-04-25T10:43:53.556Z" }, + { url = "https://files.pythonhosted.org/packages/25/8c/072120d506978ab053e1732d0efa7c86cb478fee0ee098fda0ac0d31cb34/nh3-0.3.5-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50d401ab2d8e86d59e2126e3ab2a2f45840c405842b626d9a51624b3a33b6878", size = 837722, upload-time = "2026-04-25T10:43:55.073Z" }, + { url = "https://files.pythonhosted.org/packages/52/86/d4e06e28c5ad1c4b065f89737d02631bd49f1660b6ebcf17a87ffcd201da/nh3-0.3.5-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:acfd354e61accbe4c74f8017c6e397a776916dfe47c48643cf7fd84ade826f93", size = 822872, upload-time = "2026-04-25T10:43:56.581Z" }, + { url = "https://files.pythonhosted.org/packages/0a/62/50659255213f241ec5797ae7427464c969397373e83b3659372b341ae869/nh3-0.3.5-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:52d877980d7ca01dc3baf3936bf844828bc6f332962227a684ed79c18cce14c3", size = 1100031, upload-time = "2026-04-25T10:43:58.098Z" }, + { url = "https://files.pythonhosted.org/packages/00/7a/a12ae77593b2fcf3be25df7bc1c01967d0de448bdb4b6c7ec80fe4f5a74f/nh3-0.3.5-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:207c01801d3e9bb8ec08f08689346bdd30ce15b8bf60013a925d08b5388962a4", size = 1057669, upload-time = "2026-04-25T10:43:59.328Z" }, + { url = "https://files.pythonhosted.org/packages/2d/71/5647dc04c0233192a3956fc91708822b21403a06508cacf78083c68e7bf0/nh3-0.3.5-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea232933394d1d58bf7c4bb348dc4660eae6604e1ae81cd2ba6d9ed80d390f3b", size = 914795, upload-time = "2026-04-25T10:44:00.52Z" }, + { url = "https://files.pythonhosted.org/packages/1b/0e/bf298920729f216adcb002acf7ea01b90842603d2e4e2ce9b900d9ee8fab/nh3-0.3.5-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe3a787dc76b50de6bee54ef242f26c41dfe47654428e3e94f0fae5bb6dd2cc1", size = 806976, upload-time = "2026-04-25T10:44:01.743Z" }, + { url = "https://files.pythonhosted.org/packages/85/01/26761e1dc2b848e65a62c19e5d39ad446283287cd4afddc89f364ab86bc9/nh3-0.3.5-cp38-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:488928988caad25ba14b1eb5bc74e25e21f3b5e40341d956f3ce4a8bc19460dc", size = 834904, upload-time = "2026-04-25T10:44:03.454Z" }, + { url = "https://files.pythonhosted.org/packages/33/53/0766113e679540ac1edc1b82b1295aecd321eeb75d6fead70109a838b6ee/nh3-0.3.5-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2c069570b06aa848457713ad7af4a9905691291548c4466a9ad78ee95808382b", size = 857159, upload-time = "2026-04-25T10:44:05.003Z" }, + { url = "https://files.pythonhosted.org/packages/58/36/734d353dfaf292fed574b8b3092f0ef79dc6404f3879f7faaa61a4701fad/nh3-0.3.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:eeedc90ed8c42c327e8e10e621ccfa314fc6cce35d5929f4297ff1cdb89667c4", size = 1018600, upload-time = "2026-04-25T10:44:06.18Z" }, + { url = "https://files.pythonhosted.org/packages/6b/aa/d9c59c1b49669fcb7bababa55df82385f029ad5c2651f583c3a1141cfdd1/nh3-0.3.5-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:de8e8621853b6470fe928c684ee0d3f39ea8086cebafe4c416486488dea7b68d", size = 1103530, upload-time = "2026-04-25T10:44:07.68Z" }, + { url = "https://files.pythonhosted.org/packages/90/b0/cdd210bfb8d9d43fb02fc3c868336b9955934d8e15e66eb1d15a147b8af0/nh3-0.3.5-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:6ea58cc44d274c643b83547ca9654a0b1a817609b160601356f76a2b744c49ad", size = 1061754, upload-time = "2026-04-25T10:44:09.362Z" }, + { url = "https://files.pythonhosted.org/packages/ce/cb/7a39e72e668c8445bdd95e494b3e21cfdddc68329be8ea3522c8befb46c4/nh3-0.3.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e49c9b564e6bcb03ecd2f057213df9a0de15a95812ac9db9600b590db23d3ae9", size = 1040938, upload-time = "2026-04-25T10:44:10.775Z" }, + { url = "https://files.pythonhosted.org/packages/af/4c/fc2f9ed208a3801a319f59b5fea03cdc20cf3bd8af14be930d3a8de01224/nh3-0.3.5-cp38-abi3-win32.whl", hash = "sha256:559e4c73b689e9a7aa97ac9760b1bc488038d7c1a575aa4ab5a0e19ee9630c0f", size = 611445, upload-time = "2026-04-25T10:44:12.317Z" }, + { url = "https://files.pythonhosted.org/packages/db/1a/e4c9b5e2ae13e6092c9ec16d8ca30646cb01fcdea245f36c5b08fd21fbd5/nh3-0.3.5-cp38-abi3-win_amd64.whl", hash = "sha256:45e6a65dc88a300a2e3502cb9c8e6d1d6b831d6fba7470643333609c6aab1f30", size = 626502, upload-time = "2026-04-25T10:44:13.682Z" }, + { url = "https://files.pythonhosted.org/packages/80/7c/19cd0671d1ba2762fb388fc149697d20d0568ccfeef833b11280a619e526/nh3-0.3.5-cp38-abi3-win_arm64.whl", hash = "sha256:8f85285700a18e9f3fc5bff41fe573fa84f81542ef13b48a89f9fecca0474d3b", size = 611069, upload-time = "2026-04-25T10:44:14.934Z" }, ] [[package]] @@ -1088,11 +1100,11 @@ wheels = [ [[package]] name = "packaging" -version = "26.0" +version = "26.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, ] [[package]] @@ -1118,11 +1130,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.9.2" +version = "4.9.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" }, + { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, ] [[package]] @@ -1136,7 +1148,7 @@ wheels = [ [[package]] name = "pre-commit" -version = "4.5.1" +version = "4.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cfgv" }, @@ -1145,24 +1157,24 @@ dependencies = [ { name = "pyyaml" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/22/2de9408ac81acbb8a7d05d4cc064a152ccf33b3d480ebe0cd292153db239/pre_commit-4.6.0.tar.gz", hash = "sha256:718d2208cef53fdc38206e40524a6d4d9576d103eb16f0fec11c875e7716e9d9", size = 198525, upload-time = "2026-04-21T20:31:41.613Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, + { url = "https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl", hash = "sha256:e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b", size = 226472, upload-time = "2026-04-21T20:31:40.092Z" }, ] [[package]] name = "protobuf" -version = "6.33.5" +version = "6.33.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531, upload-time = "2026-03-18T19:05:00.988Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, - { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, - { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, - { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, - { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, - { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, - { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, + { url = "https://files.pythonhosted.org/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, + { url = "https://files.pythonhosted.org/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089, upload-time = "2026-03-18T19:04:50.381Z" }, + { url = "https://files.pythonhosted.org/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737, upload-time = "2026-03-18T19:04:51.866Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610, upload-time = "2026-03-18T19:04:53.096Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381, upload-time = "2026-03-18T19:04:54.616Z" }, + { url = "https://files.pythonhosted.org/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436, upload-time = "2026-03-18T19:04:55.768Z" }, + { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, ] [[package]] @@ -1176,11 +1188,11 @@ wheels = [ [[package]] name = "pyasn1" -version = "0.6.2" +version = "0.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/b6/6e630dff89739fcd427e3f72b3d905ce0acb85a45d4ec3e2678718a3487f/pyasn1-0.6.2.tar.gz", hash = "sha256:9b59a2b25ba7e4f8197db7686c09fb33e658b98339fadb826e9512629017833b", size = 146586, upload-time = "2026-01-16T18:04:18.534Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/b5/a96872e5184f354da9c84ae119971a0a4c221fe9b27a4d94bd43f2596727/pyasn1-0.6.2-py3-none-any.whl", hash = "sha256:1eb26d860996a18e9b6ed05e7aae0e9fc21619fcee6af91cca9bad4fbea224bf", size = 83371, upload-time = "2026-01-16T18:04:17.174Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" }, ] [[package]] @@ -1206,7 +1218,7 @@ wheels = [ [[package]] name = "pydantic" -version = "2.12.5" +version = "2.13.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -1214,108 +1226,112 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/e4/40d09941a2cebcb20609b86a559817d5b9291c49dd6f8c87e5feffbe703a/pydantic-2.13.3.tar.gz", hash = "sha256:af09e9d1d09f4e7fe37145c1f577e1d61ceb9a41924bf0094a36506285d0a84d", size = 844068, upload-time = "2026-04-20T14:46:43.632Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, + { url = "https://files.pythonhosted.org/packages/f3/0a/fd7d723f8f8153418fb40cf9c940e82004fce7e987026b08a68a36dd3fe7/pydantic-2.13.3-py3-none-any.whl", hash = "sha256:6db14ac8dfc9a1e57f87ea2c0de670c251240f43cb0c30a5130e9720dc612927", size = 471981, upload-time = "2026-04-20T14:46:41.402Z" }, ] [[package]] name = "pydantic-core" -version = "2.41.5" +version = "2.46.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/ef/f7abb56c49382a246fd2ce9c799691e3c3e7175ec74b14d99e798bcddb1a/pydantic_core-2.46.3.tar.gz", hash = "sha256:41c178f65b8c29807239d47e6050262eb6bf84eb695e41101e62e38df4a5bc2c", size = 471412, upload-time = "2026-04-20T14:40:56.672Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, - { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, - { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, - { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, - { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, - { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, - { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, - { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, - { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, - { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, - { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, - { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, - { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, - { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, - { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, - { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, - { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, - { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, - { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, - { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, - { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, - { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, - { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, - { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, - { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, - { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, - { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, - { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, - { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, - { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, - { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, - { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, - { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, - { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, - { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, - { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, - { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, - { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, - { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, - { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, - { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, - { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, - { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, - { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, - { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, - { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, - { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, - { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, - { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, - { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, - { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, - { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, - { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, + { url = "https://files.pythonhosted.org/packages/4b/cb/5b47425556ecc1f3fe18ed2a0083188aa46e1dd812b06e406475b3a5d536/pydantic_core-2.46.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b11b59b3eee90a80a36701ddb4576d9ae31f93f05cb9e277ceaa09e6bf074a67", size = 2101946, upload-time = "2026-04-20T14:40:52.581Z" }, + { url = "https://files.pythonhosted.org/packages/a1/4f/2fb62c2267cae99b815bbf4a7b9283812c88ca3153ef29f7707200f1d4e5/pydantic_core-2.46.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:af8653713055ea18a3abc1537fe2ebc42f5b0bbb768d1eb79fd74eb47c0ac089", size = 1951612, upload-time = "2026-04-20T14:42:42.996Z" }, + { url = "https://files.pythonhosted.org/packages/50/6e/b7348fd30d6556d132cddd5bd79f37f96f2601fe0608afac4f5fb01ec0b3/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75a519dab6d63c514f3a81053e5266c549679e4aa88f6ec57f2b7b854aceb1b0", size = 1977027, upload-time = "2026-04-20T14:42:02.001Z" }, + { url = "https://files.pythonhosted.org/packages/82/11/31d60ee2b45540d3fb0b29302a393dbc01cd771c473f5b5147bcd353e593/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6cd87cb1575b1ad05ba98894c5b5c96411ef678fa2f6ed2576607095b8d9789", size = 2063008, upload-time = "2026-04-20T14:44:17.952Z" }, + { url = "https://files.pythonhosted.org/packages/8a/db/3a9d1957181b59258f44a2300ab0f0be9d1e12d662a4f57bb31250455c52/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f80a55484b8d843c8ada81ebf70a682f3f00a3d40e378c06cf17ecb44d280d7d", size = 2233082, upload-time = "2026-04-20T14:40:57.934Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e1/3277c38792aeb5cfb18c2f0c5785a221d9ff4e149abbe1184d53d5f72273/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3861f1731b90c50a3266316b9044f5c9b405eecb8e299b0a7120596334e4fe9c", size = 2304615, upload-time = "2026-04-20T14:42:12.584Z" }, + { url = "https://files.pythonhosted.org/packages/5e/d5/e3d9717c9eba10855325650afd2a9cba8e607321697f18953af9d562da2f/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb528e295ed31570ac3dcc9bfdd6e0150bc11ce6168ac87a8082055cf1a67395", size = 2094380, upload-time = "2026-04-20T14:43:05.522Z" }, + { url = "https://files.pythonhosted.org/packages/a1/20/abac35dedcbfd66c6f0b03e4e3564511771d6c9b7ede10a362d03e110d9b/pydantic_core-2.46.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:367508faa4973b992b271ba1494acaab36eb7e8739d1e47be5035fb1ea225396", size = 2135429, upload-time = "2026-04-20T14:41:55.549Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a5/41bfd1df69afad71b5cf0535055bccc73022715ad362edbc124bc1e021d7/pydantic_core-2.46.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ad3c826fe523e4becf4fe39baa44286cff85ef137c729a2c5e269afbfd0905d", size = 2174582, upload-time = "2026-04-20T14:41:45.96Z" }, + { url = "https://files.pythonhosted.org/packages/79/65/38d86ea056b29b2b10734eb23329b7a7672ca604df4f2b6e9c02d4ee22fe/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ec638c5d194ef8af27db69f16c954a09797c0dc25015ad6123eb2c73a4d271ca", size = 2187533, upload-time = "2026-04-20T14:40:55.367Z" }, + { url = "https://files.pythonhosted.org/packages/b6/55/a1129141678a2026badc539ad1dee0a71d06f54c2f06a4bd68c030ac781b/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:28ed528c45446062ee66edb1d33df5d88828ae167de76e773a3c7f64bd14e976", size = 2332985, upload-time = "2026-04-20T14:44:13.05Z" }, + { url = "https://files.pythonhosted.org/packages/d7/60/cb26f4077719f709e54819f4e8e1d43f4091f94e285eb6bd21e1190a7b7c/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aed19d0c783886d5bd86d80ae5030006b45e28464218747dcf83dabfdd092c7b", size = 2373670, upload-time = "2026-04-20T14:41:53.421Z" }, + { url = "https://files.pythonhosted.org/packages/6b/7e/c3f21882bdf1d8d086876f81b5e296206c69c6082551d776895de7801fa0/pydantic_core-2.46.3-cp312-cp312-win32.whl", hash = "sha256:06d5d8820cbbdb4147578c1fe7ffcd5b83f34508cb9f9ab76e807be7db6ff0a4", size = 1966722, upload-time = "2026-04-20T14:44:30.588Z" }, + { url = "https://files.pythonhosted.org/packages/57/be/6b5e757b859013ebfbd7adba02f23b428f37c86dcbf78b5bb0b4ffd36e99/pydantic_core-2.46.3-cp312-cp312-win_amd64.whl", hash = "sha256:c3212fda0ee959c1dd04c60b601ec31097aaa893573a3a1abd0a47bcac2968c1", size = 2072970, upload-time = "2026-04-20T14:42:54.248Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f8/a989b21cc75e9a32d24192ef700eea606521221a89faa40c919ce884f2b1/pydantic_core-2.46.3-cp312-cp312-win_arm64.whl", hash = "sha256:f1f8338dd7a7f31761f1f1a3c47503a9a3b34eea3c8b01fa6ee96408affb5e72", size = 2035963, upload-time = "2026-04-20T14:44:20.4Z" }, + { url = "https://files.pythonhosted.org/packages/9b/3c/9b5e8eb9821936d065439c3b0fb1490ffa64163bfe7e1595985a47896073/pydantic_core-2.46.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:12bc98de041458b80c86c56b24df1d23832f3e166cbaff011f25d187f5c62c37", size = 2102109, upload-time = "2026-04-20T14:41:24.219Z" }, + { url = "https://files.pythonhosted.org/packages/91/97/1c41d1f5a19f241d8069f1e249853bcce378cdb76eec8ab636d7bc426280/pydantic_core-2.46.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:85348b8f89d2c3508b65b16c3c33a4da22b8215138d8b996912bb1532868885f", size = 1951820, upload-time = "2026-04-20T14:42:14.236Z" }, + { url = "https://files.pythonhosted.org/packages/30/b4/d03a7ae14571bc2b6b3c7b122441154720619afe9a336fa3a95434df5e2f/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1105677a6df914b1fb71a81b96c8cce7726857e1717d86001f29be06a25ee6f8", size = 1977785, upload-time = "2026-04-20T14:42:31.648Z" }, + { url = "https://files.pythonhosted.org/packages/ae/0c/4086f808834b59e3c8f1aa26df8f4b6d998cdcf354a143d18ef41529d1fe/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87082cd65669a33adeba5470769e9704c7cf026cc30afb9cc77fd865578ebaad", size = 2062761, upload-time = "2026-04-20T14:40:37.093Z" }, + { url = "https://files.pythonhosted.org/packages/fa/71/a649be5a5064c2df0db06e0a512c2281134ed2fcc981f52a657936a7527c/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e5f66e12c4f5212d08522963380eaaeac5ebd795826cfd19b2dfb0c7a52b9c", size = 2232989, upload-time = "2026-04-20T14:42:59.254Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/7756e75763e810b3a710f4724441d1ecc5883b94aacb07ca71c5fb5cfb69/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6cdf19bf84128d5e7c37e8a73a0c5c10d51103a650ac585d42dd6ae233f2b7f", size = 2303975, upload-time = "2026-04-20T14:41:32.287Z" }, + { url = "https://files.pythonhosted.org/packages/6c/35/68a762e0c1e31f35fa0dac733cbd9f5b118042853698de9509c8e5bf128b/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031bb17f4885a43773c8c763089499f242aee2ea85cf17154168775dccdecf35", size = 2095325, upload-time = "2026-04-20T14:42:47.685Z" }, + { url = "https://files.pythonhosted.org/packages/77/bf/1bf8c9a8e91836c926eae5e3e51dce009bf495a60ca56060689d3df3f340/pydantic_core-2.46.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:bcf2a8b2982a6673693eae7348ef3d8cf3979c1d63b54fca7c397a635cc68687", size = 2133368, upload-time = "2026-04-20T14:41:22.766Z" }, + { url = "https://files.pythonhosted.org/packages/e5/50/87d818d6bab915984995157ceb2380f5aac4e563dddbed6b56f0ed057aba/pydantic_core-2.46.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28e8cf2f52d72ced402a137145923a762cbb5081e48b34312f7a0c8f55928ec3", size = 2173908, upload-time = "2026-04-20T14:42:52.044Z" }, + { url = "https://files.pythonhosted.org/packages/91/88/a311fb306d0bd6185db41fa14ae888fb81d0baf648a761ae760d30819d33/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:17eaface65d9fc5abb940003020309c1bf7a211f5f608d7870297c367e6f9022", size = 2186422, upload-time = "2026-04-20T14:43:29.55Z" }, + { url = "https://files.pythonhosted.org/packages/8f/79/28fd0d81508525ab2054fef7c77a638c8b5b0afcbbaeee493cf7c3fef7e1/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:93fd339f23408a07e98950a89644f92c54d8729719a40b30c0a30bb9ebc55d23", size = 2332709, upload-time = "2026-04-20T14:42:16.134Z" }, + { url = "https://files.pythonhosted.org/packages/b3/21/795bf5fe5c0f379308b8ef19c50dedab2e7711dbc8d0c2acf08f1c7daa05/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:23cbdb3aaa74dfe0837975dbf69b469753bbde8eacace524519ffdb6b6e89eb7", size = 2372428, upload-time = "2026-04-20T14:41:10.974Z" }, + { url = "https://files.pythonhosted.org/packages/45/b3/ed14c659cbe7605e3ef063077680a64680aec81eb1a04763a05190d49b7f/pydantic_core-2.46.3-cp313-cp313-win32.whl", hash = "sha256:610eda2e3838f401105e6326ca304f5da1e15393ae25dacae5c5c63f2c275b13", size = 1965601, upload-time = "2026-04-20T14:41:42.128Z" }, + { url = "https://files.pythonhosted.org/packages/ef/bb/adb70d9a762ddd002d723fbf1bd492244d37da41e3af7b74ad212609027e/pydantic_core-2.46.3-cp313-cp313-win_amd64.whl", hash = "sha256:68cc7866ed863db34351294187f9b729964c371ba33e31c26f478471c52e1ed0", size = 2071517, upload-time = "2026-04-20T14:43:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/52/eb/66faefabebfe68bd7788339c9c9127231e680b11906368c67ce112fdb47f/pydantic_core-2.46.3-cp313-cp313-win_arm64.whl", hash = "sha256:f64b5537ac62b231572879cd08ec05600308636a5d63bcbdb15063a466977bec", size = 2035802, upload-time = "2026-04-20T14:43:38.507Z" }, + { url = "https://files.pythonhosted.org/packages/7f/db/a7bcb4940183fda36022cd18ba8dd12f2dff40740ec7b58ce7457befa416/pydantic_core-2.46.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:afa3aa644f74e290cdede48a7b0bee37d1c35e71b05105f6b340d484af536d9b", size = 2097614, upload-time = "2026-04-20T14:44:38.374Z" }, + { url = "https://files.pythonhosted.org/packages/24/35/e4066358a22e3e99519db370494c7528f5a2aa1367370e80e27e20283543/pydantic_core-2.46.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ced3310e51aa425f7f77da8bbbb5212616655bedbe82c70944320bc1dbe5e018", size = 1951896, upload-time = "2026-04-20T14:40:53.996Z" }, + { url = "https://files.pythonhosted.org/packages/87/92/37cf4049d1636996e4b888c05a501f40a43ff218983a551d57f9d5e14f0d/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e29908922ce9da1a30b4da490bd1d3d82c01dcfdf864d2a74aacee674d0bfa34", size = 1979314, upload-time = "2026-04-20T14:41:49.446Z" }, + { url = "https://files.pythonhosted.org/packages/d8/36/9ff4d676dfbdfb2d591cf43f3d90ded01e15b1404fd101180ed2d62a2fd3/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0c9ff69140423eea8ed2d5477df3ba037f671f5e897d206d921bc9fdc39613e7", size = 2056133, upload-time = "2026-04-20T14:42:23.574Z" }, + { url = "https://files.pythonhosted.org/packages/bc/f0/405b442a4d7ba855b06eec8b2bf9c617d43b8432d099dfdc7bf999293495/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b675ab0a0d5b1c8fdb81195dc5bcefea3f3c240871cdd7ff9a2de8aa50772eb2", size = 2228726, upload-time = "2026-04-20T14:44:22.816Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f8/65cd92dd5a0bd89ba277a98ecbfaf6fc36bbd3300973c7a4b826d6ab1391/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0087084960f209a9a4af50ecd1fb063d9ad3658c07bb81a7a53f452dacbfb2ba", size = 2301214, upload-time = "2026-04-20T14:44:48.792Z" }, + { url = "https://files.pythonhosted.org/packages/fd/86/ef96a4c6e79e7a2d0410826a68fbc0eccc0fd44aa733be199d5fcac3bb87/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed42e6cc8e1b0e2b9b96e2276bad70ae625d10d6d524aed0c93de974ae029f9f", size = 2099927, upload-time = "2026-04-20T14:41:40.196Z" }, + { url = "https://files.pythonhosted.org/packages/6d/53/269caf30e0096e0a8a8f929d1982a27b3879872cca2d917d17c2f9fdf4fe/pydantic_core-2.46.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:f1771ce258afb3e4201e67d154edbbae712a76a6081079fe247c2f53c6322c22", size = 2128789, upload-time = "2026-04-20T14:41:15.868Z" }, + { url = "https://files.pythonhosted.org/packages/00/b0/1a6d9b6a587e118482910c244a1c5acf4d192604174132efd12bf0ac486f/pydantic_core-2.46.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a7610b6a5242a6c736d8ad47fd5fff87fcfe8f833b281b1c409c3d6835d9227f", size = 2173815, upload-time = "2026-04-20T14:44:25.152Z" }, + { url = "https://files.pythonhosted.org/packages/87/56/e7e00d4041a7e62b5a40815590114db3b535bf3ca0bf4dca9f16cef25246/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:ff5e7783bcc5476e1db448bf268f11cb257b1c276d3e89f00b5727be86dd0127", size = 2181608, upload-time = "2026-04-20T14:41:28.933Z" }, + { url = "https://files.pythonhosted.org/packages/e8/22/4bd23c3d41f7c185d60808a1de83c76cf5aeabf792f6c636a55c3b1ec7f9/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:9d2e32edcc143bc01e95300671915d9ca052d4f745aa0a49c48d4803f8a85f2c", size = 2326968, upload-time = "2026-04-20T14:42:03.962Z" }, + { url = "https://files.pythonhosted.org/packages/24/ac/66cd45129e3915e5ade3b292cb3bc7fd537f58f8f8dbdaba6170f7cabb74/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6e42d83d1c6b87fa56b521479cff237e626a292f3b31b6345c15a99121b454c1", size = 2369842, upload-time = "2026-04-20T14:41:35.52Z" }, + { url = "https://files.pythonhosted.org/packages/a2/51/dd4248abb84113615473aa20d5545b7c4cd73c8644003b5259686f93996c/pydantic_core-2.46.3-cp314-cp314-win32.whl", hash = "sha256:07bc6d2a28c3adb4f7c6ae46aa4f2d2929af127f587ed44057af50bf1ce0f505", size = 1959661, upload-time = "2026-04-20T14:41:00.042Z" }, + { url = "https://files.pythonhosted.org/packages/20/eb/59980e5f1ae54a3b86372bd9f0fa373ea2d402e8cdcd3459334430f91e91/pydantic_core-2.46.3-cp314-cp314-win_amd64.whl", hash = "sha256:8940562319bc621da30714617e6a7eaa6b98c84e8c685bcdc02d7ed5e7c7c44e", size = 2071686, upload-time = "2026-04-20T14:43:16.471Z" }, + { url = "https://files.pythonhosted.org/packages/8c/db/1cf77e5247047dfee34bc01fa9bca134854f528c8eb053e144298893d370/pydantic_core-2.46.3-cp314-cp314-win_arm64.whl", hash = "sha256:5dcbbcf4d22210ced8f837c96db941bdb078f419543472aca5d9a0bb7cddc7df", size = 2026907, upload-time = "2026-04-20T14:43:31.732Z" }, + { url = "https://files.pythonhosted.org/packages/57/c0/b3df9f6a543276eadba0a48487b082ca1f201745329d97dbfa287034a230/pydantic_core-2.46.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d0fe3dce1e836e418f912c1ad91c73357d03e556a4d286f441bf34fed2dbeecf", size = 2095047, upload-time = "2026-04-20T14:42:37.982Z" }, + { url = "https://files.pythonhosted.org/packages/66/57/886a938073b97556c168fd99e1a7305bb363cd30a6d2c76086bf0587b32a/pydantic_core-2.46.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9ce92e58abc722dac1bf835a6798a60b294e48eb0e625ec9fd994b932ac5feee", size = 1934329, upload-time = "2026-04-20T14:43:49.655Z" }, + { url = "https://files.pythonhosted.org/packages/0b/7c/b42eaa5c34b13b07ecb51da21761297a9b8eb43044c864a035999998f328/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a03e6467f0f5ab796a486146d1b887b2dc5e5f9b3288898c1b1c3ad974e53e4a", size = 1974847, upload-time = "2026-04-20T14:42:10.737Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9b/92b42db6543e7de4f99ae977101a2967b63122d4b6cf7773812da2d7d5b5/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2798b6ba041b9d70acfb9071a2ea13c8456dd1e6a5555798e41ba7b0790e329c", size = 2041742, upload-time = "2026-04-20T14:40:44.262Z" }, + { url = "https://files.pythonhosted.org/packages/0f/19/46fbe1efabb5aa2834b43b9454e70f9a83ad9c338c1291e48bdc4fecf167/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9be3e221bdc6d69abf294dcf7aff6af19c31a5cdcc8f0aa3b14be29df4bd03b1", size = 2236235, upload-time = "2026-04-20T14:41:27.307Z" }, + { url = "https://files.pythonhosted.org/packages/77/da/b3f95bc009ad60ec53120f5d16c6faa8cabdbe8a20d83849a1f2b8728148/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f13936129ce841f2a5ddf6f126fea3c43cd128807b5a59588c37cf10178c2e64", size = 2282633, upload-time = "2026-04-20T14:44:33.271Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6e/401336117722e28f32fb8220df676769d28ebdf08f2f4469646d404c43a3/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28b5f2ef03416facccb1c6ef744c69793175fd27e44ef15669201601cf423acb", size = 2109679, upload-time = "2026-04-20T14:44:41.065Z" }, + { url = "https://files.pythonhosted.org/packages/fc/53/b289f9bc8756a32fe718c46f55afaeaf8d489ee18d1a1e7be1db73f42cc4/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:830d1247d77ad23852314f069e9d7ddafeec5f684baf9d7e7065ed46a049c4e6", size = 2108342, upload-time = "2026-04-20T14:42:50.144Z" }, + { url = "https://files.pythonhosted.org/packages/10/5b/8292fc7c1f9111f1b2b7c1b0dcf1179edcd014fc3ea4517499f50b829d71/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0793c90c1a3c74966e7975eaef3ed30ebdff3260a0f815a62a22adc17e4c01c", size = 2157208, upload-time = "2026-04-20T14:42:08.133Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9e/f80044e9ec07580f057a89fc131f78dda7a58751ddf52bbe05eaf31db50f/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d2d0aead851b66f5245ec0c4fb2612ef457f8bbafefdf65a2bf9d6bac6140f47", size = 2167237, upload-time = "2026-04-20T14:42:25.412Z" }, + { url = "https://files.pythonhosted.org/packages/f8/84/6781a1b037f3b96be9227edbd1101f6d3946746056231bf4ac48cdff1a8d/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:2f40e4246676beb31c5ce77c38a55ca4e465c6b38d11ea1bd935420568e0b1ab", size = 2312540, upload-time = "2026-04-20T14:40:40.313Z" }, + { url = "https://files.pythonhosted.org/packages/3e/db/19c0839feeb728e7df03255581f198dfdf1c2aeb1e174a8420b63c5252e5/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:cf489cf8986c543939aeee17a09c04d6ffb43bfef8ca16fcbcc5cfdcbed24dba", size = 2369556, upload-time = "2026-04-20T14:41:09.427Z" }, + { url = "https://files.pythonhosted.org/packages/e0/15/3228774cb7cd45f5f721ddf1b2242747f4eb834d0c491f0c02d606f09fed/pydantic_core-2.46.3-cp314-cp314t-win32.whl", hash = "sha256:ffe0883b56cfc05798bf994164d2b2ff03efe2d22022a2bb080f3b626176dd56", size = 1949756, upload-time = "2026-04-20T14:41:25.717Z" }, + { url = "https://files.pythonhosted.org/packages/b8/2a/c79cf53fd91e5a87e30d481809f52f9a60dd221e39de66455cf04deaad37/pydantic_core-2.46.3-cp314-cp314t-win_amd64.whl", hash = "sha256:706d9d0ce9cf4593d07270d8e9f53b161f90c57d315aeec4fb4fd7a8b10240d8", size = 2051305, upload-time = "2026-04-20T14:43:18.627Z" }, + { url = "https://files.pythonhosted.org/packages/0b/db/d8182a7f1d9343a032265aae186eb063fe26ca4c40f256b21e8da4498e89/pydantic_core-2.46.3-cp314-cp314t-win_arm64.whl", hash = "sha256:77706aeb41df6a76568434701e0917da10692da28cb69d5fb6919ce5fdb07374", size = 2026310, upload-time = "2026-04-20T14:41:01.778Z" }, + { url = "https://files.pythonhosted.org/packages/34/42/f426db557e8ab2791bc7562052299944a118655496fbff99914e564c0a94/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:b12dd51f1187c2eb489af8e20f880362db98e954b54ab792fa5d92e8bcc6b803", size = 2091877, upload-time = "2026-04-20T14:43:27.091Z" }, + { url = "https://files.pythonhosted.org/packages/5c/4f/86a832a9d14df58e663bfdf4627dc00d3317c2bd583c4fb23390b0f04b8e/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f00a0961b125f1a47af7bcc17f00782e12f4cd056f83416006b30111d941dfa3", size = 1932428, upload-time = "2026-04-20T14:40:45.781Z" }, + { url = "https://files.pythonhosted.org/packages/11/1a/fe857968954d93fb78e0d4b6df5c988c74c4aaa67181c60be7cfe327c0ca/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57697d7c056aca4bbb680200f96563e841a6386ac1129370a0102592f4dddff5", size = 1997550, upload-time = "2026-04-20T14:44:02.425Z" }, + { url = "https://files.pythonhosted.org/packages/17/eb/9d89ad2d9b0ba8cd65393d434471621b98912abb10fbe1df08e480ba57b5/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd35aa21299def8db7ef4fe5c4ff862941a9a158ca7b63d61e66fe67d30416b4", size = 2137657, upload-time = "2026-04-20T14:42:45.149Z" }, ] [[package]] name = "pydantic-settings" -version = "2.13.1" +version = "2.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/52/6d/fffca34caecc4a3f97bda81b2098da5e8ab7efc9a66e819074a11955d87e/pydantic_settings-2.13.1.tar.gz", hash = "sha256:b4c11847b15237fb0171e1462bf540e294affb9b86db4d9aa5c01730bdbe4025", size = 223826, upload-time = "2026-02-19T13:45:08.055Z" } +sdist = { url = "https://files.pythonhosted.org/packages/42/98/c8345dccdc31de4228c039a98f6467a941e39558da41c1744fbe29fa5666/pydantic_settings-2.14.0.tar.gz", hash = "sha256:24285fd4b0e0c06507dd9fdfd331ee23794305352aaec8fc4eb92d4047aeb67d", size = 235709, upload-time = "2026-04-20T13:37:40.293Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/4b/ccc026168948fec4f7555b9164c724cf4125eac006e176541483d2c959be/pydantic_settings-2.13.1-py3-none-any.whl", hash = "sha256:d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237", size = 58929, upload-time = "2026-02-19T13:45:06.034Z" }, + { url = "https://files.pythonhosted.org/packages/01/dd/bebff3040138f00ae8a102d426b27349b9a49acc310fcae7f92112d867e3/pydantic_settings-2.14.0-py3-none-any.whl", hash = "sha256:fc8d5d692eb7092e43c8647c1c35a3ecd00e040fcf02ed86f4cb5458ca62182e", size = 60940, upload-time = "2026-04-20T13:37:38.586Z" }, ] [[package]] name = "pygments" -version = "2.19.2" +version = "2.20.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] [[package]] name = "pyinstaller" -version = "6.19.0" +version = "6.20.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "altgraph" }, @@ -1326,32 +1342,32 @@ dependencies = [ { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, { name = "setuptools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c8/63/fd62472b6371d89dc138d40c36d87a50dc2de18a035803bbdc376b4ffac4/pyinstaller-6.19.0.tar.gz", hash = "sha256:ec73aeb8bd9b7f2f1240d328a4542e90b3c6e6fbc106014778431c616592a865", size = 4036072, upload-time = "2026-02-14T18:06:28.718Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/60/d03d52e6690d4e9caf333dcd14550cde634ce6c118b3bc8fa3112c3186fd/pyinstaller-6.20.0.tar.gz", hash = "sha256:95c5c7e03d5d61e9dfb8ef259c699cf492bb1041beb6dbe83696608cec07347a", size = 4048728, upload-time = "2026-04-22T20:59:36.96Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/eb/23374721fecfa72677e79800921cb6aceefa6ba48574dc404f3f6c6c3be7/pyinstaller-6.19.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:4190e76b74f0c4b5c5f11ac360928cd2e36ec8e3194d437bf6b8648c7bc0c134", size = 1040563, upload-time = "2026-02-14T18:05:22.436Z" }, - { url = "https://files.pythonhosted.org/packages/cd/7e/dfd724b0b533f5aaec0ee5df406fe2319987ed6964480a706f85478b12ea/pyinstaller-6.19.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8bd68abd812d8a6ba33b9f1810e91fee0f325969733721b78151f0065319ca11", size = 735477, upload-time = "2026-02-14T18:05:27.143Z" }, - { url = "https://files.pythonhosted.org/packages/88/c9/ee3a4101c31f26344e66896c73c1fd6ed8282bf871473365b7f8674af406/pyinstaller-6.19.0-py3-none-manylinux2014_i686.whl", hash = "sha256:1ec54ef967996ca61dacba676227e2b23219878ccce5ee9d6f3aada7b8ed8abf", size = 747143, upload-time = "2026-02-14T18:05:31.488Z" }, - { url = "https://files.pythonhosted.org/packages/da/0a/fc77e9f861be8cf300ac37155f59cc92aff99b29f2ddd78546f563a5b5a6/pyinstaller-6.19.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:4ab2bb52e58448e14ddf9450601bdedd66800465043501c1d8f1cab87b60b122", size = 744849, upload-time = "2026-02-14T18:05:35.492Z" }, - { url = "https://files.pythonhosted.org/packages/6d/e3/6872e020ee758afe0b821663858492c10745608b07150e5e2c824a5b3e1c/pyinstaller-6.19.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:da6d5c6391ccefe73554b9fa29b86001c8e378e0f20c2a4004f836ba537eff63", size = 741590, upload-time = "2026-02-14T18:05:39.59Z" }, - { url = "https://files.pythonhosted.org/packages/53/60/b8db5f1a4b0fb228175f2ea0aa33f949adcc097fbe981cc524f9faf85777/pyinstaller-6.19.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a0fc5f6b3c55aa54353f0c74ffa59b1115433c1850c6f655d62b461a2ed6cbbe", size = 741448, upload-time = "2026-02-14T18:05:45.636Z" }, - { url = "https://files.pythonhosted.org/packages/6f/4d/63b0600f2694e9141b83129fbc1c488ec84d5a0770b1448ec154dcd0fee9/pyinstaller-6.19.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:e649ba6bd1b0b89b210ad92adb5fbdc8a42dd2c5ca4f72ef3a0bfec83a424b83", size = 740613, upload-time = "2026-02-14T18:05:49.726Z" }, - { url = "https://files.pythonhosted.org/packages/01/d4/e812ad36178093a0e9fd4b8127577748dd85b0cb71de912229dca21fd741/pyinstaller-6.19.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:481a909c8e60c8692fc60fcb1344d984b44b943f8bc9682f2fcdae305ad297e6", size = 740350, upload-time = "2026-02-14T18:05:54.093Z" }, - { url = "https://files.pythonhosted.org/packages/52/03/b2c2ee41fb8e10fd2a45d21f5ec2ef25852cfb978dbf762972eed59e3d63/pyinstaller-6.19.0-py3-none-win32.whl", hash = "sha256:3c5c251054fe4cfaa04c34a363dcfbf811545438cb7198304cd444756bc2edd2", size = 1324317, upload-time = "2026-02-14T18:06:00.085Z" }, - { url = "https://files.pythonhosted.org/packages/9c/d3/6d5e62b8270e2b53a6065e281b3a7785079b00e9019c8019952828dd1669/pyinstaller-6.19.0-py3-none-win_amd64.whl", hash = "sha256:b5bb6536c6560330d364d91522250f254b107cf69129d9cbcd0e6727c570be33", size = 1384894, upload-time = "2026-02-14T18:06:06.425Z" }, - { url = "https://files.pythonhosted.org/packages/81/65/458cd523308a101a22fd2742893405030cc24994cc74b1b767cecf137160/pyinstaller-6.19.0-py3-none-win_arm64.whl", hash = "sha256:c2d5a539b0bfe6159d5522c8c70e1c0e487f22c2badae0f97d45246223b798ea", size = 1325374, upload-time = "2026-02-14T18:06:12.804Z" }, + { url = "https://files.pythonhosted.org/packages/d0/e4/e228d6d1bbb7fd62dc660a8fb202a583b023d3a3624ca95d1a9290ee4d6a/pyinstaller-6.20.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:bf3be4e1284ee78ddccba5e29f99443a12a7b4673168288ffc4c9d38c6f7b90e", size = 1047642, upload-time = "2026-04-22T20:58:32.006Z" }, + { url = "https://files.pythonhosted.org/packages/ce/bd/afb631bcb3f9040efebd4f6d067f0828b51710818f69fb41a2d4b7787f52/pyinstaller-6.20.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:72ae9c1fdea134afa791f58bdc9a1934d5c7609753c111e0026bfc272b32b712", size = 742494, upload-time = "2026-04-22T20:58:36.285Z" }, + { url = "https://files.pythonhosted.org/packages/76/08/0729a5bac14754150e5d83b39d87d842eb42b0bffcaa03dbad6252e23a39/pyinstaller-6.20.0-py3-none-manylinux2014_i686.whl", hash = "sha256:1031bcc307f3fbeffd4e162723e64d46dbf591c82dd0997413afb2a07328b941", size = 754191, upload-time = "2026-04-22T20:58:40.603Z" }, + { url = "https://files.pythonhosted.org/packages/e6/82/bc0ee4c7b97db1958eb651e0da9fb1e672e5ae53ca8867fd97701de52906/pyinstaller-6.20.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:8df3b3f347659fa2562d8d193a98ad4600133b8b8d07c268df89e4154376750e", size = 751902, upload-time = "2026-04-22T20:58:44.7Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e7/770002d6aaa54173881cb2c49bb195ba67b97bf39bac1cdf320f28401629/pyinstaller-6.20.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:b0d3cc9dd8120d448459bd3880a12e2f9774c51443af49047801446377999a59", size = 748634, upload-time = "2026-04-22T20:58:48.579Z" }, + { url = "https://files.pythonhosted.org/packages/fe/db/68ba1fccb71278b2124fb90b37b7c8c0bc4c1173fba45b94466df3d9cb7f/pyinstaller-6.20.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:03696bb6350177c6bc23bcaf78e71a33c4a89b6754dd90d1be2f318e978c918b", size = 748490, upload-time = "2026-04-22T20:58:52.749Z" }, + { url = "https://files.pythonhosted.org/packages/03/0f/ac77ffa996a56be3d5c8f85734a007f8347240691657f9704e7de2527fa3/pyinstaller-6.20.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:6357f1699f6af84f37e7367f031d4f68abdba65543b83990c9e8f5a4cebed0b7", size = 747650, upload-time = "2026-04-22T20:58:57.093Z" }, + { url = "https://files.pythonhosted.org/packages/e0/56/1ee91c3a2bc10ca1f36da10a6fd55ff7efc4dec367171eb25992a827874f/pyinstaller-6.20.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:0ab39c690abad26ba148e8f664f0478acc82a733997f4f22e757774832802da9", size = 747413, upload-time = "2026-04-22T20:59:01.174Z" }, + { url = "https://files.pythonhosted.org/packages/d7/55/ae264339996953c4cdf9d89d916a0a8fa26a83cf917a742fff8b9d5f3fe8/pyinstaller-6.20.0-py3-none-win32.whl", hash = "sha256:9a7637e8e44b4387b13667fdcaac86ab6b29c446c16d34d8401539b81838759c", size = 1331584, upload-time = "2026-04-22T20:59:07.201Z" }, + { url = "https://files.pythonhosted.org/packages/76/8c/300f57578882cce259bfb5ae56fda3b69caa3fe9df40a176c719920ea6e2/pyinstaller-6.20.0-py3-none-win_amd64.whl", hash = "sha256:d588844e890ee80c4365867f98146636e1849bbca8e4284bbf0c809aff0f161a", size = 1391851, upload-time = "2026-04-22T20:59:14.024Z" }, + { url = "https://files.pythonhosted.org/packages/8a/ea/b2f8e1642aecda78c0b75c7321f708e49e10bb3c00dd4f148c40761a1527/pyinstaller-6.20.0-py3-none-win_arm64.whl", hash = "sha256:bd53282c0a73e5c95573e1ddc8e5d564d4932bec91efbaed4dc5fdff9c2ae7f2", size = 1332259, upload-time = "2026-04-22T20:59:20.509Z" }, ] [[package]] name = "pyinstaller-hooks-contrib" -version = "2026.2" +version = "2026.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, { name = "setuptools" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6b/90/f3b30d72b89ab5b8f3ef714db94d09c7c263cce6562b4c7c636d99630695/pyinstaller_hooks_contrib-2026.2.tar.gz", hash = "sha256:cbd1eb00b5d13301b1cce602e1fffb17f0c531c0391f0a87a383d376be68a186", size = 171884, upload-time = "2026-03-02T23:07:01.221Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/fe/9278c29394bf69169febc21f96b4252c3ee7c8ec22c2fc545004bed47e71/pyinstaller_hooks_contrib-2026.4.tar.gz", hash = "sha256:766c281acb1ecc32e21c8c667056d7ebf5da0aabd5e30c219f9c2a283620eeaa", size = 173050, upload-time = "2026-03-31T14:10:51.188Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/34/3b/1efef5ff4d4d150f646b873e963437c0b800cb375a37df01fefab149f4d9/pyinstaller_hooks_contrib-2026.2-py3-none-any.whl", hash = "sha256:fc29f0481b58adf78ce9c1d9cf135fe96f38c708f74b2aa0670ef93e59578ab9", size = 453939, upload-time = "2026-03-02T23:06:59.469Z" }, + { url = "https://files.pythonhosted.org/packages/88/f4/035fb8c06deff827f540a9a4ed9122c54e5376fca3e42eddf0c263730775/pyinstaller_hooks_contrib-2026.4-py3-none-any.whl", hash = "sha256:1de1a5e49a878122010b88c7e295502bc69776c157c4a4dc78741a4e6178b00f", size = 455496, upload-time = "2026-03-31T14:10:49.867Z" }, ] [[package]] @@ -1396,11 +1412,11 @@ wheels = [ [[package]] name = "pyjwt" -version = "2.11.0" +version = "2.12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019, upload-time = "2026-01-30T19:59:55.694Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" }, ] [package.optional-dependencies] @@ -1419,15 +1435,15 @@ wheels = [ [[package]] name = "pyright" -version = "1.1.408" +version = "1.1.409" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodeenv" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/74/b2/5db700e52554b8f025faa9c3c624c59f1f6c8841ba81ab97641b54322f16/pyright-1.1.408.tar.gz", hash = "sha256:f28f2321f96852fa50b5829ea492f6adb0e6954568d1caa3f3af3a5f555eb684", size = 4400578, upload-time = "2026-01-08T08:07:38.795Z" } +sdist = { url = "https://files.pythonhosted.org/packages/51/4e/3aa27f74211522dba7e9cbc3e74de779c6d4b654c54e50a4840623be8014/pyright-1.1.409.tar.gz", hash = "sha256:986ee05beca9e077c165758ad123667c679e050059a2546aa02473930394bc93", size = 4430434, upload-time = "2026-04-23T11:02:03.799Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/82/a2c93e32800940d9573fb28c346772a14778b84ba7524e691b324620ab89/pyright-1.1.408-py3-none-any.whl", hash = "sha256:090b32865f4fdb1e0e6cd82bf5618480d48eecd2eb2e70f960982a3d9a4c17c1", size = 6399144, upload-time = "2026-01-08T08:07:37.082Z" }, + { url = "https://files.pythonhosted.org/packages/16/6b/330d8ebae582b30c2959a1ef4c3bc344ebde48c2ff0c3f113c4710735e11/pyright-1.1.409-py3-none-any.whl", hash = "sha256:aa3ea228cab90c845c7a60d28db7a844c04315356392aa09fafcee98c8c22fb3", size = 6438161, upload-time = "2026-04-23T11:02:01.309Z" }, ] [[package]] @@ -1514,15 +1530,15 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.1.0" +version = "1.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/82/bb/93a3e83bdf9322c7e21cafd092e56a4a17c4d8ef4277b6eb01af1a540a6f/python_discovery-1.1.0.tar.gz", hash = "sha256:447941ba1aed8cc2ab7ee3cb91be5fc137c5bdbb05b7e6ea62fbdcb66e50b268", size = 55674, upload-time = "2026-02-26T09:42:49.668Z" } +sdist = { url = "https://files.pythonhosted.org/packages/de/ef/3bae0e537cfe91e8431efcba4434463d2c5a65f5a89edd47c6cf2f03c55f/python_discovery-1.2.2.tar.gz", hash = "sha256:876e9c57139eb757cb5878cbdd9ae5379e5d96266c99ef731119e04fffe533bb", size = 58872, upload-time = "2026-04-07T17:28:49.249Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/54/82a6e2ef37f0f23dccac604b9585bdcbd0698604feb64807dcb72853693e/python_discovery-1.1.0-py3-none-any.whl", hash = "sha256:a162893b8809727f54594a99ad2179d2ede4bf953e12d4c7abc3cc9cdbd1437b", size = 30687, upload-time = "2026-02-26T09:42:48.548Z" }, + { url = "https://files.pythonhosted.org/packages/d8/db/795879cc3ddfe338599bddea6388cc5100b088db0a4caf6e6c1af1c27e04/python_discovery-1.2.2-py3-none-any.whl", hash = "sha256:e1ae95d9af875e78f15e19aed0c6137ab1bb49c200f21f5061786490c9585c7a", size = 31894, upload-time = "2026-04-07T17:28:48.09Z" }, ] [[package]] @@ -1536,11 +1552,11 @@ wheels = [ [[package]] name = "python-multipart" -version = "0.0.22" +version = "0.0.27" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/01/979e98d542a70714b0cb2b6728ed0b7c46792b695e3eaec3e20711271ca3/python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58", size = 37612, upload-time = "2026-01-25T10:15:56.219Z" } +sdist = { url = "https://files.pythonhosted.org/packages/69/9b/f23807317a113dc36e74e75eb265a02dd1a4d9082abc3c1064acd22997c4/python_multipart-0.0.27.tar.gz", hash = "sha256:9870a6a8c5a20a5bf4f07c017bd1489006ff8836cff097b6933355ee2b49b602", size = 44043, upload-time = "2026-04-27T10:51:26.649Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" }, + { url = "https://files.pythonhosted.org/packages/99/78/4126abcbdbd3c559d43e0db7f7b9173fc6befe45d39a2856cc0b8ec2a5a6/python_multipart-0.0.27-py3-none-any.whl", hash = "sha256:6fccfad17a27334bd0193681b369f476eda3409f17381a2d65aa7df3f7275645", size = 29254, upload-time = "2026-04-27T10:51:24.997Z" }, ] [[package]] @@ -1644,7 +1660,7 @@ wheels = [ [[package]] name = "requests" -version = "2.32.5" +version = "2.33.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -1652,9 +1668,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, ] [[package]] @@ -1671,14 +1687,14 @@ wheels = [ [[package]] name = "respx" -version = "0.22.0" +version = "0.23.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f4/7c/96bd0bc759cf009675ad1ee1f96535edcb11e9666b985717eb8c87192a95/respx-0.22.0.tar.gz", hash = "sha256:3c8924caa2a50bd71aefc07aa812f2466ff489f1848c96e954a5362d17095d91", size = 28439, upload-time = "2024-12-19T22:33:59.374Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/98/4e55c9c486404ec12373708d015ebce157966965a5ebe7f28ff2c784d41b/respx-0.23.1.tar.gz", hash = "sha256:242dcc6ce6b5b9bf621f5870c82a63997e8e82bc7c947f9ffe272b8f3dd5a780", size = 29243, upload-time = "2026-04-08T14:37:16.008Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/67/afbb0978d5399bc9ea200f1d4489a23c9a1dad4eee6376242b8182389c79/respx-0.22.0-py2.py3-none-any.whl", hash = "sha256:631128d4c9aba15e56903fb5f66fb1eff412ce28dd387ca3a81339e52dbd3ad0", size = 25127, upload-time = "2024-12-19T22:33:57.837Z" }, + { url = "https://files.pythonhosted.org/packages/1d/4a/221da6ca167db45693d8d26c7dc79ccfc978a440251bf6721c9aaf251ac0/respx-0.23.1-py2.py3-none-any.whl", hash = "sha256:b18004b029935384bccfa6d7d9d74b4ec9af73a081cc28600fffc0447f4b8c1a", size = 25557, upload-time = "2026-04-08T14:37:14.613Z" }, ] [[package]] @@ -1692,15 +1708,15 @@ wheels = [ [[package]] name = "rich" -version = "14.3.3" +version = "15.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, ] [[package]] @@ -1784,41 +1800,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, ] -[[package]] -name = "rsa" -version = "4.9.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyasn1" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, -] - [[package]] name = "ruff" -version = "0.15.4" +version = "0.15.12" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/da/31/d6e536cdebb6568ae75a7f00e4b4819ae0ad2640c3604c305a0428680b0c/ruff-0.15.4.tar.gz", hash = "sha256:3412195319e42d634470cc97aa9803d07e9d5c9223b99bcb1518f0c725f26ae1", size = 4569550, upload-time = "2026-02-26T20:04:14.959Z" } +sdist = { url = "https://files.pythonhosted.org/packages/99/43/3291f1cc9106f4c63bdce7a8d0df5047fe8422a75b091c16b5e9355e0b11/ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", size = 4643852, upload-time = "2026-04-24T18:17:14.305Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/82/c11a03cfec3a4d26a0ea1e571f0f44be5993b923f905eeddfc397c13d360/ruff-0.15.4-py3-none-linux_armv6l.whl", hash = "sha256:a1810931c41606c686bae8b5b9a8072adac2f611bb433c0ba476acba17a332e0", size = 10453333, upload-time = "2026-02-26T20:04:20.093Z" }, - { url = "https://files.pythonhosted.org/packages/ce/5d/6a1f271f6e31dffb31855996493641edc3eef8077b883eaf007a2f1c2976/ruff-0.15.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5a1632c66672b8b4d3e1d1782859e98d6e0b4e70829530666644286600a33992", size = 10853356, upload-time = "2026-02-26T20:04:05.808Z" }, - { url = "https://files.pythonhosted.org/packages/b1/d8/0fab9f8842b83b1a9c2bf81b85063f65e93fb512e60effa95b0be49bfc54/ruff-0.15.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a4386ba2cd6c0f4ff75252845906acc7c7c8e1ac567b7bc3d373686ac8c222ba", size = 10187434, upload-time = "2026-02-26T20:03:54.656Z" }, - { url = "https://files.pythonhosted.org/packages/85/cc/cc220fd9394eff5db8d94dec199eec56dd6c9f3651d8869d024867a91030/ruff-0.15.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2496488bdfd3732747558b6f95ae427ff066d1fcd054daf75f5a50674411e75", size = 10535456, upload-time = "2026-02-26T20:03:52.738Z" }, - { url = "https://files.pythonhosted.org/packages/fa/0f/bced38fa5cf24373ec767713c8e4cadc90247f3863605fb030e597878661/ruff-0.15.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f1c4893841ff2d54cbda1b2860fa3260173df5ddd7b95d370186f8a5e66a4ac", size = 10287772, upload-time = "2026-02-26T20:04:08.138Z" }, - { url = "https://files.pythonhosted.org/packages/2b/90/58a1802d84fed15f8f281925b21ab3cecd813bde52a8ca033a4de8ab0e7a/ruff-0.15.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:820b8766bd65503b6c30aaa6331e8ef3a6e564f7999c844e9a547c40179e440a", size = 11049051, upload-time = "2026-02-26T20:04:03.53Z" }, - { url = "https://files.pythonhosted.org/packages/d2/ac/b7ad36703c35f3866584564dc15f12f91cb1a26a897dc2fd13d7cb3ae1af/ruff-0.15.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9fb74bab47139c1751f900f857fa503987253c3ef89129b24ed375e72873e85", size = 11890494, upload-time = "2026-02-26T20:04:10.497Z" }, - { url = "https://files.pythonhosted.org/packages/93/3d/3eb2f47a39a8b0da99faf9c54d3eb24720add1e886a5309d4d1be73a6380/ruff-0.15.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f80c98765949c518142b3a50a5db89343aa90f2c2bf7799de9986498ae6176db", size = 11326221, upload-time = "2026-02-26T20:04:12.84Z" }, - { url = "https://files.pythonhosted.org/packages/ff/90/bf134f4c1e5243e62690e09d63c55df948a74084c8ac3e48a88468314da6/ruff-0.15.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:451a2e224151729b3b6c9ffb36aed9091b2996fe4bdbd11f47e27d8f2e8888ec", size = 11168459, upload-time = "2026-02-26T20:04:00.969Z" }, - { url = "https://files.pythonhosted.org/packages/b5/e5/a64d27688789b06b5d55162aafc32059bb8c989c61a5139a36e1368285eb/ruff-0.15.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a8f157f2e583c513c4f5f896163a93198297371f34c04220daf40d133fdd4f7f", size = 11104366, upload-time = "2026-02-26T20:03:48.099Z" }, - { url = "https://files.pythonhosted.org/packages/f1/f6/32d1dcb66a2559763fc3027bdd65836cad9eb09d90f2ed6a63d8e9252b02/ruff-0.15.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:917cc68503357021f541e69b35361c99387cdbbf99bd0ea4aa6f28ca99ff5338", size = 10510887, upload-time = "2026-02-26T20:03:45.771Z" }, - { url = "https://files.pythonhosted.org/packages/ff/92/22d1ced50971c5b6433aed166fcef8c9343f567a94cf2b9d9089f6aa80fe/ruff-0.15.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e9737c8161da79fd7cfec19f1e35620375bd8b2a50c3e77fa3d2c16f574105cc", size = 10285939, upload-time = "2026-02-26T20:04:22.42Z" }, - { url = "https://files.pythonhosted.org/packages/e6/f4/7c20aec3143837641a02509a4668fb146a642fd1211846634edc17eb5563/ruff-0.15.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:291258c917539e18f6ba40482fe31d6f5ac023994ee11d7bdafd716f2aab8a68", size = 10765471, upload-time = "2026-02-26T20:03:58.924Z" }, - { url = "https://files.pythonhosted.org/packages/d0/09/6d2f7586f09a16120aebdff8f64d962d7c4348313c77ebb29c566cefc357/ruff-0.15.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3f83c45911da6f2cd5936c436cf86b9f09f09165f033a99dcf7477e34041cbc3", size = 11263382, upload-time = "2026-02-26T20:04:24.424Z" }, - { url = "https://files.pythonhosted.org/packages/1b/fa/2ef715a1cd329ef47c1a050e10dee91a9054b7ce2fcfdd6a06d139afb7ec/ruff-0.15.4-py3-none-win32.whl", hash = "sha256:65594a2d557d4ee9f02834fcdf0a28daa8b3b9f6cb2cb93846025a36db47ef22", size = 10506664, upload-time = "2026-02-26T20:03:50.56Z" }, - { url = "https://files.pythonhosted.org/packages/d0/a8/c688ef7e29983976820d18710f955751d9f4d4eb69df658af3d006e2ba3e/ruff-0.15.4-py3-none-win_amd64.whl", hash = "sha256:04196ad44f0df220c2ece5b0e959c2f37c777375ec744397d21d15b50a75264f", size = 11651048, upload-time = "2026-02-26T20:04:17.191Z" }, - { url = "https://files.pythonhosted.org/packages/3e/0a/9e1be9035b37448ce2e68c978f0591da94389ade5a5abafa4cf99985d1b2/ruff-0.15.4-py3-none-win_arm64.whl", hash = "sha256:60d5177e8cfc70e51b9c5fad936c634872a74209f934c1e79107d11787ad5453", size = 10966776, upload-time = "2026-02-26T20:03:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6e/e78ffb61d4686f3d96ba3df2c801161843746dcbcbb17a1e927d4829312b/ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", size = 10640713, upload-time = "2026-04-24T18:17:22.841Z" }, + { url = "https://files.pythonhosted.org/packages/ae/08/a317bc231fb9e7b93e4ef3089501e51922ff88d6936ce5cf870c4fe55419/ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c", size = 11069267, upload-time = "2026-04-24T18:17:30.105Z" }, + { url = "https://files.pythonhosted.org/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5", size = 10397182, upload-time = "2026-04-24T18:17:07.177Z" }, + { url = "https://files.pythonhosted.org/packages/71/e0/3310fc6d1b5e1fdea22bf3b1b807c7e187b581021b0d7d4514cccdb5fb71/ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002", size = 10758012, upload-time = "2026-04-24T18:16:55.759Z" }, + { url = "https://files.pythonhosted.org/packages/11/c1/a606911aee04c324ddaa883ae418f3569792fd3c4a10c50e0dd0a2311e1e/ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5", size = 10447479, upload-time = "2026-04-24T18:16:51.677Z" }, + { url = "https://files.pythonhosted.org/packages/9d/68/4201e8444f0894f21ab4aeeaee68aa4f10b51613514a20d80bd628d57e88/ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6", size = 11234040, upload-time = "2026-04-24T18:17:16.529Z" }, + { url = "https://files.pythonhosted.org/packages/34/ff/8a6d6cf4ccc23fd67060874e832c18919d1557a0611ebef03fdb01fff11e/ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33", size = 12087377, upload-time = "2026-04-24T18:17:04.944Z" }, + { url = "https://files.pythonhosted.org/packages/85/f6/c669cf73f5152f623d34e69866a46d5e6185816b19fcd5b6dd8a2d299922/ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847", size = 11367784, upload-time = "2026-04-24T18:17:25.409Z" }, + { url = "https://files.pythonhosted.org/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0", size = 11344088, upload-time = "2026-04-24T18:17:12.258Z" }, + { url = "https://files.pythonhosted.org/packages/c2/8d/49afab3645e31e12c590acb6d3b5b69d7aab5b81926dbaf7461f9441f37a/ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339", size = 11271770, upload-time = "2026-04-24T18:17:02.457Z" }, + { url = "https://files.pythonhosted.org/packages/46/06/33f41fe94403e2b755481cdfb9b7ef3e4e0ed031c4581124658d935d52b4/ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5", size = 10719355, upload-time = "2026-04-24T18:17:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/0d/59/18aa4e014debbf559670e4048e39260a85c7fcee84acfd761ac01e7b8d35/ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd", size = 10462758, upload-time = "2026-04-24T18:17:32.347Z" }, + { url = "https://files.pythonhosted.org/packages/25/e7/cc9f16fd0f3b5fddcbd7ec3d6ae30c8f3fde1047f32a4093a98d633c6570/ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b", size = 10953498, upload-time = "2026-04-24T18:17:20.674Z" }, + { url = "https://files.pythonhosted.org/packages/72/7a/a9ba7f98c7a575978698f4230c5e8cc54bbc761af34f560818f933dafa0c/ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e", size = 11447765, upload-time = "2026-04-24T18:17:09.755Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f9/0ae446942c846b8266059ad8a30702a35afae55f5cdc54c5adf8d7afdc27/ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20", size = 10657277, upload-time = "2026-04-24T18:17:18.591Z" }, + { url = "https://files.pythonhosted.org/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d", size = 11837758, upload-time = "2026-04-24T18:17:00.113Z" }, + { url = "https://files.pythonhosted.org/packages/c0/98/6beb4b351e472e5f4c4613f7c35a5290b8be2497e183825310c4c3a3984b/ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", size = 11120821, upload-time = "2026-04-24T18:16:57.979Z" }, ] [[package]] @@ -1836,11 +1840,11 @@ wheels = [ [[package]] name = "setuptools" -version = "82.0.0" +version = "82.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/f3/748f4d6f65d1756b9ae577f329c951cda23fb900e4de9f70900ced962085/setuptools-82.0.0.tar.gz", hash = "sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb", size = 1144893, upload-time = "2026-02-08T15:08:40.206Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl", hash = "sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0", size = 1003468, upload-time = "2026-02-08T15:08:38.723Z" }, + { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, ] [[package]] @@ -1854,11 +1858,11 @@ wheels = [ [[package]] name = "smmap" -version = "5.0.2" +version = "5.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/ea/49c993d6dfdd7338c9b1000a0f36817ed7ec84577ae2e52f890d1a4ff909/smmap-5.0.3.tar.gz", hash = "sha256:4d9debb8b99007ae47165abc08670bd74cb74b5227dda7f643eccc4e9eb5642c", size = 22506, upload-time = "2026-03-09T03:43:26.1Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" }, + { url = "https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl", hash = "sha256:c106e05d5a61449cf6ba9a1e650227ecfb141590d2a98412103ff35d89fc7b2f", size = 24390, upload-time = "2026-03-09T03:43:24.361Z" }, ] [[package]] @@ -1888,28 +1892,28 @@ wheels = [ [[package]] name = "sse-starlette" -version = "3.3.2" +version = "3.4.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "starlette" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5a/9f/c3695c2d2d4ef70072c3a06992850498b01c6bc9be531950813716b426fa/sse_starlette-3.3.2.tar.gz", hash = "sha256:678fca55a1945c734d8472a6cad186a55ab02840b4f6786f5ee8770970579dcd", size = 32326, upload-time = "2026-02-28T11:24:34.36Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/9a/f35932a8c0eb6b2287b66fa65a0321df8c84e4e355a659c1841a37c39fdb/sse_starlette-3.4.1.tar.gz", hash = "sha256:f780bebcf6c8997fe514e3bd8e8c648d8284976b391c8bed0bcb1f611632b555", size = 35127, upload-time = "2026-04-26T13:32:32.292Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/28/8cb142d3fe80c4a2d8af54ca0b003f47ce0ba920974e7990fa6e016402d1/sse_starlette-3.3.2-py3-none-any.whl", hash = "sha256:5c3ea3dad425c601236726af2f27689b74494643f57017cafcb6f8c9acfbb862", size = 14270, upload-time = "2026-02-28T11:24:32.984Z" }, + { url = "https://files.pythonhosted.org/packages/ff/07/45c21ed03d708c477367305726b89919b020a3a2a01f72aaf5ad941caf35/sse_starlette-3.4.1-py3-none-any.whl", hash = "sha256:6b43cf21f1d574d582a6e1b0cfbde1c94dc86a32a701a7168c99c4475c6bd1d0", size = 16487, upload-time = "2026-04-26T13:32:30.819Z" }, ] [[package]] name = "starlette" -version = "0.52.1" +version = "1.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/69/17425771797c36cded50b7fe44e850315d039f28b15901ab44839e70b593/starlette-1.0.0.tar.gz", hash = "sha256:6a4beaf1f81bb472fd19ea9b918b50dc3a77a6f2e190a12954b25e6ed5eea149", size = 2655289, upload-time = "2026-03-22T18:29:46.779Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c9/584bc9651441b4ba60cc4d557d8a547b5aff901af35bda3a4ee30c819b82/starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b", size = 72651, upload-time = "2026-03-22T18:29:45.111Z" }, ] [[package]] @@ -2110,19 +2114,19 @@ wheels = [ [[package]] name = "typos" -version = "1.44.0" +version = "1.45.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/db/12/6049f719f30e5066bb5059a24413cbd91f79fa9aa7d71517e4e620abdee0/typos-1.44.0.tar.gz", hash = "sha256:8e1046d02f2fcea6df907b34b90556e4acafd9b287ad70ab27d2c06489f5df43", size = 1817247, upload-time = "2026-02-27T16:37:09.584Z" } +sdist = { url = "https://files.pythonhosted.org/packages/69/c2/0cd9200d030f8e3c71ac30bc0ee86736d9d7d0a9b02c1b050f40138c19c0/typos-1.45.1.tar.gz", hash = "sha256:a1ac7ab02e74d4c4a2f8525b1529e1ce6261051df3229701836175fb91bb0583", size = 1820481, upload-time = "2026-04-13T15:03:01.549Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/d1/db308b654e8ecb41df0b26610fb7970436effb318fd75dd0187f67c73e2c/typos-1.44.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:bf4241c469c14b7213a5ce2cf2a0692be21641be1a247dc126bec3f982195d6c", size = 3481848, upload-time = "2026-02-27T16:36:55.129Z" }, - { url = "https://files.pythonhosted.org/packages/67/9a/c04f8993bf96ee00921693f59d789c957263ad83d87f2b9cf550315105e4/typos-1.44.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:28332344a2939f20707ad0265cc10da5b930015c75920725cdd6db9ab9bdb18b", size = 3380731, upload-time = "2026-02-27T16:36:57.1Z" }, - { url = "https://files.pythonhosted.org/packages/f3/81/c6938251220335960d0322df26b6cb8b6a920b399cf71f8e00905946c582/typos-1.44.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95b91c20c914a0f728d3d0ad21e0fee3fd0bd0b7514d66b1d42f6047ebbb8646", size = 8191469, upload-time = "2026-02-27T16:36:58.887Z" }, - { url = "https://files.pythonhosted.org/packages/30/5b/4806b29068d85bd11230b20fa412b6159e70ee9cf0e153fdcafb71d9f468/typos-1.44.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd7a3d55466896336230679bf87484074b5912f555a8c8cf6ea88c084bf5b28d", size = 7336904, upload-time = "2026-02-27T16:37:00.535Z" }, - { url = "https://files.pythonhosted.org/packages/a3/91/54d735f2e792a20aedad46d33e0ec848afc4b0faa401dd356a656e24ef89/typos-1.44.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d78c219d802b124a9b90d7327665e6de20e3aba5f4ff31fb76e25c338a9475d", size = 7711695, upload-time = "2026-02-27T16:37:02.233Z" }, - { url = "https://files.pythonhosted.org/packages/03/2e/10baf07d7af76915dee47139e959c40fdbd821ad4c5530b055432da98cc3/typos-1.44.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b3796090bdf1531cc3abd34a597d0ca5aef5c40841e49f9aeda58f1ab950e060", size = 7065163, upload-time = "2026-02-27T16:37:03.691Z" }, - { url = "https://files.pythonhosted.org/packages/e3/b0/e9eb53fdd512971fbf4a60d70402365242b99063fc92afff0294aab00791/typos-1.44.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c6aea9c04fb9759efe94bddf8af4156707bc82de3b9b58118530f2c9818352c9", size = 8145921, upload-time = "2026-02-27T16:37:05.191Z" }, - { url = "https://files.pythonhosted.org/packages/e8/80/256939188c954219a9541cf7f7aa91212bcfb33efd61db2f4cb5ccc43376/typos-1.44.0-py3-none-win32.whl", hash = "sha256:4af31b78e38e9720be009b725334108a3c4684bfc820ca8309f7ce54d72c303d", size = 3138754, upload-time = "2026-02-27T16:37:06.699Z" }, - { url = "https://files.pythonhosted.org/packages/98/53/cc43bbfd5e003ebdba987e352b1f3f7fc069be5c61ee472983e689661a79/typos-1.44.0-py3-none-win_amd64.whl", hash = "sha256:3624055a8f04d9c40faf20ff0fcce9dbf4f8e2987c39680358901cc8634228c0", size = 3318795, upload-time = "2026-02-27T16:37:08.196Z" }, + { url = "https://files.pythonhosted.org/packages/6d/49/e0e0614d635f4847de1c3464ee068be579dfe14a81dd9051fa6fc3653305/typos-1.45.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f3cd3d7e7e35f971e04974c7b34563dc1efb101841be3a39fec36c51f3d6ca2d", size = 3480310, upload-time = "2026-04-13T15:02:43.19Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f5/b0fc73ab073eb844e888cf47c8fc1fab3b59eaf8becbb901b58e46ded7e2/typos-1.45.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:be6f26c580915e63df107f88bc766f131efe5f7d01d41c7bad83e6f9e5fe42be", size = 3381483, upload-time = "2026-04-13T15:02:45.415Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e1/1b1c8ff64d61143206e700fe4ae6732749053e44d10a33d9da1eceecbadc/typos-1.45.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cd6a6ccbb1fc4fb8f0d9fee0201642d7a7560bd1661ebbefb9eac2da1ae4a5c", size = 8247207, upload-time = "2026-04-13T15:02:47.658Z" }, + { url = "https://files.pythonhosted.org/packages/8b/6b/79ccb79cab37c04a10759709042476b8534f3f2f6a89180f67821f396482/typos-1.45.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d33c7750a29524dff020a17f356ed079227f36f43ec57f193e9681606a35749b", size = 7361395, upload-time = "2026-04-13T15:02:49.564Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4c/b97fbabf0413edda31e1e830befb267f934e990a417a11e1f6f6b2e1235e/typos-1.45.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:745b0584eeead4593858671113fceed3c28b8ca67bdc7a517120127aa509c6a6", size = 7757620, upload-time = "2026-04-13T15:02:51.664Z" }, + { url = "https://files.pythonhosted.org/packages/69/f8/1b757c9b83750100b6c2f09fb6d84d4521ca158c74d9a437495329fca58e/typos-1.45.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e962d414fb92ad31dc4c930fc5d07ac9e4b55fdd4f42688468040fc5649d92da", size = 7111197, upload-time = "2026-04-13T15:02:53.831Z" }, + { url = "https://files.pythonhosted.org/packages/63/76/65c1d4248d9cb1b12fb4bad81b829bd5cc3da7564ffe0a794fac3126f1f4/typos-1.45.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f39afdfcc2d159705f3ffb11162e13e8affd994d07836738c8d2a592194604ab", size = 8180810, upload-time = "2026-04-13T15:02:55.901Z" }, + { url = "https://files.pythonhosted.org/packages/a2/b5/65b3e0509c3d698faf1a965a9afcfff3ebff50c850e789891945735f8066/typos-1.45.1-py3-none-win32.whl", hash = "sha256:212fdbb7b90d40522fe77efb69c15f7063c146812df01d5605e5d7816a3f37d3", size = 3135956, upload-time = "2026-04-13T15:02:57.934Z" }, + { url = "https://files.pythonhosted.org/packages/ea/50/a9ec45215912d8e8600ba903b09382144071b450ad2f7adebc63e7699b99/typos-1.45.1-py3-none-win_amd64.whl", hash = "sha256:67a56bd1f06184f3761883f4f75dd3cc196f939180de595d0980164d4a19d363", size = 3318717, upload-time = "2026-04-13T15:02:59.829Z" }, ] [[package]] @@ -2145,20 +2149,20 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.41.0" +version = "0.46.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/32/ce/eeb58ae4ac36fe09e3842eb02e0eb676bf2c53ae062b98f1b2531673efdd/uvicorn-0.41.0.tar.gz", hash = "sha256:09d11cf7008da33113824ee5a1c6422d89fbc2ff476540d69a34c87fab8b571a", size = 82633, upload-time = "2026-02-16T23:07:24.1Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/93/041fca8274050e40e6791f267d82e0e2e27dd165627bd640d3e0e378d877/uvicorn-0.46.0.tar.gz", hash = "sha256:fb9da0926999cc6cb22dc7cd71a94a632f078e6ae47ff683c5c420750fb7413d", size = 88758, upload-time = "2026-04-23T07:16:00.151Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/e4/d04a086285c20886c0daad0e026f250869201013d18f81d9ff5eada73a88/uvicorn-0.41.0-py3-none-any.whl", hash = "sha256:29e35b1d2c36a04b9e180d4007ede3bcb32a85fbdfd6c6aeb3f26839de088187", size = 68783, upload-time = "2026-02-16T23:07:22.357Z" }, + { url = "https://files.pythonhosted.org/packages/31/a3/5b1562db76a5a488274b2332a97199b32d0442aca0ed193697fd47786316/uvicorn-0.46.0-py3-none-any.whl", hash = "sha256:bbebbcbed972d162afca128605223022bedd345b7bc7855ce66deb31487a9048", size = 70926, upload-time = "2026-04-23T07:15:58.355Z" }, ] [[package]] name = "virtualenv" -version = "21.1.0" +version = "21.2.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -2166,18 +2170,18 @@ dependencies = [ { name = "platformdirs" }, { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2f/c9/18d4b36606d6091844daa3bd93cf7dc78e6f5da21d9f21d06c221104b684/virtualenv-21.1.0.tar.gz", hash = "sha256:1990a0188c8f16b6b9cf65c9183049007375b26aad415514d377ccacf1e4fb44", size = 5840471, upload-time = "2026-02-27T08:49:29.702Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/98/3a7e644e19cb26133488caff231be390579860bbbb3da35913c49a1d0a46/virtualenv-21.2.4.tar.gz", hash = "sha256:b294ef68192638004d72524ce7ef303e9d0cf5a44c95ce2e54a7500a6381cada", size = 5850742, upload-time = "2026-04-14T22:15:31.438Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/55/896b06bf93a49bec0f4ae2a6f1ed12bd05c8860744ac3a70eda041064e4d/virtualenv-21.1.0-py3-none-any.whl", hash = "sha256:164f5e14c5587d170cf98e60378eb91ea35bf037be313811905d3a24ea33cc07", size = 5825072, upload-time = "2026-02-27T08:49:27.516Z" }, + { url = "https://files.pythonhosted.org/packages/27/8d/edd0bd910ff803c308ee9a6b7778621af0d10252219ad9f19ef4d4982a61/virtualenv-21.2.4-py3-none-any.whl", hash = "sha256:29d21e941795206138d0f22f4e45ff7050e5da6c6472299fb7103318763861ac", size = 5831232, upload-time = "2026-04-14T22:15:29.342Z" }, ] [[package]] name = "vulture" -version = "2.15" +version = "2.16" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/59/c6/4f147b621b4c0899eb1770f98113334bb706ebd251ac2be979316b1985fa/vulture-2.15.tar.gz", hash = "sha256:f9d8b4ce29c69950d323f21dceab4a4d6c694403dffbed7713c4691057e561fe", size = 52438, upload-time = "2026-03-04T21:41:39.096Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/3e/4d08c5903b2c0c70cad583c170cc4a663fc6a61e2ad00b711fcda61358cd/vulture-2.16.tar.gz", hash = "sha256:f8d9f6e2af03011664a3c6c240c9765b3f392917d3135fddca6d6a68d359f717", size = 52680, upload-time = "2026-03-25T14:41:27.141Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/f3/07cf122e145bc6df976030e9935123124c3fcb5044cf407b5e71e85821b4/vulture-2.15-py3-none-any.whl", hash = "sha256:a3d8ebef918694326620eb128fa783486c8d285b23381c2b457d864ac056ef8d", size = 26895, upload-time = "2026-03-04T21:41:39.878Z" }, + { url = "https://files.pythonhosted.org/packages/f5/be/f935130312330614811dae2ea9df3f395f6d63889eb6c2e68c14507152ee/vulture-2.16-py3-none-any.whl", hash = "sha256:6e0f1c312cef1c87856957e5c2ca9608834a7c794c2180477f30bf0e4cc58eee", size = 26993, upload-time = "2026-03-25T14:41:26.21Z" }, ] [[package]] @@ -2297,11 +2301,11 @@ wheels = [ [[package]] name = "zipp" -version = "3.23.0" +version = "3.23.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +sdist = { url = "https://files.pythonhosted.org/packages/30/21/093488dfc7cc8964ded15ab726fad40f25fd3d788fd741cc1c5a17d78ee8/zipp-3.23.1.tar.gz", hash = "sha256:32120e378d32cd9714ad503c1d024619063ec28aad2248dc6672ad13edfa5110", size = 25965, upload-time = "2026-04-13T23:21:46.6Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, + { url = "https://files.pythonhosted.org/packages/08/8a/0861bec20485572fbddf3dfba2910e38fe249796cb73ecdeb74e07eeb8d3/zipp-3.23.1-py3-none-any.whl", hash = "sha256:0b3596c50a5c700c9cb40ba8d86d9f2cc4807e9bedb06bcdf7fac85633e444dc", size = 10378, upload-time = "2026-04-13T23:21:45.386Z" }, ] [[package]] diff --git a/vibe/__init__.py b/vibe/__init__.py index eef1571..a916b91 100644 --- a/vibe/__init__.py +++ b/vibe/__init__.py @@ -3,4 +3,4 @@ from __future__ import annotations from pathlib import Path VIBE_ROOT = Path(__file__).parent -__version__ = "2.9.3" +__version__ = "2.9.4" diff --git a/vibe/acp/acp_agent_loop.py b/vibe/acp/acp_agent_loop.py index 3a02fd8..a881c8b 100644 --- a/vibe/acp/acp_agent_loop.py +++ b/vibe/acp/acp_agent_loop.py @@ -52,6 +52,7 @@ from acp.schema import ( SessionConfigOptionSelect, SessionForkCapabilities, SessionInfo, + SessionInfoUpdate, SessionListCapabilities, SetSessionConfigOptionResponse, SseMcpServer, @@ -122,6 +123,10 @@ from vibe.core.proxy_setup import ( set_proxy_var, unset_proxy_var, ) +from vibe.core.session.saved_sessions import ( + update_saved_session_title, + update_saved_session_title_at_path, +) from vibe.core.session.session_loader import SessionLoader from vibe.core.skills.manager import SkillManager from vibe.core.telemetry.build_metadata import build_entrypoint_metadata @@ -159,6 +164,15 @@ class ForkSessionParams(BaseModel): message_id: str | None = Field(default=None, alias="messageId") +class SessionSetTitleRequest(BaseModel): + model_config = ConfigDict(extra="ignore", str_strip_whitespace=True) + + session_id: str = Field( + validation_alias=AliasChoices("session_id", "sessionId"), min_length=1 + ) + title: str = Field(min_length=1) + + class TelemetrySendNotification(BaseModel): model_config = ConfigDict(extra="ignore") @@ -167,7 +181,20 @@ class TelemetrySendNotification(BaseModel): session_id: str = Field(validation_alias=AliasChoices("session_id", "sessionId")) -_EVENT_DISPATCHERS: dict[str, Callable[[TelemetryClient, dict[str, Any]], None]] = {} +def _dispatch_at_mention_inserted( + client: TelemetryClient, properties: dict[str, Any] +) -> None: + client.send_at_mention_inserted( + nb_mentions=properties.get("nb_mentions", 0), + context_types=properties.get("context_types", {}), + file_extensions=properties.get("file_extensions"), + message_id=properties.get("message_id"), + ) + + +_EVENT_DISPATCHERS: dict[str, Callable[[TelemetryClient, dict[str, Any]], None]] = { + "vibe.at_mention_inserted": _dispatch_at_mention_inserted +} def _resolved_user_message_id(client_message_id: str | None) -> str: @@ -301,9 +328,20 @@ class VibeAcpAgentLoop(AcpAgent): agent_loop.set_approval_callback(self._create_approval_callback(session.id)) session.spawn(self._send_available_commands(session)) + session.spawn(self._warm_up_agent_loop(agent_loop)) return session + async def _warm_up_agent_loop(self, agent_loop: AgentLoop) -> None: + """Proactively await deferred init so `vibe.ready` telemetry is emitted + without waiting for the user's first prompt. Errors are swallowed here + and will resurface on the first `act()` call via `requires_init`. + """ + try: + await agent_loop.wait_until_ready() + except Exception: + pass + def _create_agent_loop( self, config: VibeConfig, agent_name: str, hook_config_result: Any = None ) -> AgentLoop: @@ -398,6 +436,11 @@ class VibeAcpAgentLoop(AcpAgent): case ToolOption.ALLOW_ALWAYS: session.agent_loop.approve_always(tool_name, required_permissions) return (ApprovalResponse.YES, None) + case ToolOption.ALLOW_ALWAYS_PERMANENT: + session.agent_loop.approve_always( + tool_name, required_permissions, save_permanently=True + ) + return (ApprovalResponse.YES, None) case ToolOption.REJECT_ONCE: session.agent_loop.telemetry_client.send_user_cancelled_action( "reject_approval" @@ -454,6 +497,29 @@ class VibeAcpAgentLoop(AcpAgent): raise SessionNotFoundError(session_id) return self.sessions[session_id] + def _find_acp_session_by_vibe_session_id( + self, session_id: str + ) -> AcpSessionLoop | None: + for candidate in self.sessions.values(): + if candidate.agent_loop.session_id == session_id: + return candidate + + return None + + def _load_session_logging_config(self) -> SessionLoggingConfig: + try: + return VibeConfig.load().session_logging + except MissingAPIKeyError: + try: + persisted_config = VibeConfig.get_persisted_config() + return SessionLoggingConfig.model_validate( + persisted_config.get("session_logging", {}) + ) + except Exception as e: + raise ConfigurationError(str(e)) from e + except Exception as e: + raise ConfigurationError(str(e)) from e + def _build_usage(self, session: AcpSessionLoop) -> Usage: stats = session.agent_loop.stats return Usage( @@ -1008,9 +1074,97 @@ class VibeAcpAgentLoop(AcpAgent): ) -> ResumeSessionResponse: raise NotImplementedMethodError("resume_session") + async def _emit_session_info_update( + self, session_id: str, *, title: str, updated_at: str | None + ) -> None: + update_kwargs: dict[str, Any] = { + "session_update": "session_info_update", + "title": title, + } + if updated_at is not None: + update_kwargs["updated_at"] = updated_at + + await self.client.session_update( + session_id=session_id, update=SessionInfoUpdate(**update_kwargs) + ) + + async def _persist_live_session_title( + self, session: AcpSessionLoop, title: str + ) -> dict[str, Any] | None: + logger = session.agent_loop.session_logger + if not logger.enabled or logger.session_dir is None: + return None + if not logger.metadata_filepath.exists(): + return None + + try: + return await update_saved_session_title_at_path(logger.session_dir, title) + except ValueError as exc: + raise InternalError( + f"Failed to persist title update for session {logger.session_id}: {exc}" + ) from exc + + def _set_live_session_title(self, session: AcpSessionLoop, title: str) -> None: + try: + session.agent_loop.session_logger.set_title(title) + except ValueError as exc: + raise InvalidRequestError( + f"Invalid ACP session title request: {exc}" + ) from exc + + async def _handle_session_set_title(self, params: dict[str, Any]) -> dict[str, Any]: + try: + request = SessionSetTitleRequest.model_validate(params) + except ValidationError as exc: + raise InvalidRequestError( + f"Invalid ACP session title request: {exc}" + ) from exc + + live_session = self.sessions.get( + request.session_id + ) or self._find_acp_session_by_vibe_session_id(request.session_id) + if live_session is None: + try: + metadata = await update_saved_session_title( + request.session_id, + request.title, + self._load_session_logging_config(), + ) + except ValueError as exc: + raise SessionNotFoundError(request.session_id) from exc + + await self._emit_session_info_update( + request.session_id, + title=request.title, + updated_at=metadata.get("end_time"), + ) + return {} + + persisted_metadata = await self._persist_live_session_title( + live_session, request.title + ) + self._set_live_session_title(live_session, request.title) + updated_at = ( + persisted_metadata.get("end_time") + if persisted_metadata is not None + else ( + live_session.agent_loop.session_logger.session_metadata.end_time + if live_session.agent_loop.session_logger.session_metadata is not None + else None + ) + ) + + await self._emit_session_info_update( + live_session.id, title=request.title, updated_at=updated_at + ) + return {} + @override async def ext_method(self, method: str, params: dict) -> dict: - raise NotImplementedMethodError("ext_method") + if method == "session/set_title": + return await self._handle_session_set_title(params) + + raise NotImplementedMethodError(method) @override async def ext_notification(self, method: str, params: dict) -> None: diff --git a/vibe/acp/utils.py b/vibe/acp/utils.py index 29544c1..57884ee 100644 --- a/vibe/acp/utils.py +++ b/vibe/acp/utils.py @@ -1,7 +1,7 @@ from __future__ import annotations from enum import StrEnum -from typing import TYPE_CHECKING, Literal, cast +from typing import TYPE_CHECKING from acp.schema import ( AgentMessageChunk, @@ -9,6 +9,7 @@ from acp.schema import ( ContentToolCallContent, ModelInfo, PermissionOption, + PermissionOptionKind, SessionConfigOptionSelect, SessionConfigSelectOption, SessionMode, @@ -34,25 +35,31 @@ if TYPE_CHECKING: class ToolOption(StrEnum): ALLOW_ONCE = "allow_once" ALLOW_ALWAYS = "allow_always" + ALLOW_ALWAYS_PERMANENT = "allow_always_permanent" REJECT_ONCE = "reject_once" REJECT_ALWAYS = "reject_always" +_KIND_ALLOW_ONCE: PermissionOptionKind = "allow_once" +_KIND_ALLOW_ALWAYS: PermissionOptionKind = "allow_always" +_KIND_REJECT_ONCE: PermissionOptionKind = "reject_once" + TOOL_OPTIONS = [ PermissionOption( - option_id=ToolOption.ALLOW_ONCE, - name="Allow once", - kind=cast(Literal["allow_once"], ToolOption.ALLOW_ONCE), + option_id=ToolOption.ALLOW_ONCE, name="Allow once", kind=_KIND_ALLOW_ONCE ), PermissionOption( option_id=ToolOption.ALLOW_ALWAYS, - name="Allow for this session", - kind=cast(Literal["allow_always"], ToolOption.ALLOW_ALWAYS), + name="Allow for remainder of this session", + kind=_KIND_ALLOW_ALWAYS, ), PermissionOption( - option_id=ToolOption.REJECT_ONCE, - name="Reject once", - kind=cast(Literal["reject_once"], ToolOption.REJECT_ONCE), + option_id=ToolOption.ALLOW_ALWAYS_PERMANENT, + name="Always allow", + kind=_KIND_ALLOW_ALWAYS, + ), + PermissionOption( + option_id=ToolOption.REJECT_ONCE, name="Deny", kind=_KIND_REJECT_ONCE ), ] @@ -64,7 +71,6 @@ def build_permission_options( if not required_permissions: return TOOL_OPTIONS - labels = ", ".join(rp.label for rp in required_permissions) permissions_meta = [ { "scope": rp.scope, @@ -77,20 +83,22 @@ def build_permission_options( return [ PermissionOption( - option_id=ToolOption.ALLOW_ONCE, - name="Allow once", - kind=cast(Literal["allow_once"], ToolOption.ALLOW_ONCE), + option_id=ToolOption.ALLOW_ONCE, name="Allow once", kind=_KIND_ALLOW_ONCE ), PermissionOption( option_id=ToolOption.ALLOW_ALWAYS, - name=f"Allow for this session: {labels}", - kind=cast(Literal["allow_always"], ToolOption.ALLOW_ALWAYS), + name="Allow for remainder of this session", + kind=_KIND_ALLOW_ALWAYS, field_meta={"required_permissions": permissions_meta}, ), PermissionOption( - option_id=ToolOption.REJECT_ONCE, - name="Reject once", - kind=cast(Literal["reject_once"], ToolOption.REJECT_ONCE), + option_id=ToolOption.ALLOW_ALWAYS_PERMANENT, + name="Always allow", + kind=_KIND_ALLOW_ALWAYS, + field_meta={"required_permissions": permissions_meta}, + ), + PermissionOption( + option_id=ToolOption.REJECT_ONCE, name="Deny", kind=_KIND_REJECT_ONCE ), ] diff --git a/vibe/cli/cli.py b/vibe/cli/cli.py index 0241d59..2600850 100644 --- a/vibe/cli/cli.py +++ b/vibe/cli/cli.py @@ -265,6 +265,7 @@ def run_cli(args: argparse.Namespace) -> None: initial_prompt=args.initial_prompt or stdin_prompt, teleport_on_start=args.teleport, show_resume_picker=args.resume is True, + is_resuming_session=loaded_session is not None, ), ) diff --git a/vibe/cli/commands.py b/vibe/cli/commands.py index 9478506..ca43689 100644 --- a/vibe/cli/commands.py +++ b/vibe/cli/commands.py @@ -128,6 +128,11 @@ class CommandRegistry: description="Browse and resume past sessions", handler="_show_session_picker", ), + "rename": Command( + aliases=frozenset(["/rename"]), + description="Rename the current session", + handler="_rename_session", + ), "mcp": Command( aliases=frozenset(["/mcp", "/connectors"]), description=( @@ -225,7 +230,7 @@ class CommandRegistry: "- `Ctrl+C` Quit (or clear input if text present)", "- `Ctrl+G` Edit input in external editor", "- `Ctrl+O` Toggle tool output view", - "- `Shift+Tab` Toggle auto-approve mode", + "- `Shift+Tab` Cycle through agents (default, plan, ...)", f"- `{ALT_KEY}+↑↓` / `Ctrl+P/N` Rewind to previous/next message", "", "### Special Features", diff --git a/vibe/cli/textual_ui/app.py b/vibe/cli/textual_ui/app.py index a6d9c06..cb5b812 100644 --- a/vibe/cli/textual_ui/app.py +++ b/vibe/cli/textual_ui/app.py @@ -1,6 +1,7 @@ from __future__ import annotations import asyncio +import codecs from collections.abc import AsyncGenerator from contextlib import aclosing from dataclasses import dataclass @@ -9,9 +10,9 @@ import gc import os from pathlib import Path import signal -import subprocess import time from typing import Any, ClassVar, assert_never, cast +from uuid import uuid4 from weakref import WeakKeyDictionary import webbrowser @@ -121,6 +122,7 @@ from vibe.core.agent_loop import AgentLoop, TeleportError from vibe.core.agents import AgentProfile from vibe.core.audio_player.audio_player import AudioPlayer from vibe.core.audio_recorder import AudioRecorder +from vibe.core.autocompletion.path_prompt import build_path_prompt_payload from vibe.core.autocompletion.path_prompt_adapter import render_path_prompt from vibe.core.config import VibeConfig from vibe.core.data_retention import DATA_RETENTION_MESSAGE @@ -135,6 +137,7 @@ from vibe.core.session.resume_sessions import ( list_remote_resume_sessions, short_session_id, ) +from vibe.core.session.saved_sessions import update_saved_session_title_at_path from vibe.core.session.session_loader import SessionLoader from vibe.core.skills.manager import SkillManager from vibe.core.teleport.types import ( @@ -294,6 +297,7 @@ class StartupOptions: initial_prompt: str | None = None teleport_on_start: bool = False show_resume_picker: bool = False + is_resuming_session: bool = False class VibeApp(App): # noqa: PLR0904 @@ -349,6 +353,7 @@ class VibeApp(App): # noqa: PLR0904 self._agent_running = False self._interrupt_requested = False self._agent_task: asyncio.Task | None = None + self._bash_task: asyncio.Task | None = None self._remote_manager = RemoteSessionManager() self._loading_widget: LoadingWidget | None = None @@ -374,12 +379,7 @@ class VibeApp(App): # noqa: PLR0904 self._update_cache_repository = update_cache_repository self._current_version = current_version self._plan_offer_gateway = plan_offer_gateway - opts = startup or StartupOptions() - self._initial_prompt = opts.initial_prompt - self._teleport_on_start = ( - opts.teleport_on_start and self.agent_loop.base_config.vibe_code_enabled - ) - self._show_resume_picker = opts.show_resume_picker + self._configure_startup_options(startup) self._last_escape_time: float | None = None self._quit_manager = QuitManager(self) self._banner: Banner | None = None @@ -399,6 +399,15 @@ class VibeApp(App): # noqa: PLR0904 self._fatal_init_error = False self.commands = self._build_command_registry() + def _configure_startup_options(self, startup: StartupOptions | None) -> None: + opts = startup or StartupOptions() + self._initial_prompt = opts.initial_prompt + self._teleport_on_start = ( + opts.teleport_on_start and self.agent_loop.base_config.vibe_code_enabled + ) + self._show_resume_picker = opts.show_resume_picker + self._is_resuming_session = opts.is_resuming_session + @property def config(self) -> VibeConfig: return self.agent_loop.config @@ -497,7 +506,8 @@ class VibeApp(App): # noqa: PLR0904 await self._resume_history_from_messages() await self._check_and_show_whats_new() self._schedule_update_notification() - self.agent_loop.emit_new_session_telemetry() + if not self._is_resuming_session: + self.agent_loop.emit_new_session_telemetry() self.call_after_refresh(self._refresh_banner) self._show_hook_config_issues_once() @@ -586,11 +596,15 @@ class VibeApp(App): # noqa: PLR0904 input_widget = self.query_one(ChatInputContainer) input_widget.value = "" + if self._bash_task and not self._bash_task.done(): + self._bash_task.cancel() + self._bash_task = None + if self._agent_running: await self._interrupt_agent_loop() if value.startswith("!"): - await self._handle_bash_command(value[1:]) + self._bash_task = asyncio.create_task(self._handle_bash_command(value[1:])) return if value.startswith("&") and self.commands.has_command("teleport"): @@ -619,6 +633,16 @@ class VibeApp(App): # noqa: PLR0904 if self._pending_approval and not self._pending_approval.done(): self._pending_approval.set_result((ApprovalResponse.YES, None)) + async def on_approval_app_approval_granted_always_permanent( + self, message: ApprovalApp.ApprovalGrantedAlwaysPermanent + ) -> None: + self.agent_loop.approve_always( + message.tool_name, message.required_permissions, save_permanently=True + ) + + if self._pending_approval and not self._pending_approval.done(): + self._pending_approval.set_result((ApprovalResponse.YES, None)) + async def on_approval_app_approval_rejected( self, message: ApprovalApp.ApprovalRejected ) -> None: @@ -905,6 +929,40 @@ class VibeApp(App): # noqa: PLR0904 await self._handle_user_message(prompt) return True + @staticmethod + async def _bash_read_stream( + stream: asyncio.StreamReader | None, + parts: list[str], + bash_msg: BashOutputMessage, + ) -> None: + if not stream: + return + decoder = codecs.getincrementaldecoder("utf-8")(errors="replace") + while True: + chunk = await stream.read(4096) + if not chunk: + break + text = decoder.decode(chunk) + if not text: + continue + parts.append(text) + await bash_msg.append_output(text) + final_text = decoder.decode(b"", final=True) + if not final_text: + return + parts.append(final_text) + await bash_msg.append_output(final_text) + + @staticmethod + async def _kill_running_process(proc: asyncio.subprocess.Process | None) -> None: + if proc is None or proc.returncode is not None: + return + try: + proc.kill() + except ProcessLookupError: + pass + await proc.wait() + async def _handle_bash_command(self, command: str) -> None: if not command: await self._mount_and_scroll( @@ -914,21 +972,52 @@ class VibeApp(App): # noqa: PLR0904 ) return + bash_msg = BashOutputMessage(command, str(Path.cwd()), pending=True) + await self._mount_and_scroll(bash_msg) + + proc: asyncio.subprocess.Process | None = None + stdout_parts: list[str] = [] + stderr_parts: list[str] = [] try: - result = subprocess.run( - command, shell=True, capture_output=True, text=False, timeout=30 - ) - stdout = ( - result.stdout.decode("utf-8", errors="replace") if result.stdout else "" - ) - stderr = ( - result.stderr.decode("utf-8", errors="replace") if result.stderr else "" - ) - output = stdout or stderr or "(no output)" - exit_code = result.returncode - await self._mount_and_scroll( - BashOutputMessage(command, str(Path.cwd()), output, exit_code) + proc = await asyncio.create_subprocess_shell( + command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE ) + + try: + await asyncio.wait_for( + asyncio.gather( + self._bash_read_stream(proc.stdout, stdout_parts, bash_msg), + self._bash_read_stream(proc.stderr, stderr_parts, bash_msg), + proc.wait(), + ), + timeout=30, + ) + except TimeoutError: + await self._kill_running_process(proc) + stdout = "".join(stdout_parts) + stderr = "".join(stderr_parts) + await bash_msg.finish(1) + await self._mount_and_scroll( + ErrorMessage( + "Command timed out after 30 seconds", + collapsed=self._tools_collapsed, + ) + ) + await self.agent_loop.inject_user_context( + self._format_manual_command_context( + command=command, + cwd=str(Path.cwd()), + stdout=stdout, + stderr=stderr, + status="timed out after 30 seconds", + ) + ) + return + + stdout = "".join(stdout_parts) + stderr = "".join(stderr_parts) + exit_code = proc.returncode or 0 + await bash_msg.finish(exit_code) await self.agent_loop.inject_user_context( self._format_manual_command_context( command=command, @@ -938,33 +1027,25 @@ class VibeApp(App): # noqa: PLR0904 stderr=stderr, ) ) - except subprocess.TimeoutExpired as error: - stdout = ( - error.stdout.decode("utf-8", errors="replace") - if isinstance(error.stdout, bytes) - else (error.stdout or "") - ) - stderr = ( - error.stderr.decode("utf-8", errors="replace") - if isinstance(error.stderr, bytes) - else (error.stderr or "") - ) - await self._mount_and_scroll( - ErrorMessage( - "Command timed out after 30 seconds", - collapsed=self._tools_collapsed, - ) - ) + except asyncio.CancelledError: + await self._kill_running_process(proc) + await bash_msg.finish(1, interrupted=True) + stdout = "".join(stdout_parts) + stderr = "".join(stderr_parts) await self.agent_loop.inject_user_context( self._format_manual_command_context( command=command, cwd=str(Path.cwd()), stdout=stdout, stderr=stderr, - status="timed out after 30 seconds", + status="interrupted by user", ) ) except Exception as e: + await self._kill_running_process(proc) + await bash_msg.finish(1) + stdout = "".join(stdout_parts) + stderr = "".join(stderr_parts) await self._mount_and_scroll( ErrorMessage(f"Command failed: {e}", collapsed=self._tools_collapsed) ) @@ -972,6 +1053,8 @@ class VibeApp(App): # noqa: PLR0904 self._format_manual_command_context( command=command, cwd=str(Path.cwd()), + stdout=stdout, + stderr=stderr, status=f"failed before completion: {e}", ) ) @@ -1230,10 +1313,30 @@ class VibeApp(App): # noqa: PLR0904 try: await self._handle_agent_loop_init() await self._ensure_loading_widget() + message_id = str(uuid4()) + prompt_payload = build_path_prompt_payload(prompt, base_dir=Path.cwd()) + if prompt_payload.all_resources: + context_types: dict[str, int] = {} + for r in prompt_payload.all_resources: + context_types[r.kind] = context_types.get(r.kind, 0) + 1 + file_ext_counts: dict[str, int] = {} + for r in prompt_payload.all_resources: + if r.kind == "file" and r.path.suffix: + file_ext_counts[r.path.suffix] = ( + file_ext_counts.get(r.path.suffix, 0) + 1 + ) + self.agent_loop.telemetry_client.send_at_mention_inserted( + nb_mentions=len(prompt_payload.all_resources), + context_types=context_types, + file_extensions=file_ext_counts or None, + message_id=message_id, + ) rendered_prompt = render_path_prompt(prompt, base_dir=Path.cwd()) self._narrator_manager.cancel() self._narrator_manager.on_turn_start(rendered_prompt) - async with aclosing(self.agent_loop.act(rendered_prompt)) as events: + async with aclosing( + self.agent_loop.act(rendered_prompt, client_message_id=message_id) + ) as events: await self._handle_agent_loop_events(events) except asyncio.CancelledError: await self._handle_turn_error() @@ -1566,6 +1669,53 @@ class VibeApp(App): # noqa: PLR0904 async def _show_data_retention(self, **kwargs: Any) -> None: await self._mount_and_scroll(UserCommandMessage(DATA_RETENTION_MESSAGE)) + async def _rename_local_session(self, title: str) -> str: + session_logger = self.agent_loop.session_logger + if not session_logger.enabled or session_logger.session_metadata is None: + raise ValueError("Session logging is disabled in configuration.") + + if ( + session_logger.session_dir is not None + and session_logger.metadata_filepath.exists() + ): + await update_saved_session_title_at_path(session_logger.session_dir, title) + + session_logger.set_title(title) + renamed_title = session_logger.session_metadata.title + assert renamed_title is not None + return renamed_title + + async def _rename_session(self, cmd_args: str = "", **kwargs: Any) -> None: + if self._remote_manager.is_active: + await self._mount_and_scroll( + ErrorMessage( + "Renaming is only supported for local sessions.", + collapsed=self._tools_collapsed, + ) + ) + return + + title = cmd_args.strip() + if not title: + await self._mount_and_scroll( + ErrorMessage("Usage: /rename <title>", collapsed=self._tools_collapsed) + ) + return + + try: + renamed_title = await self._rename_local_session(title) + except Exception as e: + await self._mount_and_scroll( + ErrorMessage( + f"Failed to rename session: {e}", collapsed=self._tools_collapsed + ) + ) + return + + await self._mount_and_scroll( + UserCommandMessage(f'Session renamed to "{renamed_title}".') + ) + async def _show_session_picker(self, **kwargs: Any) -> None: cwd = str(Path.cwd()) local_sessions = ( @@ -1608,7 +1758,8 @@ class VibeApp(App): # noqa: PLR0904 sessions = sorted(raw_sessions, key=lambda s: s.end_time or "", reverse=True) latest_messages = { - s.option_id: SessionLoader.get_first_user_message( + s.option_id: s.title + or SessionLoader.get_first_user_message( s.session_id, self.config.session_logging ) for s in sessions @@ -2598,6 +2749,8 @@ class VibeApp(App): # noqa: PLR0904 def _force_quit(self) -> None: if self._agent_task and not self._agent_task.done(): self._agent_task.cancel() + if self._bash_task and not self._bash_task.done(): + self._bash_task.cancel() self._remote_manager.cancel_stream_task() self._log_reader.shutdown() diff --git a/vibe/cli/textual_ui/app.tcss b/vibe/cli/textual_ui/app.tcss index 22b9fba..6a2b5d6 100644 --- a/vibe/cli/textual_ui/app.tcss +++ b/vibe/cli/textual_ui/app.tcss @@ -413,6 +413,10 @@ Markdown { &.bash-error { color: ansi_red; } + + &.bash-interrupted { + color: ansi_yellow; + } } .bash-command { @@ -786,7 +790,7 @@ StatusMessage { .approval-title { height: auto; text-style: bold; - color: ansi_yellow; + color: ansi_bright_yellow; } .approval-tool-info-container { @@ -808,6 +812,11 @@ StatusMessage { .approval-option { height: auto; color: ansi_default; + margin-bottom: 1; + + &:last-of-type { + margin-bottom: 0; + } } .approval-cursor-selected { @@ -817,14 +826,14 @@ StatusMessage { } &.approval-option-no { - color: ansi_red; + color: ansi_bright_red; text-style: bold; } } .approval-option-selected { &.approval-option-yes { - color: ansi_green; + color: ansi_white; } &.approval-option-no { @@ -839,7 +848,7 @@ StatusMessage { .approval-description { height: auto; - color: ansi_default; + color: ansi_bright_black; } .code-block { diff --git a/vibe/cli/textual_ui/widgets/approval_app.py b/vibe/cli/textual_ui/widgets/approval_app.py index 0fcfc6d..97df412 100644 --- a/vibe/cli/textual_ui/widgets/approval_app.py +++ b/vibe/cli/textual_ui/widgets/approval_app.py @@ -20,6 +20,8 @@ class ApprovalApp(Container): can_focus = True can_focus_children = False + NUM_OPTIONS = 4 + BINDINGS: ClassVar[list[BindingType]] = [ Binding("up", "move_up", "Up", show=False), Binding("down", "move_down", "Down", show=False), @@ -27,8 +29,9 @@ class ApprovalApp(Container): Binding("1", "select_1", "Yes", show=False), Binding("y", "select_1", "Yes", show=False), Binding("2", "select_2", "Always Tool Session", show=False), - Binding("3", "select_3", "No", show=False), - Binding("n", "select_3", "No", show=False), + Binding("3", "select_3", "Always Permanent", show=False), + Binding("4", "select_4", "No", show=False), + Binding("n", "select_4", "No", show=False), ] class ApprovalGranted(Message): @@ -49,6 +52,18 @@ class ApprovalApp(Container): self.tool_args = tool_args self.required_permissions = required_permissions + class ApprovalGrantedAlwaysPermanent(Message): + def __init__( + self, + tool_name: str, + tool_args: BaseModel, + required_permissions: list[RequiredPermission], + ) -> None: + super().__init__() + self.tool_name = tool_name + self.tool_args = tool_args + self.required_permissions = required_permissions + class ApprovalRejected(Message): def __init__(self, tool_name: str, tool_args: BaseModel) -> None: super().__init__() @@ -77,20 +92,18 @@ class ApprovalApp(Container): def compose(self) -> ComposeResult: with Vertical(id="approval-options"): yield NoMarkupStatic("") - for _ in range(3): + for _ in range(self.NUM_OPTIONS): widget = NoMarkupStatic("", classes="approval-option") self.option_widgets.append(widget) yield widget - yield NoMarkupStatic("") self.help_widget = NoMarkupStatic( "↑↓ navigate Enter select ESC reject", classes="approval-help" ) yield self.help_widget with Vertical(id="approval-content"): - self.title_widget = NoMarkupStatic( - f"⚠ {self.tool_name} command", classes="approval-title" - ) + title = self._build_title() + self.title_widget = NoMarkupStatic(title, classes="approval-title") yield self.title_widget with VerticalScroll(classes="approval-tool-info-scroll"): @@ -99,6 +112,12 @@ class ApprovalApp(Container): ) yield self.tool_info_container + def _build_title(self) -> str: + if self.required_permissions: + labels = ", ".join(rp.label for rp in self.required_permissions) + return f"Permission for the {self.tool_name} tool ({labels})" + return f"Permission for the {self.tool_name} tool" + async def on_mount(self) -> None: await self._update_tool_info() self._update_options() @@ -113,16 +132,11 @@ class ApprovalApp(Container): await self.tool_info_container.mount(approval_widget) def _update_options(self) -> None: - if self.required_permissions: - labels = ", ".join(rp.label for rp in self.required_permissions) - always_text = f"Yes and always allow for this session: {labels}" - else: - always_text = f"Yes and always allow {self.tool_name} for this session" - options = [ - ("Yes", "yes"), - (always_text, "yes"), - ("No and tell the agent what to do instead", "no"), + ("Allow once", "yes"), + ("Allow for remainder of this session", "yes"), + ("Always allow", "yes"), + ("Deny", "no"), ] for idx, ((text, color_type), widget) in enumerate( @@ -154,11 +168,11 @@ class ApprovalApp(Container): widget.add_class("approval-option-no") def action_move_up(self) -> None: - self.selected_option = (self.selected_option - 1) % 3 + self.selected_option = (self.selected_option - 1) % self.NUM_OPTIONS self._update_options() def action_move_down(self) -> None: - self.selected_option = (self.selected_option + 1) % 3 + self.selected_option = (self.selected_option + 1) % self.NUM_OPTIONS self._update_options() def action_select(self) -> None: @@ -176,9 +190,13 @@ class ApprovalApp(Container): self.selected_option = 2 self._handle_selection(2) + def action_select_4(self) -> None: + self.selected_option = 3 + self._handle_selection(3) + def action_reject(self) -> None: - self.selected_option = 2 - self._handle_selection(2) + self.selected_option = 3 + self._handle_selection(3) def _handle_selection(self, option: int) -> None: match option: @@ -197,6 +215,14 @@ class ApprovalApp(Container): ) ) case 2: + self.post_message( + self.ApprovalGrantedAlwaysPermanent( + tool_name=self.tool_name, + tool_args=self.tool_args, + required_permissions=self.required_permissions, + ) + ) + case 3: self.post_message( self.ApprovalRejected( tool_name=self.tool_name, tool_args=self.tool_args diff --git a/vibe/cli/textual_ui/widgets/messages.py b/vibe/cli/textual_ui/widgets/messages.py index 37a05ea..cfa9413 100644 --- a/vibe/cli/textual_ui/widgets/messages.py +++ b/vibe/cli/textual_ui/widgets/messages.py @@ -264,23 +264,92 @@ class InterruptMessage(Static): class BashOutputMessage(Static): - def __init__(self, command: str, cwd: str, output: str, exit_code: int) -> None: + def __init__( + self, + command: str, + cwd: str, + output: str = "", + exit_code: int = 0, + *, + pending: bool = False, + ) -> None: super().__init__() self.add_class("bash-output-message") self._command = command self._cwd = cwd self._output = output.rstrip("\n") self._exit_code = exit_code + self._pending = pending + self._output_widget: NoMarkupStatic | None = None + self._output_container: Horizontal | None = None + self._prompt_widget: NonSelectableStatic | None = None def compose(self) -> ComposeResult: - status_class = "bash-success" if self._exit_code == 0 else "bash-error" + status_class = ( + "bash-error" + if not self._pending and self._exit_code != 0 + else "bash-success" + ) self.add_class(status_class) with Horizontal(classes="bash-command-line"): - yield NonSelectableStatic("$ ", classes=f"bash-prompt {status_class}") + self._prompt_widget = NonSelectableStatic( + "$ ", classes=f"bash-prompt {status_class}" + ) + yield self._prompt_widget yield NoMarkupStatic(self._command, classes="bash-command") - with Horizontal(classes="bash-output-container"): - yield ExpandingBorder(classes="bash-output-border") - yield NoMarkupStatic(self._output, classes="bash-output") + if not self._pending: + self._output_container = Horizontal(classes="bash-output-container") + with self._output_container: + yield ExpandingBorder(classes="bash-output-border") + self._output_widget = NoMarkupStatic( + self._output, classes="bash-output" + ) + yield self._output_widget + + async def _ensure_output_container(self) -> None: + if self._output_container is not None: + return + self._output_widget = NoMarkupStatic("", classes="bash-output") + self._output_container = Horizontal( + ExpandingBorder(classes="bash-output-border"), + self._output_widget, + classes="bash-output-container", + ) + await self.mount(self._output_container) + + async def append_output(self, text: str) -> None: + await self._ensure_output_container() + self._output += text + if self._output_widget: + self._output_widget.update(self._output.rstrip("\n")) + + async def finish(self, exit_code: int, *, interrupted: bool = False) -> None: + self._exit_code = exit_code + self._pending = False + if interrupted: + self.remove_class("bash-success") + self.add_class("bash-interrupted") + if self._prompt_widget: + self._prompt_widget.remove_class("bash-success") + self._prompt_widget.add_class("bash-interrupted") + elif exit_code != 0: + self.remove_class("bash-success") + self.add_class("bash-error") + if self._prompt_widget: + self._prompt_widget.remove_class("bash-success") + self._prompt_widget.add_class("bash-error") + if interrupted: + suffix = ( + "\n(interrupted)" + if self._output and not self._output.endswith("\n") + else "(interrupted)" + ) + self._output += suffix + if not self._output: + self._output = "(no output)" + await self._ensure_output_container() + if self._output_widget: + self._output_widget.update(self._output.rstrip("\n")) class ErrorMessage(Static): diff --git a/vibe/core/agent_loop.py b/vibe/core/agent_loop.py index 87960d9..16bf1cd 100644 --- a/vibe/core/agent_loop.py +++ b/vibe/core/agent_loop.py @@ -172,6 +172,13 @@ def _is_context_too_long_error(e: Exception) -> bool: return False +def _is_non_retryable_error(e: BaseException) -> bool: + # Detect Temporal-style ``non_retryable`` flag without importing temporalio. + # Wrapping such an exception in a plain RuntimeError strips the flag, so + # Temporal's activity retry policy will retry the call until exhaustion. + return bool(getattr(e, "non_retryable", False)) + + def requires_init(fn: Callable[..., Any]) -> Callable[..., Any]: """Decorator that awaits deferred initialization before executing the method.""" if inspect.isasyncgenfunction(fn): @@ -451,6 +458,10 @@ class AgentLoop: session_pattern=rp.session_pattern, ) ) + if save_permanently: + self.config.add_tool_allowlist_patterns( + tool_name, [rp.session_pattern for rp in required_permissions] + ) else: self.set_tool_permission( tool_name, ToolPermission.ALWAYS, save_permanently=save_permanently @@ -1199,6 +1210,8 @@ class AgentLoop: raise RateLimitError(provider.name, active_model.name) from e if _is_context_too_long_error(e): raise ContextTooLongError(provider.name, active_model.name) from e + if _is_non_retryable_error(e): + raise raise RuntimeError( f"API error from {provider.name} (model: {active_model.name}): {e}" @@ -1275,6 +1288,8 @@ class AgentLoop: raise RateLimitError(provider.name, active_model.name) from e if _is_context_too_long_error(e): raise ContextTooLongError(provider.name, active_model.name) from e + if _is_non_retryable_error(e): + raise raise RuntimeError( f"API error from {provider.name} (model: {active_model.name}): {e}" @@ -1414,13 +1429,14 @@ class AgentLoop: i += 1 - def _reset_session(self) -> None: + def _reset_session(self, keep_parent: bool = True) -> None: old_session_id = self.session_id suffix = extract_suffix(self.session_id) self.session_id = generate_session_id(suffix=suffix) - self.parent_session_id = old_session_id + parent_session_id = old_session_id if keep_parent else None + self.parent_session_id = parent_session_id self.session_logger.reset_session( - self.session_id, parent_session_id=old_session_id + self.session_id, parent_session_id=parent_session_id ) self.emit_new_session_telemetry() @@ -1500,7 +1516,7 @@ class AgentLoop: self.middleware_pipeline.reset() self.tool_manager.reset_all() - self._reset_session() + self._reset_session(keep_parent=False) @requires_init async def compact(self, extra_instructions: str = "") -> str: diff --git a/vibe/core/autocompletion/path_prompt.py b/vibe/core/autocompletion/path_prompt.py index 49880e3..f0b5b2e 100644 --- a/vibe/core/autocompletion/path_prompt.py +++ b/vibe/core/autocompletion/path_prompt.py @@ -9,7 +9,7 @@ from typing import Literal class PathResource: path: Path alias: str - kind: Literal["file", "directory"] + kind: Literal["file", "folder"] @dataclass(frozen=True, slots=True) @@ -17,13 +17,14 @@ class PathPromptPayload: display_text: str prompt_text: str resources: list[PathResource] + all_resources: list[PathResource] def build_path_prompt_payload( message: str, *, base_dir: Path | None = None ) -> PathPromptPayload: if not message: - return PathPromptPayload(message, message, []) + return PathPromptPayload(message, message, [], []) resolved_base = (base_dir or Path.cwd()).resolve() prompt_parts: list[str] = [] @@ -44,7 +45,7 @@ def build_path_prompt_payload( prompt_text = "".join(prompt_parts) unique_resources = _dedupe_resources(resources) - return PathPromptPayload(message, prompt_text, unique_resources) + return PathPromptPayload(message, prompt_text, unique_resources, resources) def _is_path_anchor(message: str, pos: int) -> bool: @@ -93,7 +94,7 @@ def _to_resource(candidate: str, base_dir: Path) -> PathResource | None: if not resolved.exists(): return None - kind = "directory" if resolved.is_dir() else "file" + kind = "folder" if resolved.is_dir() else "file" return PathResource(path=resolved, alias=candidate, kind=kind) diff --git a/vibe/core/autocompletion/path_prompt_adapter.py b/vibe/core/autocompletion/path_prompt_adapter.py index 856b8d3..bb8fcb3 100644 --- a/vibe/core/autocompletion/path_prompt_adapter.py +++ b/vibe/core/autocompletion/path_prompt_adapter.py @@ -44,7 +44,7 @@ def _path_prompt_to_content_blocks( "uri": resource.path.as_uri(), "name": resource.alias, }) - case "directory": + case "folder": blocks.append({ "type": "resource_link", "uri": resource.path.as_uri(), diff --git a/vibe/core/config/_settings.py b/vibe/core/config/_settings.py index f7482b5..3afadc6 100644 --- a/vibe/core/config/_settings.py +++ b/vibe/core/config/_settings.py @@ -332,8 +332,6 @@ MCPServer = Annotated[ class ConnectorConfig(BaseModel): - """Per-connector settings persisted in config.toml under ``[[connectors]]``.""" - name: str = Field(description="Normalized connector alias to match against.") disabled: bool = Field( default=False, @@ -937,9 +935,21 @@ class VibeConfig(BaseSettings): ] type(self).save_updates({"models": models}) + def add_tool_allowlist_patterns(self, tool_name: str, patterns: list[str]) -> None: + current_allowlist: list[str] = list( + self.tools.get(tool_name, {}).get("allowlist", []) + ) + new_patterns = [p for p in patterns if p not in current_allowlist] + if not new_patterns: + return + merged = sorted(current_allowlist + new_patterns) + self.save_updates({"tools": {tool_name: {"allowlist": merged}}}) + if tool_name not in self.tools: + self.tools[tool_name] = {} + self.tools[tool_name]["allowlist"] = merged + @classmethod def get_persisted_config(cls) -> dict[str, Any]: - """Return the raw config as persisted in the TOML file (no profile merging).""" return TomlFileSettingsSource(cls).toml_data @classmethod diff --git a/vibe/core/programmatic.py b/vibe/core/programmatic.py index ee76729..8f0f429 100644 --- a/vibe/core/programmatic.py +++ b/vibe/core/programmatic.py @@ -67,8 +67,8 @@ def run_programmatic( # noqa: PLR0913, PLR0917 logger.info( "Loaded %d messages from previous session", len(non_system_messages) ) - - agent_loop.emit_new_session_telemetry() + else: + agent_loop.emit_new_session_telemetry() if teleport and config.vibe_code_enabled: gen = agent_loop.teleport_to_vibe_code(prompt or None) diff --git a/vibe/core/session/saved_sessions.py b/vibe/core/session/saved_sessions.py new file mode 100644 index 0000000..f4364f5 --- /dev/null +++ b/vibe/core/session/saved_sessions.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any + +from vibe.core.config import SessionLoggingConfig +from vibe.core.session.session_loader import METADATA_FILENAME, SessionLoader +from vibe.core.session.session_logger import SessionLogger +from vibe.core.utils.io import read_safe + + +def _normalize_session_title(title: str) -> str: + normalized_title = title.strip() + if not normalized_title: + raise ValueError("Session title cannot be empty.") + + return normalized_title + + +def _resolve_saved_session_dir( + session_id: str, session_config: SessionLoggingConfig +) -> Path: + for session_dir in SessionLoader._find_session_dirs_by_short_id( + session_id, session_config + ): + try: + metadata = _load_raw_metadata(session_dir) + except (OSError, json.JSONDecodeError): + continue + + if metadata.get("session_id") == session_id: + return session_dir + + raise ValueError(f"Session not found: {session_id}") + + +def _load_raw_metadata(session_dir: Path) -> dict[str, Any]: + metadata_path = session_dir / METADATA_FILENAME + return json.loads(read_safe(metadata_path).text) + + +async def update_saved_session_title_at_path( + session_dir: Path, title: str +) -> dict[str, Any]: + normalized_title = _normalize_session_title(title) + metadata = _load_raw_metadata(session_dir) + + updated_metadata = {**metadata, "title": normalized_title, "title_source": "manual"} + await SessionLogger.persist_metadata(updated_metadata, session_dir) + return updated_metadata + + +async def update_saved_session_title( + session_id: str, title: str, session_config: SessionLoggingConfig +) -> dict[str, Any]: + session_dir = _resolve_saved_session_dir(session_id, session_config) + return await update_saved_session_title_at_path(session_dir, title) diff --git a/vibe/core/session/session_logger.py b/vibe/core/session/session_logger.py index 0b14b96..236660e 100644 --- a/vibe/core/session/session_logger.py +++ b/vibe/core/session/session_logger.py @@ -8,7 +8,7 @@ import os from pathlib import Path import subprocess from threading import Lock -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Literal from anyio import NamedTemporaryFile, Path as AsyncPath @@ -134,6 +134,8 @@ class SessionLogger: git_branch=git_branch, username=user_name, environment={"working_directory": str(Path.cwd())}, + title=None, + title_source="auto", ) def _get_title(self, messages: Sequence[LLMMessage]) -> str: @@ -154,6 +156,37 @@ class SessionLogger: return title + def _set_title_state( + self, title: str | None, *, source: Literal["auto", "manual"] + ) -> None: + if self.session_metadata is None: + return + + self.session_metadata.title = title + self.session_metadata.title_source = source + + def set_title(self, title: str | None) -> None: + if title is None: + self._set_title_state(None, source="auto") + return + + normalized_title = title.strip() + if not normalized_title: + raise ValueError("Session title cannot be empty.") + + self._set_title_state(normalized_title, source="manual") + + def _resolve_title(self, messages: Sequence[LLMMessage]) -> str | None: + if self.session_metadata is None: + return self._get_title(messages) + + if self.session_metadata.title_source == "manual": + return self.session_metadata.title + + title = self._get_title(messages) + self._set_title_state(title, source="auto") + return title + @staticmethod async def persist_metadata(metadata: Any, session_dir: Path) -> None: temp_metadata_filepath = None @@ -265,7 +298,7 @@ class SessionLogger: for tool_class in tool_manager.available_tools.values() ] - title = self._get_title(messages) + title = self._resolve_title(messages) system_prompt = ( messages[0].model_dump() if len(messages) > 0 and messages[0].role == Role.system diff --git a/vibe/core/telemetry/send.py b/vibe/core/telemetry/send.py index 62ad281..30e6fc9 100644 --- a/vibe/core/telemetry/send.py +++ b/vibe/core/telemetry/send.py @@ -300,6 +300,22 @@ class TelemetryClient: payload = {"init_duration_ms": init_duration_ms} self.send_telemetry_event("vibe.ready", payload) + def send_at_mention_inserted( + self, + *, + nb_mentions: int, + context_types: dict[str, int], + file_extensions: dict[str, int] | None, + message_id: str | None, + ) -> None: + payload: dict[str, Any] = { + "nb_mentions": nb_mentions, + "context_types": context_types, + "file_extensions": file_extensions, + "message_id": message_id, + } + self.send_telemetry_event("vibe.at_mention_inserted", payload) + def send_user_rating_feedback(self, rating: int, model: str) -> None: self.send_telemetry_event( "vibe.user_rating_feedback", diff --git a/vibe/core/tools/connectors/connector_registry.py b/vibe/core/tools/connectors/connector_registry.py index 1ca6bb9..5a6598e 100644 --- a/vibe/core/tools/connectors/connector_registry.py +++ b/vibe/core/tools/connectors/connector_registry.py @@ -7,9 +7,6 @@ from typing import TYPE_CHECKING, Any, ClassVar import httpx from mistralai.client import Mistral -from mistralai.client.models.connectorsqueryfilters import ( - ConnectorsQueryFiltersTypedDict, -) from vibe.core.logger import logger from vibe.core.tools.base import ( @@ -33,9 +30,7 @@ from vibe.core.utils import run_sync if TYPE_CHECKING: from vibe.core.types import ToolResultEvent -_LIST_PAGE_SIZE = 100 -_LIST_QUERY_FILTERS: ConnectorsQueryFiltersTypedDict = {"active": True} -_TOOL_FETCH_TIMEOUT = 8.0 +_BOOTSTRAP_TIMEOUT = 30.0 def _normalize_name(name: str) -> str: @@ -44,25 +39,15 @@ def _normalize_name(name: str) -> str: return result or "unnamed" -def _connector_tool_to_remote(tool: dict[str, Any] | Any) -> RemoteTool | None: - """Convert a ConnectorTool (SDK object or raw dict) to a RemoteTool.""" - if isinstance(tool, dict): - name = tool.get("name") - description = tool.get("description") - schema = tool.get("jsonschema") or tool.get("json_schema") - else: - name = getattr(tool, "name", None) - description = getattr(tool, "description", None) - schema = getattr(tool, "jsonschema", None) or getattr(tool, "json_schema", None) +def _connector_tool_to_remote(tool: dict[str, Any]) -> RemoteTool | None: + """Convert a bootstrap tool dict to a RemoteTool.""" + name = tool.get("name") if not name: return None - if schema is not None and not isinstance(schema, dict): - dump = getattr(schema, "model_dump", None) - schema = dump() if callable(dump) else None return RemoteTool.model_validate({ "name": name, - "description": description, - "inputSchema": schema or {"type": "object", "properties": {}}, + "description": tool.get("description"), + "inputSchema": tool.get("inputSchema") or {"type": "object", "properties": {}}, }) @@ -208,6 +193,36 @@ def create_connector_proxy_tool_class( return ConnectorProxyTool +def _deduplicate_connectors( + connectors: list[dict[str, Any]], +) -> list[tuple[str, str, dict[str, Any]]]: + """Deduplicate connectors by normalized alias, preserving order. + + When two connectors share the same alias, disambiguate with a + numeric suffix rather than silently dropping. + """ + seen_names: set[str] = set() + result: list[tuple[str, str, dict[str, Any]]] = [] + for connector in connectors: + connector_id = str(connector.get("id", "")) + if not connector_id: + continue + raw_name = connector.get("name") or connector_id + alias = _normalize_name(raw_name) + if alias in seen_names: + original = alias + suffix = 2 + while f"{original}_{suffix}" in seen_names: + suffix += 1 + alias = f"{original}_{suffix}" + logger.warning( + "Connector %r alias %r collides, using %r", raw_name, original, alias + ) + seen_names.add(alias) + result.append((connector_id, alias, connector)) + return result + + class ConnectorRegistry: """Discovers connector tools from the Mistral API. @@ -240,6 +255,47 @@ class ConnectorRegistry: return await self._discover_all() + async def _fetch_bootstrap(self) -> dict[str, Any]: + base_url = self._server_url or _DEFAULT_BASE_URL + url = f"{base_url}/v1/connectors/bootstrap" + headers = {"Authorization": f"Bearer {self._api_key}"} + async with httpx.AsyncClient(timeout=_BOOTSTRAP_TIMEOUT) as http: + response = await http.get(url, headers=headers) + response.raise_for_status() + return response.json() + + def _build_tools_for_connector( + self, + *, + connector_id: str, + alias: str, + name: str, + raw_tools: list[dict[str, Any]], + ) -> dict[str, type[BaseTool]]: + tools_map: dict[str, type[BaseTool]] = {} + for tool in raw_tools: + remote = _connector_tool_to_remote(tool) + if remote is None: + continue + try: + proxy_cls = create_connector_proxy_tool_class( + connector_name=name, + connector_alias=alias, + connector_id=connector_id, + remote=remote, + api_key=self._api_key, + server_url=self._server_url, + ) + tools_map[proxy_cls.get_name()] = proxy_cls + except Exception: + logger.warning( + "Failed to register connector tool %s for %s", + tool.get("name", "<unknown>"), + name, + exc_info=True, + ) + return tools_map + async def _discover_all(self) -> dict[str, type[BaseTool]]: async with self._discover_lock: # Re-check under lock — another coroutine may have finished @@ -250,157 +306,55 @@ class ConnectorRegistry: result.update(tools) return result - async with self._get_client() as client: - connectors: list[Any] = [] + try: + data = await self._fetch_bootstrap() + except Exception: + logger.warning("Failed to bootstrap connectors", exc_info=True) + self._cache = {} + self._connector_names = [] + self._connector_connected = {} + self._alias_to_id = {} + return {} - try: - page = await client.beta.connectors.list_async( - page_size=_LIST_PAGE_SIZE, query_filters=_LIST_QUERY_FILTERS + unique_connectors = _deduplicate_connectors(data.get("connectors") or []) + + cache: dict[str, dict[str, type[BaseTool]]] = {} + all_tools: dict[str, type[BaseTool]] = {} + connector_names: list[str] = [] + connector_connected: dict[str, bool] = {} + + for connector_id, alias, connector in unique_connectors: + connector_names.append(alias) + name = connector.get("name") or connector_id + + if bootstrap_errors := connector.get("bootstrap_errors"): + logger.warning( + "Connector %r bootstrap errors: %s", name, bootstrap_errors ) - connectors.extend(page.items or []) - while page.pagination and page.pagination.next_cursor: - page = await client.beta.connectors.list_async( - page_size=_LIST_PAGE_SIZE, - cursor=page.pagination.next_cursor, - query_filters=_LIST_QUERY_FILTERS, - ) - connectors.extend(page.items or []) - except Exception as exc: - logger.warning(f"Failed to list connectors: {exc}") - self._cache = {} - self._connector_names = [] - self._connector_connected = {} - self._alias_to_id = {} - return {} - # Build results locally to avoid publishing incomplete state. - cache: dict[str, dict[str, type[BaseTool]]] = {} - all_tools: dict[str, type[BaseTool]] = {} - connector_names: list[str] = [] - connector_connected: dict[str, bool] = {} + status = connector.get("status") or {} + if not status.get("is_ready", False): + connector_connected[alias] = False + continue - # Deduplicate by normalized name, preserving order. - # When two connectors share the same alias, disambiguate - # with a numeric suffix rather than silently dropping. - seen_names: set[str] = set() - unique_connectors: list[tuple[str, str, Any]] = [] - for connector in connectors: - connector_id = str(connector.id) - raw_name = connector.name or connector_id - alias = _normalize_name(raw_name) - if alias in seen_names: - original = alias - suffix = 2 - while f"{alias}_{suffix}" in seen_names: - suffix += 1 - alias = f"{alias}_{suffix}" - logger.warning( - f"Connector {raw_name!r} alias {original!r} collides, " - f"using {alias!r}" - ) - seen_names.add(alias) - unique_connectors.append((connector_id, alias, connector)) - - # Fetch tools for all connectors in parallel. - tasks = [ - self._fetch_connector_tools(client, c, alias) - for _, alias, c in unique_connectors - ] - results = await asyncio.gather(*tasks) - - for (connector_id, alias, _), tools_map in zip( - unique_connectors, results, strict=True - ): - connector_names.append(alias) - if tools_map is None: - # Timeout or error — show in UI but don't cache so - # a refresh will retry discovery for this connector. - connector_connected[alias] = False - continue - cache[connector_id] = tools_map - all_tools.update(tools_map) - # TODO: replace with actual API field when available - connector_connected[alias] = bool(tools_map) - - # Publish atomically — concurrent callers waiting on the - # lock will see the completed cache. - self._connector_names = connector_names - self._connector_connected = connector_connected - self._alias_to_id = {alias: cid for cid, alias, _ in unique_connectors} - self._cache = cache - - return all_tools - - async def _fetch_connector_tools( - self, client: Mistral, connector: Any, connector_alias: str - ) -> dict[str, type[BaseTool]] | None: - """Fetch tools for a single connector via ``list_tools_async``. - - The list endpoint does not always include tools inline, so we - call ``list_tools_async`` per connector to get the full set. - """ - connector_id = str(connector.id) - name = connector.name or connector_id - - try: - response = await asyncio.wait_for( - client.beta.connectors.list_tools_async( - connector_id_or_name=connector_id - ), - timeout=_TOOL_FETCH_TIMEOUT, - ) - except TimeoutError: - logger.warning( - f"Timeout fetching tools for connector {name} (>{_TOOL_FETCH_TIMEOUT}s)" - ) - return None - except Exception as exc: - logger.warning(f"Failed to list tools for connector {name}: {exc}") - return None - - # The SDK may return a plain list, an iterable wrapper, or an - # object with a `data`/`items` attribute. Handle all cases. - if isinstance(response, list): - raw_tools: list[Any] = response - elif hasattr(response, "data"): - raw_tools = list(response.data or []) - elif hasattr(response, "items"): - raw_tools = list(response.items or []) - else: - try: - raw_tools = list(response) - except TypeError: - logger.warning( - f"Unexpected response type from list_tools_async for {name}: " - f"{type(response).__name__}" - ) - raw_tools = [] - - result: dict[str, type[BaseTool]] = {} - for tool in raw_tools: - remote = _connector_tool_to_remote(tool) - if remote is None: - continue - try: - proxy_cls = create_connector_proxy_tool_class( - connector_name=name, - connector_alias=connector_alias, + tools_map = self._build_tools_for_connector( connector_id=connector_id, - remote=remote, - api_key=self._api_key, - server_url=self._server_url, + alias=alias, + name=name, + raw_tools=connector.get("tools") or [], ) - result[proxy_cls.get_name()] = proxy_cls - except Exception as exc: - tool_name = ( - tool.get("name") - if isinstance(tool, dict) - else getattr(tool, "name", "<unknown>") - ) - logger.warning( - f"Failed to register connector tool {tool_name} for {name}: {exc}" - ) - return result + cache[connector_id] = tools_map + all_tools.update(tools_map) + connector_connected[alias] = bool(tools_map) + + # Publish atomically — concurrent callers waiting on the + # lock will see the completed cache. + self._connector_names = connector_names + self._connector_connected = connector_connected + self._alias_to_id = {alias: cid for cid, alias, _ in unique_connectors} + self._cache = cache + + return all_tools @property def connector_count(self) -> int: @@ -421,6 +375,7 @@ class ConnectorRegistry: async def refresh_connector_async(self, alias: str) -> dict[str, type[BaseTool]]: """Re-fetch tools for a single connector by alias. + Calls the bootstrap endpoint and extracts the matching connector. Updates the internal cache for that connector only. Returns the new tool map (empty dict on failure). """ @@ -428,15 +383,27 @@ class ConnectorRegistry: if connector_id is None: return {} + tools_map: dict[str, type[BaseTool]] | None = None try: - async with self._get_client() as client: - connector = await client.beta.connectors.get_async( - connector_id_or_name=connector_id + data = await self._fetch_bootstrap() + for connector in data.get("connectors") or []: + if str(connector.get("id")) != connector_id: + continue + + name = connector.get("name") or connector_id + status = connector.get("status") or {} + if not status.get("is_ready", False): + break + + tools_map = self._build_tools_for_connector( + connector_id=connector_id, + alias=alias, + name=name, + raw_tools=connector.get("tools") or [], ) - tools_map = await self._fetch_connector_tools(client, connector, alias) + break except Exception: logger.debug("Failed to refresh connector %s", alias) - tools_map = None if self._cache is None: self._cache = {} diff --git a/vibe/core/types.py b/vibe/core/types.py index c14c6fa..52b1dd6 100644 --- a/vibe/core/types.py +++ b/vibe/core/types.py @@ -142,6 +142,8 @@ class SessionMetadata(BaseModel): git_branch: str | None environment: dict[str, str | None] username: str + title: str | None = None + title_source: Literal["auto", "manual"] = "auto" StrToolChoice = Literal["auto", "none", "any", "required"] diff --git a/vibe/whats_new.md b/vibe/whats_new.md index 4d325bf..0ee9980 100644 --- a/vibe/whats_new.md +++ b/vibe/whats_new.md @@ -1,12 +1,5 @@ -# What's new in v2.9.1 +# What's new in v2.9.4 -- **Default model**: Migrated to `mistral-medium-3.5`. -- **Connector OAuth**: Authenticate connectors via OAuth directly from the `/mcp` menu. - -# What's new in v2.9.0 -- **Scratchpad directory**: Model's autonomous workspace for temporary files, intermediate results, and drafts shared with subagents -- **`/copy` command**: New slash command to copy content directly from the conversation -- **`--trust` flag**: Grant session-only tool permissions -- **MCP toggle**: Enable/disable MCP servers and individual tools from the `/mcp` menu -- **Custom compaction**: Provide custom instructions when compacting context via `/compact` -- **Thinking level picker**: Choose thinking level with `/thinking` +- **`/rename` command**: Rename the current session from the slash menu. +- **Persistent "always allow"**: Tool permissions granted with "always allow" now stick across sessions. +- **Faster bash bang commands**: `!command` runs via async subprocess.