diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 039af8b5..d0905b11 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -77,7 +77,7 @@ jobs: autocontrol:ci -c " pip install --no-cache-dir -r dev_requirements.txt && xvfb-run -a -s '-screen 0 1280x800x24' \ - python -m pytest test/unit_test/headless -q --tb=short + python -m pytest -q --tb=short " - name: Smoke test the entrypoint (rest mode) diff --git a/.github/workflows/platform-smoke.yml b/.github/workflows/platform-smoke.yml new file mode 100644 index 00000000..5f72b736 --- /dev/null +++ b/.github/workflows/platform-smoke.yml @@ -0,0 +1,51 @@ +name: Platform smoke + +on: + push: + branches: ["main", "dev"] + pull_request: + branches: ["main", "dev"] + +permissions: + contents: read + +jobs: + stable-api: + strategy: + fail-fast: false + matrix: + os: [windows-2022, ubuntu-22.04, macos-14] + python-version: ["3.10", "3.14"] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - run: python -m pip install -e . # NOSONAR githubactions:S8544 # reason: installs the checked-out project itself, there is no upstream version to lock + # The X11 backend connects to a display at import time, so Linux + # runs need a virtual one. + - name: Install a virtual display (Linux) + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install -y xvfb + - name: Import stable API and generate platform-neutral code + shell: bash + run: >- + ${{ runner.os == 'Linux' && 'xvfb-run -a' || '' }} + python -c "import je_auto_control.api as ac; + compile(ac.generate_code([['AC_screen_size']], style='actions'), + '', 'exec')" + - name: Create headless diagnostic bundle + shell: bash + run: >- + ${{ runner.os == 'Linux' && 'xvfb-run -a' || '' }} + python -c "from je_auto_control.api import + FailureBundleOptions, create_failure_bundle; + create_failure_bundle('platform-smoke.zip', + options=FailureBundleOptions(screenshot=False))" + - uses: actions/upload-artifact@v4 + if: always() + with: + name: platform-smoke-${{ matrix.os }}-${{ matrix.python-version }} + path: platform-smoke.zip + if-no-files-found: warn diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 16b284d8..d159b626 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -16,6 +16,15 @@ permissions: contents: read jobs: + dependency-review: + if: github.event_name == 'pull_request' + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + - uses: actions/dependency-review-action@v4 + lint: runs-on: ubuntu-latest steps: @@ -28,9 +37,7 @@ jobs: cache: "pip" - name: Install ruff - run: | - python -m pip install --upgrade pip - pip install ruff + run: "pip install --only-binary :all: ruff==0.15.22" - name: Run ruff run: ruff check je_auto_control/ @@ -47,9 +54,7 @@ jobs: cache: "pip" - name: Install bandit - run: | - python -m pip install --upgrade pip - pip install bandit + run: "pip install --only-binary :all: bandit==1.9.4" - name: Run bandit (recursive, skip tests + i18n dicts) run: bandit -r je_auto_control/ -c pyproject.toml @@ -81,7 +86,30 @@ jobs: # for any sub-package the snapshot doesn't include # (admin, usb, remote_desktop, vision, …). pip install -e . - pip install ruff==0.15.14 bandit==1.9.4 pytest==9.0.3 pytest-timeout==2.4.0 pytest-rerunfailures==15.1 PySide6==6.11.1 + pip install --only-binary :all: ruff==0.15.22 bandit==1.9.4 pytest==9.1.1 pytest-timeout==2.4.0 pytest-rerunfailures==15.1 pytest-cov==7.0.0 PySide6==6.11.1 + # Paths come from `testpaths` in pyproject.toml. Do NOT pass an explicit + # path here: an argument overrides testpaths, which previously meant the + # flow_control tests were configured to run but silently never did. - name: Run headless pytest suite - run: pytest test/unit_test/headless/ -v --tb=short --timeout=120 + run: >- + pytest -v --tb=short --timeout=120 + --cov=je_auto_control --cov-report=term-missing + --cov-report=xml --cov-fail-under=35 + + - name: Upload coverage report + uses: actions/upload-artifact@v4 + with: + name: coverage-${{ matrix.python-version }} + path: coverage.xml + + typing-stable-api: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - run: pip install -e . # NOSONAR githubactions:S8541,githubactions:S8544 # reason: installs the checked-out project itself, there is no upstream version to lock and the build must run + - run: "pip install --only-binary :all: mypy==2.3.0" + - run: mypy je_auto_control/api je_auto_control/utils/failure_bundle diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..dfd2b115 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,62 @@ +name: Release + +on: + push: + tags: ["v*"] + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + attestations: write + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - run: "python -m pip install --only-binary :all: build==1.5.0 twine==6.2.0" + - name: Verify tag matches package version + env: + RELEASE_TAG: ${{ github.ref_name }} + run: | + python - <<'PY' + import os, tomllib + with open("pyproject.toml", "rb") as handle: + version = tomllib.load(handle)["project"]["version"] + if os.environ["RELEASE_TAG"] != f"v{version}": + raise SystemExit(f"tag {os.environ['RELEASE_TAG']} != v{version}") + PY + - run: python -m build + - run: python -m twine check dist/* + - name: Smoke-test the built wheel + run: | + python -m venv /tmp/wheel-test + /tmp/wheel-test/bin/pip install dist/*.whl + /tmp/wheel-test/bin/python -c "import je_auto_control.api" + - uses: actions/attest-build-provenance@v2 + with: + subject-path: "dist/*" + - uses: actions/upload-artifact@v4 + with: + name: python-distributions + path: dist/ + + publish: + needs: build + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/je-auto-control + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v4 + with: + name: python-distributions + path: dist/ + - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 diff --git a/.github/workflows/stable.yml b/.github/workflows/stable.yml index 60d36c02..06e6e1e1 100644 --- a/.github/workflows/stable.yml +++ b/.github/workflows/stable.yml @@ -118,7 +118,8 @@ jobs: publish: needs: test - if: github.event_name == 'push' && github.ref == 'refs/heads/main' + # Publishing moved to release.yml: immutable v* tags + Trusted Publishing. + if: ${{ false }} runs-on: ubuntu-latest permissions: contents: write @@ -134,9 +135,7 @@ jobs: python-version: "3.12" - name: Install build tooling - run: | - python -m pip install --upgrade pip - pip install build twine + run: "pip install --only-binary :all: build==1.5.0 twine==6.2.0" - name: Bump patch version in pyproject.toml id: bump diff --git a/.gitignore b/.gitignore index 66c4f152..ab74cb98 100644 --- a/.gitignore +++ b/.gitignore @@ -126,3 +126,8 @@ dmypy.json /.claude/ /.claude /.idea + +# Local test/smoke artifacts +.test-tmp/ +bundle-smoke.zip +platform-smoke.zip diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..06d0aa0b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,43 @@ +# Changelog + +This file records user-visible compatibility changes. Detailed development +notes remain in `WHATS_NEW.md`. + +The format follows Keep a Changelog. Until 1.0, breaking changes are permitted +only when documented here with a migration path. + +## Unreleased + +### Added + +- Stable, headless `je_auto_control.api` façade. +- Portable `autocontrol.failure-bundle/v1` diagnostic archives and CLI command. +- Public API lifecycle, capability matrix, security policy, coverage and type + checking configuration. + +### Changed + +- Releases are prepared from version tags and use PyPI Trusted Publishing. +- The USB/IP server binds `127.0.0.1` by default (least-privilege). Exporting + the attached device to the LAN now requires an explicit `host="0.0.0.0"`. + +### Deprecated + +- New integrations should avoid the eager, historical top-level import surface + and import stable entry points from `je_auto_control.api`. + +### Fixed + +- macOS cursor position and omitted-coordinate clicks on Retina / HiDPI + displays (pixel-vs-point display-height mismatch). +- Remote-desktop relay hang on Linux + CPython 3.14 when one paired peer + disconnected (a cross-thread `shutdown()` no longer wakes a blocked `recv()`). +- `AC_expect_poll` crashing on a not-ready value instead of continuing to poll; + `AC_parallel` branch variable-scope isolation; malformed `run_suite` specs now + report a clean error instead of aborting. +- Windows Interception backend send-to-window click silently no-opping. +- Wayland partial-coordinate `mouse_scroll` raising instead of degrading. +- Action-file save now raises `AutoControlJsonActionException` (not a raw + `UnicodeEncodeError`) on non-encodable text; non-ASCII USB/IP busid no longer + kills the client thread; SQLite connections are closed; USB ACL removal is + case-insensitive. diff --git a/CLAUDE.md b/CLAUDE.md index df15f5ad..d06de150 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -84,6 +84,7 @@ No feature is complete unless it can be driven entirely without the GUI **and** - **Re-export from the package facade**: add the public functions / classes to `je_auto_control/__init__.py` and its `__all__` so `import je_auto_control as ac; ac.(...)` works out of the box. - **Executor command coverage**: wire an `AC_*` command into `utils/executor/action_executor.py` so the feature is usable from JSON action files, the socket server, the scheduler, and the visual script builder — all without Python glue. - **GUI tab or control is a thin wrapper**: the Qt widget must only translate user input into calls on the headless core. It must not contain business logic that would be unreachable headlessly. +- **Tab commands live in the Actions menu, not in-tab buttons**: the main window is menu-driven. A tab keeps only its inputs, tables, and result/status views; its commands surface through the window-level **Actions** menu. Core tabs declare `(label_key, handler)` pairs at registration in `gui/main_widget.py`; feature tabs expose a `menu_actions()` method returning the same shape. Script Builder and Remote Desktop are the only exempt tabs (interactive panel layouts). `test/unit_test/headless/test_actions_menu_gui.py` guards this contract — a new tab without registry actions or a `menu_actions()` hook fails CI. - **The top-level package stays Qt-free**: `import je_auto_control` MUST NOT import `PySide6`. The GUI entry point is loaded lazily inside `start_autocontrol_gui()`. Verify with: ```python diff --git a/README.md b/README.md index 18682fe7..1df11e07 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,9 @@ ## What's New -All per-release notes have moved to **[WHATS_NEW.md](WHATS_NEW.md)**. +**Latest (2026-07-18) — cross-platform reliability hardening.** A full-project runtime audit fixed execution-time defects across the macOS / Windows / Linux / Wayland backends, the executor, and the remote-desktop / USB stacks — correct Retina cursor math, a relay hang on CPython 3.14, `AC_expect_poll` / `AC_parallel` robustness, localhost-by-default USB/IP, and typed exceptions preserved at I/O boundaries — each covered by a headless regression test. No API changes. + +All per-release notes are in **[WHATS_NEW.md](WHATS_NEW.md)**. ## Features @@ -99,9 +101,10 @@ All per-release notes have moved to **[WHATS_NEW.md](WHATS_NEW.md)**. - **WebRTC Packet Inspector** — process-global rolling window of `StatsSnapshot` samples (default 600 / ~10 min @ 1Hz) fed by the existing WebRTC stats pollers. Per-metric `last/min/max/avg/p95` for RTT, FPS, bitrate, packet loss, jitter - **USB Device Enumeration** — read-only cross-platform device listing. Tries pyusb (libusb) first; falls back to platform-specific (Windows `Get-PnpDevice`, macOS `system_profiler`, Linux `/sys/bus/usb/devices`). Phase 2 passthrough builds on this (see below) - **System Diagnostics** — single-command "is everything OK?" probe across platform, optional deps, executor command count, audit chain, screenshot, mouse, disk space, REST registry. CLI exits 0 if all green / 1 otherwise; REST `/diagnose`; severity-tagged GUI tab +- **Stable API & Failure Bundles** — versioned, lazy `je_auto_control.api` façade for new integrations (`execute_action`, `generate_code`, `run_diagnostics`, failure bundles) with a documented [lifecycle policy](docs/API_LIFECYCLE.md). Portable `autocontrol.failure-bundle/v1` diagnostic ZIPs: manifest + redacted context/events/log tail, optional screenshot and diagnostics, best-effort collectors, atomic write. CLI `je_auto_control failure-bundle out.zip`; `codegen --failure-bundle` wraps generated pytest in automatic failure diagnostics - **USB Hotplug Events** — polling-based hotplug watcher (`UsbHotplugWatcher`) with bounded ring buffer + sequence-numbered events; `GET /usb/events?since=N` lets late subscribers catch up. GUI auto-refresh toggle on the USB tab. - **OpenAPI 3.1 + Swagger UI** — `GET /openapi.json` (auth-gated, generated from the live route table) + `GET /docs` (browser Swagger UI with bearer token bar). Drift test in CI catches new routes added without metadata. -- **Configuration Bundle** — single-file JSON export/import of user config (admin hosts, address book, trusted viewers, known hosts, host service, IDs). Atomic write with `.bak.` backups; CLI `python -m je_auto_control.utils.config_bundle export|import`; `POST /config/{export,import}`; GUI buttons on the REST API tab. +- **Configuration Bundle** — single-file JSON export/import of user config (admin hosts, address book, trusted viewers, known hosts, host service, IDs). Atomic write with `.bak.` backups; CLI `python -m je_auto_control.utils.config_bundle export|import`; `POST /config/{export,import}`; export/import commands on the REST API tab's Actions menu. - **USB Passthrough (opt-in)** — let a remote viewer use a USB device physically attached to the host, over a WebRTC `usb` DataChannel. Wire-level protocol (11 opcodes incl. `RESUME`, CREDIT-based flow control, 16 KiB payload cap with EOF fragmentation for oversize transfers). All eight original open questions resolved: reliable-ordered channel, LIST-over-channel (ACL-filtered), per-claim credits, Linux kernel-driver detach/reattach, and ACL **HMAC-SHA256 integrity** (fail-closed on tamper; pluggable key — Windows DPAPI or passphrase vault). **Backends:** `LibusbBackend` (production), `WinusbBackend` (ctypes) and `IokitBackend` (native IOKit enumeration + libusb transfers) — Windows/macOS *hardware-unverified*; `default_passthrough_backend()` picks per-OS. Viewer-side blocking client (`control/bulk/interrupt_transfer`, `list_devices`, `resume`); in-process `UsbLoopback` so one machine can share + use a device through the full stack. **Wired into WebRTC** host/viewer (`viewer.usb_client()`) plus claim **resume tokens** that survive a reconnect. Persistent ACL (default deny, mode 0600) with host-side prompt dialog, abuse **rate-limit / lockout**, and tamper-evident audit integration. Five driving surfaces: AnyDesk-style **GUI panel** (share + ACL allow/block + local/remote use), `AC_usb_*` executor commands (JSON / socket / scheduler), **REST** `/usb/...`, first-class **MCP** `ac_usb_*` tools, and the Python API. Default off — opt-in via `enable_usb_passthrough(True)` or `JE_AUTOCONTROL_USB_PASSTHROUGH=1`; default-on still pending Phase 2e external security sign-off + real-hardware verification. - **Observability (Prometheus + OpenTelemetry)** — stdlib-only `Counter` / `Gauge` / `Histogram` registry with a tiny built-in HTTP exporter on `/metrics`, plus an OpenTelemetry-compatible tracer that upgrades to real OTel spans when the SDK is installed. The executor and agent loop emit `autocontrol_action_calls_total{action,outcome}`, `autocontrol_action_duration_seconds`, and `autocontrol_agent_steps_total{tool,outcome}` automatically — drop the URL into a Prometheus scrape config and you have a Grafana dashboard with zero per-script wiring. @@ -546,8 +549,8 @@ ac.run_from_description("open Notepad and type hello", executor=executor) | `AUTOCONTROL_LLM_BACKEND` | `anthropic` to force a backend | | `AUTOCONTROL_LLM_MODEL` | Override the default model (e.g. `claude-opus-4-7`) | -GUI: **LLM Planner** tab — description box, `QThread`-backed *Plan* -button, action-list preview, and a *Run plan* button. +GUI: **LLM Planner** tab — description box and action-list preview; +*Plan* (`QThread`-backed) and *Run plan* live in the window's Actions menu. ### Runtime Variables & Control Flow @@ -575,7 +578,8 @@ commands, scripts can drive themselves from data without Python glue: `AC_if_var` operators: `eq`, `ne`, `lt`, `le`, `gt`, `ge`, `contains`, `startswith`, `endswith`. GUI: **Variables** tab — live view of -`executor.variables` with single-set, JSON seed, and clear-all controls. +`executor.variables`; single-set, JSON seed, and clear-all run from the +window's Actions menu. ### Remote Desktop @@ -1099,8 +1103,9 @@ for run in default_history_store.list_runs(limit=20): print(run.id, run.source, run.status, run.artifact_path) ``` -The GUI **Run History** tab exposes filter/refresh/clear and -double-click-to-open on the artifact column. +The GUI **Run History** tab shows the runs table with +double-click-to-open on the artifact column; filter, refresh, and +clear run from the window's Actions menu. ### Report Generation @@ -1357,6 +1362,16 @@ Or from the command line: python -m je_auto_control ``` +The main window is menu-driven: tabs hold only their inputs, tables, +and result views, and every tab's commands live in the window-level +**Actions** menu, which rebuilds for the active tab. **View → Tabs** +shows or hides any of the ~48 registered tabs, grouped by category +(Core / Editing / Detection & Vision / Automation Engines / System); +the default layout opens with just Record, Script Builder, and Remote +Desktop. **View → Text Size** offers auto/preset font scaling, and the +**Language** menu (English / 繁體中文 / 简体中文 / 日本語) retranslates +the whole window live. + --- ## Command-Line Interface @@ -1438,6 +1453,11 @@ python -m pytest test/integrated_test/ ### Project Links +- [Capability and platform matrix](docs/CAPABILITY_MATRIX.md) +- [Public API lifecycle and deprecation policy](docs/API_LIFECYCLE.md) +- [Security policy](SECURITY.md) +- [Compatibility changelog](CHANGELOG.md) + - **Homepage**: https://github.com/Intergration-Automation-Testing/AutoControl - **Documentation**: https://autocontrol.readthedocs.io/en/latest/ - **PyPI**: https://pypi.org/project/je_auto_control/ diff --git a/README/README_zh-CN.md b/README/README_zh-CN.md index a1113591..be26df10 100644 --- a/README/README_zh-CN.md +++ b/README/README_zh-CN.md @@ -56,7 +56,9 @@ ## 本次更新 -所有各版本更新说明已移至 **[WHATS_NEW_zh-CN.md](WHATS_NEW_zh-CN.md)**。 +**最新(2026-07-18)— 跨平台稳定性强化。** 一次全项目运行期审计修正了 macOS/Windows/Linux/Wayland 各后端、执行器,以及远程桌面/USB 堆栈的运行期缺陷——包含正确的 Retina 光标坐标运算、CPython 3.14 上的中继卡死、`AC_expect_poll`/`AC_parallel` 的健壮性、USB/IP 默认绑定本机,以及在 I/O 边界保留类型化异常——每项均有 headless 回归测试覆盖。无 API 变更。 + +各版本更新说明详见 **[WHATS_NEW.md](../WHATS_NEW.md)**。 ## 功能特性 @@ -97,9 +99,10 @@ - **WebRTC 包监测** — 由既有 WebRTC stats 轮询喂入的进程级 `StatsSnapshot` 滚动窗口(默认 600 条 / 1 Hz 约 10 分钟)。对 RTT、FPS、bitrate、丢包率、jitter 各回 `last/min/max/avg/p95` - **USB 设备列举** — 只读的跨平台 USB 设备列举。优先尝试 pyusb(libusb);若无则退回平台特定命令(Windows `Get-PnpDevice`、macOS `system_profiler`、Linux `/sys/bus/usb/devices`)。第二阶段 passthrough 构建于此(见下) - **系统诊断** — 一键"目前正常吗?"探测:平台、可选依赖包、executor 命令数、审计链、截图、鼠标、磁盘空间、REST registry。CLI 全绿 exit 0/否则 1;REST `/diagnose`;按严重度上色的 GUI 分页 +- **稳定 API 与失败诊断包** — 给新集成用的版本化、延迟加载 `je_auto_control.api` 门面(`execute_action`、`generate_code`、`run_diagnostics`、failure bundles),附[生命周期政策](../docs/API_LIFECYCLE.md)。便携式 `autocontrol.failure-bundle/v1` 诊断 ZIP:manifest + 已脱敏的 context/events/log 尾段、可选截图与诊断、best-effort 收集器、原子写入。CLI `je_auto_control failure-bundle out.zip`;`codegen --failure-bundle` 让生成的 pytest 自动包上失败诊断 - **USB Hotplug 事件** — 轮询式 hotplug 监测(`UsbHotplugWatcher`),含 bounded ring buffer 与带序号的事件;`GET /usb/events?since=N` 让晚加入的订阅者补上进度。USB 分页有自动刷新切换钮。 - **OpenAPI 3.1 + Swagger UI** — `GET /openapi.json`(auth-gated,从活的路由表生成)+ `GET /docs`(浏览器版 Swagger UI 含 bearer token 栏)。CI 上有 drift 测试,新加路由忘记写 metadata 会被拦下。 -- **配置包导出/导入** — 单一 JSON 文件,导出/导入用户配置(admin hosts、address book、trusted viewers、known hosts、host service、IDs)。原子写入加 `.bak.<时间戳>` 备份;CLI `python -m je_auto_control.utils.config_bundle export|import`;`POST /config/{export,import}`;REST API 分页有按钮。 +- **配置包导出/导入** — 单一 JSON 文件,导出/导入用户配置(admin hosts、address book、trusted viewers、known hosts、host service、IDs)。原子写入加 `.bak.<时间戳>` 备份;CLI `python -m je_auto_control.utils.config_bundle export|import`;`POST /config/{export,import}`;REST API 分页的导出/导入命令位于窗口的 Actions 菜单。 - **USB Passthrough(需主动启用)** — 让远端 viewer 使用实体插在 host 上的 USB 设备,走 WebRTC `usb` DataChannel。Wire-level 协议(11 个 opcode 含 `RESUME`、CREDIT 流量控制、16 KiB payload 上限,超量传输以 EOF 分片)。八个原始未决问题全部解决:可靠有序 channel、LIST 走 channel(ACL 过滤)、per-claim credit、Linux kernel driver detach/reattach、ACL **HMAC-SHA256 完整性**(篡改 fail-closed;密钥可插拔 — Windows DPAPI 或 passphrase vault)。**Backend:**`LibusbBackend`(production)、`WinusbBackend`(ctypes)、`IokitBackend`(原生 IOKit 列举 + libusb 传输)— Windows/macOS *硬件未验证*;`default_passthrough_backend()` 依 OS 自动挑。Viewer 端阻塞式 client(`control/bulk/interrupt_transfer`、`list_devices`、`resume`);in-process `UsbLoopback` 让同机可走完整堆栈 share+use。**已接入 WebRTC** host/viewer(`viewer.usb_client()`)并含断线可续租的 **resume token**。持久化 ACL(默认 deny、mode 0600),含 host 端 prompt 对话框、滥用 **rate-limit / lockout** 与可检测篡改审计整合。五个驱动面:AnyDesk 风 **GUI 面板**(分享 + ACL 允许/封锁 + 本机/远端使用)、`AC_usb_*` executor 命令(JSON / socket / 调度器)、**REST** `/usb/...`、一级 **MCP** `ac_usb_*` 工具、以及 Python API。默认 off — 用 `enable_usb_passthrough(True)` 或 `JE_AUTOCONTROL_USB_PASSTHROUGH=1` 启用;默认启用仍待 Phase 2e 外部安全签核 + 实机硬件验证。 --- @@ -529,7 +532,7 @@ ac.run_from_description("打开记事本并输入 hello", executor=executor) | `AUTOCONTROL_LLM_BACKEND` | 强制指定 `anthropic` | | `AUTOCONTROL_LLM_MODEL` | 覆盖默认模型(如 `claude-opus-4-7`) | -GUI:**LLM Planner** 分页 — 描述输入框、`QThread` 后台执行的 *Plan* 按钮、预览指令清单,以及 *Run plan* 按钮。 +GUI:**LLM Planner** 分页 — 描述输入框与指令清单预览;*Plan*(`QThread` 后台执行)与 *Run plan* 位于窗口的 Actions 菜单。 ### 运行期变量与流程控制 @@ -1004,8 +1007,8 @@ for run in default_history_store.list_runs(limit=20): print(run.id, run.source, run.status, run.artifact_path) ``` -GUI **执行历史** 标签页提供筛选 / 刷新 / 清除功能,并可双击截图列打开 -附件。 +GUI **执行历史** 标签页显示运行记录表格,可双击截图列打开附件;筛选 / +刷新 / 清除命令位于窗口的 Actions 菜单。 ### 报告生成 @@ -1237,6 +1240,13 @@ je_auto_control.start_autocontrol_gui() python -m je_auto_control ``` +主窗口采用菜单驱动设计:标签页只保留输入字段、表格与结果视图,每个 +标签页的命令都集中在窗口级的 **Actions** 菜单,会随当前标签页动态重建。 +**View → Tabs** 可按分类(核心 / 编辑 / 检测与视觉 / 自动化引擎 / 系统) +显示或隐藏约 48 个已注册标签页;默认布局只打开录制、脚本构建器与远程 +桌面三个标签页。**View → Text Size** 提供自动/预设字号,**Language** +菜单(English / 繁體中文 / 简体中文 / 日本語)可即时切换整个窗口的语言。 + --- ## 命令行界面 diff --git a/README/README_zh-TW.md b/README/README_zh-TW.md index 6812e988..a5fbdd54 100644 --- a/README/README_zh-TW.md +++ b/README/README_zh-TW.md @@ -56,7 +56,9 @@ ## 本次更新 -所有各版本更新說明已移至 **[WHATS_NEW_zh-TW.md](WHATS_NEW_zh-TW.md)**。 +**最新(2026-07-18)— 跨平台穩定性強化。** 一次全專案執行期稽核修正了 macOS/Windows/Linux/Wayland 各後端、執行器,以及遠端桌面/USB 堆疊的執行期缺陷——包含正確的 Retina 游標座標運算、CPython 3.14 上的中繼卡死、`AC_expect_poll`/`AC_parallel` 的健全性、USB/IP 預設綁定本機,以及在 I/O 邊界保留型別化例外——每項皆有 headless 回歸測試涵蓋。無 API 變更。 + +各版本更新說明詳見 **[WHATS_NEW.md](../WHATS_NEW.md)**。 ## 功能特色 @@ -97,9 +99,10 @@ - **WebRTC 封包監測** — 由既有 WebRTC stats 輪詢餵入的程序級 `StatsSnapshot` 滾動視窗(預設 600 筆 / 1 Hz 約 10 分鐘)。對 RTT、FPS、bitrate、封包遺失、jitter 各回 `last/min/max/avg/p95` - **USB 裝置列舉** — 唯讀的跨平台 USB 裝置列舉。優先嘗試 pyusb(libusb);若無則退回平台特定指令(Windows `Get-PnpDevice`、macOS `system_profiler`、Linux `/sys/bus/usb/devices`)。第二階段 passthrough 建構於此(見下) - **系統診斷** — 一鍵「目前正常嗎?」探測:平台、選用相依套件、executor 指令數、稽核鏈、截圖、滑鼠、硬碟空間、REST registry。CLI 全綠 exit 0/否則 1;REST `/diagnose`;依嚴重度上色的 GUI 分頁 +- **穩定 API 與失敗診斷包** — 給新整合用的版本化、延遲載入 `je_auto_control.api` 門面(`execute_action`、`generate_code`、`run_diagnostics`、failure bundles),附[生命週期政策](../docs/API_LIFECYCLE.md)。可攜式 `autocontrol.failure-bundle/v1` 診斷 ZIP:manifest + 已遮罩的 context/events/log 尾段、可選截圖與診斷、best-effort 收集器、原子寫入。CLI `je_auto_control failure-bundle out.zip`;`codegen --failure-bundle` 讓產生的 pytest 自動包上失敗診斷 - **USB Hotplug 事件** — 輪詢式 hotplug 監測(`UsbHotplugWatcher`),含 bounded ring buffer 與帶序號的事件;`GET /usb/events?since=N` 讓晚加入的訂閱者補上進度。USB 分頁有自動更新切換鈕。 - **OpenAPI 3.1 + Swagger UI** — `GET /openapi.json`(auth-gated,從活的路由表生成)+ `GET /docs`(瀏覽器版 Swagger UI 含 bearer token 列)。CI 上有 drift 測試,新加路由忘記寫 metadata 會被擋下。 -- **設定包匯出/匯入** — 單一 JSON 檔,匯出/匯入使用者設定(admin hosts、address book、trusted viewers、known hosts、host service、IDs)。原子寫入加 `.bak.<時間戳>` 備份;CLI `python -m je_auto_control.utils.config_bundle export|import`;`POST /config/{export,import}`;REST API 分頁有按鈕。 +- **設定包匯出/匯入** — 單一 JSON 檔,匯出/匯入使用者設定(admin hosts、address book、trusted viewers、known hosts、host service、IDs)。原子寫入加 `.bak.<時間戳>` 備份;CLI `python -m je_auto_control.utils.config_bundle export|import`;`POST /config/{export,import}`;REST API 分頁的匯出/匯入指令位於視窗的 Actions 選單。 - **USB Passthrough(需主動啟用)** — 讓遠端 viewer 使用實體插在 host 上的 USB 裝置,走 WebRTC `usb` DataChannel。Wire-level 協定(11 個 opcode 含 `RESUME`、CREDIT 流量控制、16 KiB payload 上限,超量傳輸以 EOF 分片)。八個原始未決問題全部解決:可靠有序 channel、LIST 走 channel(ACL 過濾)、per-claim credit、Linux kernel driver detach/reattach、ACL **HMAC-SHA256 完整性**(竄改 fail-closed;金鑰可插拔 — Windows DPAPI 或 passphrase vault)。**Backend:**`LibusbBackend`(production)、`WinusbBackend`(ctypes)、`IokitBackend`(原生 IOKit 列舉 + libusb 傳輸)— Windows/macOS *硬體未驗證*;`default_passthrough_backend()` 依 OS 自動挑。Viewer 端阻塞式 client(`control/bulk/interrupt_transfer`、`list_devices`、`resume`);in-process `UsbLoopback` 讓同機可走完整堆疊 share+use。**已接入 WebRTC** host/viewer(`viewer.usb_client()`)並含斷線可續租的 **resume token**。持久化 ACL(預設 deny、mode 0600),含 host 端 prompt 對話框、濫用 **rate-limit / lockout** 與可偵測竄改稽核整合。五個驅動面:AnyDesk 風 **GUI 面板**(分享 + ACL 允許/封鎖 + 本機/遠端使用)、`AC_usb_*` executor 指令(JSON / socket / 排程器)、**REST** `/usb/...`、一級 **MCP** `ac_usb_*` 工具、以及 Python API。預設 off — 用 `enable_usb_passthrough(True)` 或 `JE_AUTOCONTROL_USB_PASSTHROUGH=1` 開啟;預設啟用仍待 Phase 2e 外部安全簽核 + 實機硬體驗證。 --- @@ -529,7 +532,7 @@ ac.run_from_description("開記事本,輸入 hello", executor=executor) | `AUTOCONTROL_LLM_BACKEND` | 強制指定 `anthropic` | | `AUTOCONTROL_LLM_MODEL` | 覆寫預設模型(如 `claude-opus-4-7`) | -GUI:**LLM Planner** 分頁 — 描述輸入框、`QThread` 背景執行的 *Plan* 按鈕、預覽指令清單,以及 *Run plan* 按鈕。 +GUI:**LLM Planner** 分頁 — 描述輸入框與指令清單預覽;*Plan*(`QThread` 背景執行)與 *Run plan* 位於視窗的 Actions 選單。 ### 執行期變數與流程控制 @@ -1004,8 +1007,8 @@ for run in default_history_store.list_runs(limit=20): print(run.id, run.source, run.status, run.artifact_path) ``` -GUI **執行歷史** 分頁提供篩選 / 更新 / 清除功能,並可雙擊截圖欄位開啟 -附件。 +GUI **執行歷史** 分頁顯示執行紀錄表格,可雙擊截圖欄位開啟附件;篩選 / +更新 / 清除指令位於視窗的 Actions 選單。 ### 報告產生 @@ -1237,6 +1240,13 @@ je_auto_control.start_autocontrol_gui() python -m je_auto_control ``` +主視窗採選單驅動設計:分頁只保留輸入欄位、表格與結果檢視,每個分頁的 +指令都集中在視窗層級的 **Actions** 選單,會隨當前分頁動態重建。 +**View → Tabs** 可依分類(核心 / 編輯 / 偵測與視覺 / 自動化引擎 / 系統) +顯示或隱藏約 48 個已註冊分頁;預設版面只開啟錄製、腳本建構器與遠端桌面 +三個分頁。**View → Text Size** 提供自動/預設字級,**Language** 選單 +(English / 繁體中文 / 简体中文 / 日本語)可即時切換整個視窗的語系。 + --- ## 命令列介面 diff --git a/README/WHATS_NEW_zh-CN.md b/README/WHATS_NEW_zh-CN.md index 519be585..ba7e4e4b 100644 --- a/README/WHATS_NEW_zh-CN.md +++ b/README/WHATS_NEW_zh-CN.md @@ -1,5 +1,20 @@ # 本次更新 — AutoControl +## 本次更新 (2026-07-03) — 稳定 API、失败诊断包与发布工程 + +给新集成用的版本化入口点、便携式失败诊断格式,以及强化后的发布管线。完整参考:[`docs/API_LIFECYCLE.md`](../docs/API_LIFECYCLE.md) 与 [`docs/CAPABILITY_MATRIX.md`](../docs/CAPABILITY_MATRIX.md)。 + +- **稳定 `je_auto_control.api` 门面**:小巧、延迟加载、有类型的命名空间(`execute_action`、`execute_action_with_vars`、`generate_code`、`run_diagnostics`、failure bundles),新的使用者导入核心自动化时不必连带加载数百个可选集成。受书面生命周期政策管辖——移除稳定 API 需要弃用警告加两个 minor 版本——并由 `utils/deprecation.deprecated` 提供一致、带元数据的警告。CI 以 mypy 检查此接口类型,并在 Windows/Ubuntu/macOS × Python 3.10/3.14 矩阵上冒烟测试导入。 +- **失败诊断包**(`create_failure_bundle` / `failure_bundle_on_error`,CLI `je_auto_control failure-bundle out.zip`):一个原子写入、自足的 `autocontrol.failure-bundle/v1` ZIP,用于诊断失败的运行——含运行环境信息的 manifest、已脱敏的 error/context/events、已脱敏的 log 尾段、可选截图与诊断报告、opt-in 附件。收集器为 best-effort:截图或诊断探测坏掉会记进 `collector_failures` 而不是丢失整个诊断包。`codegen --failure-bundle` 让生成的 pytest 流程自动封存自己的失败证据。秘密脱敏现在也会掩蔽明确的 `key=value` / `Authorization: Bearer` 凭证语法,不论其熵值高低。 +- **发布工程**:发布从 push-to-main 改为不可变的 `v*` 标签——新的 `release.yml` 验证标签与包版本一致、构建、冒烟测试 wheel、附上构建来源证明(provenance attestation),并经 PyPI Trusted Publishing 发布。`quality.yml` 新增 dependency review、覆盖率下限(fail-under 35,分支覆盖)与稳定 API 的 mypy 关卡;新的 platform-smoke workflow 在三个操作系统上演练稳定 API。 +- **项目文档**:新增 [`SECURITY.md`](../SECURITY.md)(私密安全通报、响应时限、操作默认值)、[`CHANGELOG.md`](../CHANGELOG.md)(Keep-a-Changelog 兼容性记录)、API 生命周期政策与能力/平台支持矩阵。Sphinx 索引补齐 v182–v223 两种语言的功能文档。 + +## 本次更新 (2026-07-02) — 菜单驱动 GUI:Actions 菜单取代标签页内按钮 + +每个标签页的命令现在集中在一个可预期的位置。窗口菜单栏新增动态 **Actions** 菜单,会随当前标签页重建;标签页只保留输入字段、表格与结果/状态视图,不再是一排排按钮。完整参考:[`docs/source/Zh/doc/new_features/v223_features_doc.rst`](../docs/source/Zh/doc/new_features/v223_features_doc.rst)。 + +- **窗口级 Actions 菜单**:核心标签页在注册时声明命令;功能标签页提供 `menu_actions()` 挂钩,返回 `(label_key, handler)` 配对。48 个已注册标签页中有 46 个以此方式呈现命令——Script Builder 与 Remote Desktop 刻意保留交互式面板布局,菜单在该处显示占位信息。窗口级菜单无法取代的按钮维持原位(堆叠触发器表单内的逐页浏览按钮、随可见性切换的数据源浏览按钮、有状态的自动刷新复选框)。无头回归测试守护此契约,标签页不可能默默失去其命令。 + ## 本次更新 (2026-06-24) — 扩充 UIA 控制模式(展开 / 选取 / 范围 / 滚动) 以原生模式驱动树节点、列表/下拉项目、滑块与滚动,而非像素猜测。完整参考:[`docs/source/Zh/doc/new_features/v181_features_doc.rst`](../docs/source/Zh/doc/new_features/v181_features_doc.rst)。 diff --git a/README/WHATS_NEW_zh-TW.md b/README/WHATS_NEW_zh-TW.md index bfd2f407..3ba94c5a 100644 --- a/README/WHATS_NEW_zh-TW.md +++ b/README/WHATS_NEW_zh-TW.md @@ -1,5 +1,20 @@ # 本次更新 — AutoControl +## 本次更新 (2026-07-03) — 穩定 API、失敗診斷包與發佈工程 + +給新整合用的版本化進入點、可攜式失敗診斷格式,以及強化後的發佈管線。完整參考:[`docs/API_LIFECYCLE.md`](../docs/API_LIFECYCLE.md) 與 [`docs/CAPABILITY_MATRIX.md`](../docs/CAPABILITY_MATRIX.md)。 + +- **穩定 `je_auto_control.api` 門面**:小巧、延遲載入、有型別的命名空間(`execute_action`、`execute_action_with_vars`、`generate_code`、`run_diagnostics`、failure bundles),新的使用者匯入核心自動化時不必連帶載入數百個選用整合。受書面生命週期政策管轄——移除穩定 API 需要棄用警告加兩個 minor 版本——並由 `utils/deprecation.deprecated` 提供一致、帶中繼資料的警告。CI 以 mypy 檢查此介面型別,並在 Windows/Ubuntu/macOS × Python 3.10/3.14 矩陣上煙霧測試匯入。 +- **失敗診斷包**(`create_failure_bundle` / `failure_bundle_on_error`,CLI `je_auto_control failure-bundle out.zip`):一個原子寫入、自足的 `autocontrol.failure-bundle/v1` ZIP,用於診斷失敗的執行——含執行環境資訊的 manifest、已遮罩的 error/context/events、已遮罩的 log 尾段、可選截圖與診斷報告、opt-in 附件。收集器為 best-effort:截圖或診斷探測壞掉會記進 `collector_failures` 而不是丟失整個診斷包。`codegen --failure-bundle` 讓產生的 pytest 流程自動封存自己的失敗證據。秘密遮罩現在也會遮蔽明確的 `key=value` / `Authorization: Bearer` 憑證語法,不論其熵值高低。 +- **發佈工程**:發佈從 push-to-main 改為不可變的 `v*` 標籤——新的 `release.yml` 驗證標籤與套件版本一致、建置、煙霧測試 wheel、附上建置來源證明(provenance attestation),並經 PyPI Trusted Publishing 發佈。`quality.yml` 新增 dependency review、覆蓋率下限(fail-under 35,分支覆蓋)與穩定 API 的 mypy 關卡;新的 platform-smoke workflow 在三個作業系統上演練穩定 API。 +- **專案文件**:新增 [`SECURITY.md`](../SECURITY.md)(私密安全通報、回應時限、操作預設值)、[`CHANGELOG.md`](../CHANGELOG.md)(Keep-a-Changelog 相容性紀錄)、API 生命週期政策與能力/平台支援矩陣。Sphinx 索引補齊 v182–v223 兩種語言的功能文件。 + +## 本次更新 (2026-07-02) — 選單驅動 GUI:Actions 選單取代分頁內按鈕 + +每個分頁的指令現在集中在一個可預期的位置。視窗選單列新增動態 **Actions** 選單,會隨當前分頁重建;分頁只保留輸入欄位、表格與結果/狀態檢視,不再是一排排按鈕。完整參考:[`docs/source/Zh/doc/new_features/v223_features_doc.rst`](../docs/source/Zh/doc/new_features/v223_features_doc.rst)。 + +- **視窗層級 Actions 選單**:核心分頁在註冊時宣告指令;功能分頁提供 `menu_actions()` 掛鉤,回傳 `(label_key, handler)` 配對。48 個已註冊分頁中有 46 個以此方式呈現指令——Script Builder 與 Remote Desktop 刻意保留互動式面板版面,選單在該處顯示佔位訊息。視窗層級選單無法取代的按鈕維持原位(堆疊觸發器表單內的逐頁瀏覽按鈕、隨可見性切換的資料來源瀏覽按鈕、有狀態的自動更新核取方塊)。無頭迴歸測試守護此契約,分頁不可能默默失去其指令。 + ## 本次更新 (2026-06-24) — 擴充 UIA 控制模式(展開 / 選取 / 範圍 / 捲動) 以原生模式驅動樹節點、清單/下拉項目、滑桿與捲動,而非像素猜測。完整參考:[`docs/source/Zh/doc/new_features/v181_features_doc.rst`](../docs/source/Zh/doc/new_features/v181_features_doc.rst)。 diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..96944596 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,25 @@ +# Security policy + +## Supported versions + +Security fixes are provided for the latest PyPI release and the `main` branch. +Experimental capabilities listed in the capability matrix receive best-effort +fixes and are not covered by compatibility guarantees. + +## Reporting a vulnerability + +Do not open a public issue. Use GitHub's **Report a vulnerability** private +advisory for this repository. Include affected versions, platform, impact, +reproduction steps, and any suggested mitigation. Avoid attaching credentials, +tokens, unredacted logs, or screenshots containing personal data. + +The maintainers aim to acknowledge a report within 3 business days, provide an +initial assessment within 7 business days, and coordinate disclosure after a +fix is available. There is no bug-bounty promise. + +## Operational defaults + +Network listeners must bind to loopback unless explicitly configured, remote +control and USB passthrough remain opt-in, and diagnostic bundles redact secret +values by default. Operators remain responsible for access control, TLS, log +retention, and reviewing attachments before sharing them. diff --git a/WHATS_NEW.md b/WHATS_NEW.md index ce781384..c878728c 100644 --- a/WHATS_NEW.md +++ b/WHATS_NEW.md @@ -1,5 +1,39 @@ # What's New — AutoControl +## What's new (2026-07-18) + +### Cross-Platform Reliability Hardening + +A full-project runtime audit swept every platform backend and utility for execution-time defects and unexpected behaviour, adding a headless regression test for each fix. There are **no API changes** — existing scripts keep working, they just fail less and behave correctly in more places. + +- **macOS (Retina / HiDPI)**: mouse-position reads and omitted-coordinate clicks now land at the correct point. The cursor y-flip used a *pixel*-based display height against a *point*-based cursor, offsetting every implicit click on a 2× display. +- **Remote-desktop relay**: fixed a hang on **Linux + CPython 3.14** where a paired pipe never exited after one peer disconnected — a cross-thread `shutdown()` no longer reliably wakes a blocked `recv()` there, so the pump now polls readability with `select()` and always re-checks its stop flag. +- **USB/IP server** now binds `127.0.0.1` by default (least-privilege); export the device to the LAN with an explicit `host="0.0.0.0"`. +- **Executor**: `AC_expect_poll` no longer crashes on a not-ready value (missing result key or a transiently-failing action) and keeps polling; `AC_parallel` branches are properly variable-scope-isolated, so a nested `AC_execute_action` can't race on the parent's scope; a malformed `run_suite` spec reports a clean error instead of aborting the run. +- **Windows Interception backend**: send-to-window click now performs a real press/release instead of silently no-opping on the button tuple. +- **Wayland**: a partial-coordinate `mouse_scroll` degrades gracefully instead of raising `NotImplementedError`. +- **Typed exceptions preserved at boundaries**: saving an action file with non-encodable text raises `AutoControlJsonActionException` (not a raw `UnicodeEncodeError`); a non-ASCII USB/IP busid no longer kills the client worker thread; SQLite data-source / query connections are always closed; USB ACL rule removal is case-insensitive to match the (case-insensitive) rule matching. +- Builds on the round-3 sweep that reparented the exception family under `AutoControlException` (assertions still propagate under `raise_on_error=False`) and hardened thread-safety across the socket server, scheduler, triggers, and MCP server. + +## What's new (2026-07-03) + +### Stable API, Failure Bundles, and Release Engineering + +A versioned entry point for new integrations, a portable failure-diagnostics format, and a hardened release pipeline. Full reference: [`docs/API_LIFECYCLE.md`](docs/API_LIFECYCLE.md) and [`docs/CAPABILITY_MATRIX.md`](docs/CAPABILITY_MATRIX.md). + +- **Stable `je_auto_control.api` façade**: small, lazy, typed namespace (`execute_action`, `execute_action_with_vars`, `generate_code`, `run_diagnostics`, failure bundles) so new consumers can import core automation without eagerly loading hundreds of optional integrations. Governed by a written lifecycle policy — stable removals need a deprecation warning plus two minor releases — with `utils/deprecation.deprecated` supplying consistent, metadata-carrying warnings. CI type-checks this surface with mypy and smoke-imports it on a Windows/Ubuntu/macOS × Python 3.10/3.14 matrix. +- **Failure bundles** (`create_failure_bundle` / `failure_bundle_on_error`, CLI `je_auto_control failure-bundle out.zip`): one atomic, self-contained `autocontrol.failure-bundle/v1` ZIP for diagnosing a failed run — manifest with runtime info, redacted error/context/events, redacted log tail, optional screenshot and diagnostics report, opt-in attachments. Collectors are best-effort: a broken screen grab or diagnostics probe is recorded in `collector_failures` instead of losing the bundle. `codegen --failure-bundle` wraps generated pytest flows so every generated test archives its own failure evidence. Secret redaction now also masks explicit `key=value` / `Authorization: Bearer` credential syntax regardless of entropy. +- **Release engineering**: publishing moves from push-to-main to immutable `v*` tags — the new `release.yml` verifies the tag matches the package version, builds, smoke-tests the wheel, attests build provenance, and publishes via PyPI Trusted Publishing. `quality.yml` gains dependency review, a coverage floor (fail-under 35, branch coverage), and a mypy gate on the stable API; a new platform-smoke workflow exercises the stable API on all three OSes. +- **Project docs**: new [`SECURITY.md`](SECURITY.md) (private-advisory reporting, response targets, operational defaults), [`CHANGELOG.md`](CHANGELOG.md) (Keep-a-Changelog compatibility record), API lifecycle policy, and a capability/platform support matrix. The Sphinx indexes catch up on v182–v223 feature docs in both languages. + +## What's new (2026-07-02) + +### Menu-Driven GUI: the Actions Menu Replaces In-Tab Buttons + +Every tab's commands now live in one predictable place. The window menu bar gains a dynamic **Actions** menu that rebuilds for the active tab; tabs keep only their inputs, tables, and result/status views instead of rows of buttons. Full reference: [`docs/source/Eng/doc/new_features/v223_features_doc.rst`](docs/source/Eng/doc/new_features/v223_features_doc.rst). + +- **Window-level Actions menu**: core tabs declare their commands at registration; feature tabs expose a `menu_actions()` hook returning `(label_key, handler)` pairs. 46 of 48 registered tabs now surface their commands this way — Script Builder and Remote Desktop intentionally keep their interactive panel layouts, and the menu shows a placeholder there. Buttons a window-level menu cannot replace stay in place (per-page browse buttons inside stacked trigger forms, the visibility-toggled data-source browse button, stateful auto-refresh checkboxes). A headless regression test guards the contract so no tab can silently lose its commands. + ## What's new (2026-06-26) ### Trial and Force Action Modes (Playwright-style) diff --git a/benchmarks/core_latency.py b/benchmarks/core_latency.py new file mode 100644 index 00000000..bdb72271 --- /dev/null +++ b/benchmarks/core_latency.py @@ -0,0 +1,31 @@ +"""Repeatable smoke benchmark for stable headless entry points.""" +import json +import statistics +import time + + +def _measure(callable_, repeats=20): + samples = [] + for _ in range(repeats): + start = time.perf_counter() + callable_() + samples.append((time.perf_counter() - start) * 1000) + ordered = sorted(samples) + return { + "median_ms": round(statistics.median(samples), 3), + "p95_ms": round(ordered[max(0, int(len(ordered) * .95) - 1)], 3), + } + + +def main(): + import je_auto_control.api as ac + results = { + "diagnostics_ms": _measure(ac.run_diagnostics, repeats=5), + "codegen_ms": _measure( + lambda: ac.generate_code([["AC_screen_size"]]), repeats=20), + } + print(json.dumps(results, indent=2, sort_keys=True)) + + +if __name__ == "__main__": + main() diff --git a/dev_requirements.txt b/dev_requirements.txt index 8ac5598d..ba452fa3 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -15,8 +15,10 @@ python-docx==1.2.0 python-pptx==1.0.2 # Quality tooling — used by .github/workflows/quality.yml and locally. -ruff==0.15.14 +ruff==0.15.22 bandit==1.9.4 -pytest==9.0.3 +pytest==9.1.1 pytest-timeout==2.4.0 pytest-rerunfailures==15.1 +pytest-cov>=6.0 +mypy>=1.15 diff --git a/docs/API_LIFECYCLE.md b/docs/API_LIFECYCLE.md new file mode 100644 index 00000000..11058c61 --- /dev/null +++ b/docs/API_LIFECYCLE.md @@ -0,0 +1,17 @@ +# Public API lifecycle + +The supported entry point for new integrations is `je_auto_control.api`. +Everything reachable only through `je_auto_control.utils` is internal unless a +document explicitly says otherwise. The historical top-level package remains +available for compatibility but is not expanded with new integrations. + +- Stable API removal requires a deprecation warning and two minor releases. +- Beta API removal requires one release note and one minor release. +- Experimental API may change in any release and must be labelled as such. +- Deprecations state the version introduced, planned removal version, and + replacement. Use `je_auto_control.utils.deprecation.deprecated`. +- Breaking changes and migrations are recorded in `CHANGELOG.md`. + +The project remains pre-1.0. A 1.0 release requires passing stable capability +tests on every claimed platform, documented recovery/diagnostic behavior, and +no unresolved critical security advisories. diff --git a/docs/CAPABILITY_MATRIX.md b/docs/CAPABILITY_MATRIX.md new file mode 100644 index 00000000..e4c7e063 --- /dev/null +++ b/docs/CAPABILITY_MATRIX.md @@ -0,0 +1,23 @@ +# Capability matrix + +Status meanings: **stable** is compatibility-supported, **beta** is suitable +for evaluation with documented limitations, and **experimental** may change +without a compatibility window. + +| Capability | Status | Windows | Linux X11 | Linux Wayland | macOS | +|---|---|---:|---:|---:|---:| +| Mouse, keyboard, screenshot | stable | CI | CI/Xvfb | partial | implementation | +| JSON executor and variables | stable | CI | CI | CI | platform-neutral | +| Image and anchor locators | beta | CI | CI | screenshot-only | implementation | +| Accessibility locator | beta | CI | backend tests | unavailable | backend tests | +| Recorder | beta | CI | implementation | unavailable | unavailable | +| Reports, trace, failure bundle | stable | CI | CI | CI | platform-neutral | +| REST, MCP, scheduler | beta | CI | CI | CI | platform-neutral | +| Remote desktop / WebRTC | beta | tests | tests | tests | tests | +| Android and iOS bridges | experimental | mocked CI | mocked CI | mocked CI | mocked CI | +| LLM/VLM agents | experimental | fake-backend CI | fake-backend CI | fake-backend CI | fake-backend CI | +| USB passthrough | experimental | hardware-unverified | backend tests | backend tests | hardware-unverified | + +“Implementation” means code exists but the repository does not currently run a +real OS runner for it. It must not be interpreted as a production guarantee. +Hardware-backed results and known limitations should be attached to releases. diff --git a/docs/source/Eng/doc/new_features/v223_features_doc.rst b/docs/source/Eng/doc/new_features/v223_features_doc.rst new file mode 100644 index 00000000..ef5264f6 --- /dev/null +++ b/docs/source/Eng/doc/new_features/v223_features_doc.rst @@ -0,0 +1,48 @@ +Menu-Driven GUI: the Actions Menu Replaces In-Tab Buttons +========================================================= + +The main window is redesigned around a menu bar and a low-button tab layout. +Tabs keep only their inputs, tables, and result/status views; every tab's +commands move to one predictable place — a window-level **Actions** menu that +rebuilds for the active tab. + +The Actions menu +---------------- + +Two ways a tab surfaces its commands: + +* **Registry actions** — core tabs (Auto Click, Screenshot, Image Detection, + Record, Script Executor, Report) declare ``(label_key, handler)`` pairs when + they are registered in ``gui/main_widget.py``. +* **The** ``menu_actions()`` **hook** — feature tabs expose a + ``menu_actions()`` method returning the same ``[(label_key, handler), ...]`` + shape; the menu bar queries the active tab and renders whatever it returns. + +46 of 48 registered tabs surface their commands this way. **Script Builder** +and **Remote Desktop** intentionally keep their interactive panel layouts, and +the Actions menu shows a placeholder there. Controls a window-level menu cannot +replace stay in place: per-page browse buttons inside stacked trigger forms, +the visibility-toggled data-source browse button, and stateful auto-refresh +checkboxes. + +The View menu +------------- + +* **View → Tabs** shows or hides any registered tab, grouped by category + (Core / Editing / Detection & Vision / Automation Engines / System). The + default layout opens with just Record, Script Builder, and Remote Desktop; + everything else is one menu click away. Tabs are closable — closing one is + the same as unchecking it in the View menu. +* **View → Text Size** offers auto (screen-height based) and preset font + sizes applied live. + +The contract test +----------------- + +``test/unit_test/headless/test_actions_menu_gui.py`` guards the contract: every +registered tab must expose commands through registry actions or a +``menu_actions()`` hook (the two exempt tabs aside), and every entry must be a +non-empty ``label_key`` string paired with a callable. A new tab that forgets +the hook fails CI instead of silently shipping with no reachable commands. The +probe runs the full widget construction in a subprocess so the Qt lifetime +cannot destabilise the rest of the headless suite. diff --git a/docs/source/Eng/eng_index.rst b/docs/source/Eng/eng_index.rst index 10f84a13..e3f679e6 100644 --- a/docs/source/Eng/eng_index.rst +++ b/docs/source/Eng/eng_index.rst @@ -204,6 +204,48 @@ Comprehensive guides for all AutoControl features. doc/new_features/v179_features_doc doc/new_features/v180_features_doc doc/new_features/v181_features_doc + doc/new_features/v182_features_doc + doc/new_features/v183_features_doc + doc/new_features/v184_features_doc + doc/new_features/v185_features_doc + doc/new_features/v186_features_doc + doc/new_features/v187_features_doc + doc/new_features/v188_features_doc + doc/new_features/v189_features_doc + doc/new_features/v190_features_doc + doc/new_features/v191_features_doc + doc/new_features/v192_features_doc + doc/new_features/v193_features_doc + doc/new_features/v194_features_doc + doc/new_features/v195_features_doc + doc/new_features/v196_features_doc + doc/new_features/v197_features_doc + doc/new_features/v198_features_doc + doc/new_features/v199_features_doc + doc/new_features/v200_features_doc + doc/new_features/v201_features_doc + doc/new_features/v202_features_doc + doc/new_features/v203_features_doc + doc/new_features/v204_features_doc + doc/new_features/v205_features_doc + doc/new_features/v206_features_doc + doc/new_features/v207_features_doc + doc/new_features/v208_features_doc + doc/new_features/v209_features_doc + doc/new_features/v210_features_doc + doc/new_features/v211_features_doc + doc/new_features/v212_features_doc + doc/new_features/v213_features_doc + doc/new_features/v214_features_doc + doc/new_features/v215_features_doc + doc/new_features/v216_features_doc + doc/new_features/v217_features_doc + doc/new_features/v218_features_doc + doc/new_features/v219_features_doc + doc/new_features/v220_features_doc + doc/new_features/v221_features_doc + doc/new_features/v222_features_doc + doc/new_features/v223_features_doc doc/ocr_backends/ocr_backends_doc doc/observability/observability_doc doc/operations_layer/operations_layer_doc diff --git a/docs/source/Zh/doc/new_features/v223_features_doc.rst b/docs/source/Zh/doc/new_features/v223_features_doc.rst new file mode 100644 index 00000000..4b872c04 --- /dev/null +++ b/docs/source/Zh/doc/new_features/v223_features_doc.rst @@ -0,0 +1,42 @@ +選單驅動 GUI:Actions 選單取代分頁內按鈕 +========================================= + +主視窗改以選單列與低按鈕分頁版面重新設計。分頁只保留輸入欄位、表格與 +結果/狀態檢視;每個分頁的指令移到一個可預期的位置——會隨當前分頁動態 +重建的視窗層級 **Actions** 選單。 + +Actions 選單 +------------ + +分頁有兩種方式呈現其指令: + +* **註冊時宣告**——核心分頁(自動點擊、截圖、影像偵測、錄製、腳本執行器、 + 報告)在 ``gui/main_widget.py`` 註冊時宣告 ``(label_key, handler)`` 配對。 +* ``menu_actions()`` **掛鉤**——功能分頁提供 ``menu_actions()`` 方法, + 回傳相同的 ``[(label_key, handler), ...]`` 形狀;選單列查詢當前分頁並 + 渲染其回傳內容。 + +48 個已註冊分頁中有 46 個以此方式呈現指令。**Script Builder** 與 +**Remote Desktop** 刻意保留其互動式面板版面,Actions 選單在這兩頁顯示 +佔位訊息。視窗層級選單無法取代的控制項則維持原位:堆疊觸發器表單內的 +逐頁瀏覽按鈕、隨可見性切換的資料來源瀏覽按鈕,以及有狀態的自動更新 +核取方塊。 + +View 選單 +--------- + +* **View → Tabs** 可依分類(核心 / 編輯 / 偵測與視覺 / 自動化引擎 / 系統) + 顯示或隱藏任一已註冊分頁。預設版面只開啟錄製、Script Builder 與遠端 + 桌面;其餘分頁一個選單點擊即可叫出。分頁可關閉——關閉等同於在 View + 選單取消勾選。 +* **View → Text Size** 提供自動(依螢幕高度)與預設字級,即時套用。 + +契約測試 +-------- + +``test/unit_test/headless/test_actions_menu_gui.py`` 守護此契約:每個已 +註冊分頁都必須透過註冊宣告或 ``menu_actions()`` 掛鉤呈現指令(兩個豁免 +分頁除外),且每個項目都必須是非空的 ``label_key`` 字串搭配可呼叫物件。 +新分頁若忘了掛鉤會直接讓 CI 失敗,而不是默默出貨一個沒有任何可觸及指令 +的分頁。探測程序在子行程中建構完整 widget,使 Qt 生命週期不會影響無頭 +測試套件的其餘部分。 diff --git a/docs/source/Zh/zh_index.rst b/docs/source/Zh/zh_index.rst index 3c90f5b6..b857826f 100644 --- a/docs/source/Zh/zh_index.rst +++ b/docs/source/Zh/zh_index.rst @@ -204,6 +204,48 @@ AutoControl 所有功能的完整使用指南。 doc/new_features/v179_features_doc doc/new_features/v180_features_doc doc/new_features/v181_features_doc + doc/new_features/v182_features_doc + doc/new_features/v183_features_doc + doc/new_features/v184_features_doc + doc/new_features/v185_features_doc + doc/new_features/v186_features_doc + doc/new_features/v187_features_doc + doc/new_features/v188_features_doc + doc/new_features/v189_features_doc + doc/new_features/v190_features_doc + doc/new_features/v191_features_doc + doc/new_features/v192_features_doc + doc/new_features/v193_features_doc + doc/new_features/v194_features_doc + doc/new_features/v195_features_doc + doc/new_features/v196_features_doc + doc/new_features/v197_features_doc + doc/new_features/v198_features_doc + doc/new_features/v199_features_doc + doc/new_features/v200_features_doc + doc/new_features/v201_features_doc + doc/new_features/v202_features_doc + doc/new_features/v203_features_doc + doc/new_features/v204_features_doc + doc/new_features/v205_features_doc + doc/new_features/v206_features_doc + doc/new_features/v207_features_doc + doc/new_features/v208_features_doc + doc/new_features/v209_features_doc + doc/new_features/v210_features_doc + doc/new_features/v211_features_doc + doc/new_features/v212_features_doc + doc/new_features/v213_features_doc + doc/new_features/v214_features_doc + doc/new_features/v215_features_doc + doc/new_features/v216_features_doc + doc/new_features/v217_features_doc + doc/new_features/v218_features_doc + doc/new_features/v219_features_doc + doc/new_features/v220_features_doc + doc/new_features/v221_features_doc + doc/new_features/v222_features_doc + doc/new_features/v223_features_doc doc/ocr_backends/ocr_backends_doc doc/observability/observability_doc doc/operations_layer/operations_layer_doc diff --git a/je_auto_control/api/__init__.py b/je_auto_control/api/__init__.py new file mode 100644 index 00000000..4a4bc657 --- /dev/null +++ b/je_auto_control/api/__init__.py @@ -0,0 +1,22 @@ +"""Small, versioned entry points for new integrations. + +The historical top-level package remains compatible. New consumers should +prefer this namespace so importing core automation does not eagerly import +hundreds of optional integrations. +""" + +from je_auto_control.api.core import ( + FailureBundleOptions, + create_failure_bundle, + execute_action, + execute_action_with_vars, + generate_code, + failure_bundle_on_error, + run_diagnostics, +) + +__all__ = [ + "FailureBundleOptions", "create_failure_bundle", "execute_action", + "execute_action_with_vars", "failure_bundle_on_error", "generate_code", + "run_diagnostics", +] diff --git a/je_auto_control/api/core.py b/je_auto_control/api/core.py new file mode 100644 index 00000000..f57f54c8 --- /dev/null +++ b/je_auto_control/api/core.py @@ -0,0 +1,19 @@ +"""Stable, headless AutoControl API façade.""" + +from je_auto_control.utils.codegen.codegen import generate_code +from je_auto_control.utils.diagnostics import run_diagnostics +from je_auto_control.utils.executor.action_executor import ( + execute_action, + execute_action_with_vars, +) +from je_auto_control.utils.failure_bundle import ( + FailureBundleOptions, + create_failure_bundle, + failure_bundle_on_error, +) + +__all__ = [ + "FailureBundleOptions", "create_failure_bundle", "execute_action", + "execute_action_with_vars", "failure_bundle_on_error", "generate_code", + "run_diagnostics", +] diff --git a/je_auto_control/cli.py b/je_auto_control/cli.py index 4a59595d..6864efff 100644 --- a/je_auto_control/cli.py +++ b/je_auto_control/cli.py @@ -11,6 +11,7 @@ je_auto_control fmt script.json [--check] je_auto_control record out.json [--duration 5] je_auto_control codegen script.json [--target pytest] [-o test_flow.py] + je_auto_control failure-bundle failure.zip [--error "message"] je_auto_control version je_auto_control list-jobs je_auto_control start-server --port 9938 @@ -147,11 +148,13 @@ def cmd_codegen(args: argparse.Namespace) -> int: from je_auto_control.utils.json.json_file import read_action_json if args.output: generate_code_file(args.script, args.output, target=args.target, - name=args.name, style=args.style) + name=args.name, style=args.style, + failure_bundle=args.failure_bundle) sys.stderr.write(f"Wrote {args.target} code to {args.output}\n") else: code = generate_code(read_action_json(args.script), target=args.target, - name=args.name, style=args.style) + name=args.name, style=args.style, + failure_bundle=args.failure_bundle) sys.stdout.write(code) return 0 @@ -166,6 +169,25 @@ def cmd_version(_: argparse.Namespace) -> int: return 0 +def cmd_failure_bundle(args: argparse.Namespace) -> int: + """Collect a portable, redacted diagnostic archive.""" + from je_auto_control.utils.failure_bundle import ( + FailureBundleOptions, create_failure_bundle, + ) + context = json.loads(args.context) if args.context else {} + path = create_failure_bundle( + args.output, error=args.error, context=context, + options=FailureBundleOptions( + screenshot=not args.no_screenshot, + diagnostics=not args.no_diagnostics, + log_path=args.log, + attachments=tuple(args.attach or ()), + ), + ) + sys.stdout.write(path + "\n") + return 0 + + def cmd_list_jobs(_: argparse.Namespace) -> int: from je_auto_control.utils.scheduler.scheduler import default_scheduler jobs = default_scheduler.list_jobs() @@ -253,11 +275,26 @@ def build_parser() -> argparse.ArgumentParser: default="calls") p_codegen.add_argument("--name", default="recorded_flow") p_codegen.add_argument("-o", "--output", help="Write to file instead of stdout") + p_codegen.add_argument( + "--failure-bundle", action="store_true", + help="Wrap generated pytest in automatic failure diagnostics") p_codegen.set_defaults(func=cmd_codegen) p_version = sub.add_parser("version", help="Print the installed version") p_version.set_defaults(func=cmd_version) + p_bundle = sub.add_parser( + "failure-bundle", help="Create a redacted failure diagnostic ZIP") + p_bundle.add_argument("output") + p_bundle.add_argument("--error", help="Failure summary") + p_bundle.add_argument("--context", help="JSON object with run context") + p_bundle.add_argument("--log", help="Log file whose redacted tail is included") + p_bundle.add_argument("--attach", action="append", + help="Explicit attachment; may be repeated") + p_bundle.add_argument("--no-screenshot", action="store_true") + p_bundle.add_argument("--no-diagnostics", action="store_true") + p_bundle.set_defaults(func=cmd_failure_bundle) + p_jobs = sub.add_parser("list-jobs", help="List scheduler jobs") p_jobs.set_defaults(func=cmd_list_jobs) diff --git a/je_auto_control/gui/_auto_click_tab.py b/je_auto_control/gui/_auto_click_tab.py index 684732d1..d6f9b042 100644 --- a/je_auto_control/gui/_auto_click_tab.py +++ b/je_auto_control/gui/_auto_click_tab.py @@ -1,10 +1,11 @@ from PySide6.QtGui import QIntValidator from PySide6.QtWidgets import ( - QWidget, QLineEdit, QComboBox, QPushButton, QVBoxLayout, QLabel, + QWidget, QLineEdit, QComboBox, QVBoxLayout, QLabel, QGridLayout, QHBoxLayout, QRadioButton, QButtonGroup, QMessageBox, QGroupBox, ) +from je_auto_control.utils.exception.exceptions import AutoControlException from je_auto_control.wrapper.auto_control_keyboard import ( type_keyboard, hotkey, write, get_keyboard_keys_table, ) @@ -100,29 +101,18 @@ def _build_auto_click_tab(self) -> QWidget: rh.addWidget(self.repeat_count_input) grid.addLayout(rh, row, 0, 1, 2) - row += 1 - btn_h = QHBoxLayout() - self.start_button = self._tr(QPushButton(), "start") - self.start_button.clicked.connect(self._start_auto_click) - self.stop_button = self._tr(QPushButton(), "stop") - self.stop_button.clicked.connect(self._stop_auto_click) - btn_h.addWidget(self.start_button) - btn_h.addWidget(self.stop_button) - grid.addLayout(btn_h, row, 0, 1, 2) - click_group.setLayout(grid) outer.addWidget(click_group) + # Start/stop, position probe, hotkey, write, and scroll commands all + # run from the Actions menu; the tab keeps only their inputs. pos_group = self._tr(QGroupBox(), "get_position") pos_layout = QHBoxLayout() - self.pos_btn = self._tr(QPushButton(), "get_position") - self.pos_btn.clicked.connect(self._get_mouse_pos) self.pos_label = QLabel() self._pos_label_suffix = " --" self.pos_label.setText( self._translate("current_position") + self._pos_label_suffix, ) - pos_layout.addWidget(self.pos_btn) pos_layout.addWidget(self.pos_label) pos_group.setLayout(pos_layout) outer.addWidget(pos_group) @@ -131,20 +121,14 @@ def _build_auto_click_tab(self) -> QWidget: hk_layout = QHBoxLayout() self.hotkey_input = QLineEdit() self.hotkey_input.setPlaceholderText("ctrl,a") - self.hotkey_btn = self._tr(QPushButton(), "hotkey_send") - self.hotkey_btn.clicked.connect(self._send_hotkey) hk_layout.addWidget(self.hotkey_input) - hk_layout.addWidget(self.hotkey_btn) hotkey_group.setLayout(hk_layout) outer.addWidget(hotkey_group) write_group = self._tr(QGroupBox(), "write_label") wr_layout = QHBoxLayout() self.write_input = QLineEdit() - self.write_btn = self._tr(QPushButton(), "write_send") - self.write_btn.clicked.connect(self._send_write) wr_layout.addWidget(self.write_input) - wr_layout.addWidget(self.write_btn) write_group.setLayout(wr_layout) outer.addWidget(write_group) @@ -160,9 +144,6 @@ def _build_auto_click_tab(self) -> QWidget: sc_layout.addWidget(self.scroll_dir_combo) else: self.scroll_dir_combo = None - self.scroll_btn = self._tr(QPushButton(), "scroll_send") - self.scroll_btn.clicked.connect(self._send_scroll) - sc_layout.addWidget(self.scroll_btn) scroll_group.setLayout(sc_layout) outer.addWidget(scroll_group) @@ -244,7 +225,10 @@ def _do_click(self): type_keyboard(key) if is_double: type_keyboard(key) - except (OSError, ValueError, TypeError, RuntimeError) as error: + except (AutoControlException, OSError, ValueError, TypeError, RuntimeError) as error: + # AutoControlMouseException / AutoControlCantFindKeyException from a + # throwing backend must land here — otherwise timer.stop() is + # skipped and the auto-click QTimer fires the failing action forever. self.timer.stop() QMessageBox.warning(self, "Error", str(error)) @@ -257,7 +241,7 @@ def _get_mouse_pos(self): ) self.cursor_x_input.setText(str(x)) self.cursor_y_input.setText(str(y)) - except (OSError, ValueError, TypeError, RuntimeError) as error: + except (AutoControlException, OSError, ValueError, TypeError, RuntimeError) as error: QMessageBox.warning(self, "Error", str(error)) def _send_hotkey(self): @@ -265,7 +249,7 @@ def _send_hotkey(self): keys = [k.strip() for k in self.hotkey_input.text().split(",") if k.strip()] if keys: hotkey(keys) - except (OSError, ValueError, TypeError, RuntimeError) as error: + except (AutoControlException, OSError, ValueError, TypeError, RuntimeError) as error: QMessageBox.warning(self, "Error", str(error)) def _send_write(self): @@ -273,7 +257,7 @@ def _send_write(self): text = self.write_input.text() if text: write(text) - except (OSError, ValueError, TypeError, RuntimeError) as error: + except (AutoControlException, OSError, ValueError, TypeError, RuntimeError) as error: QMessageBox.warning(self, "Error", str(error)) def _send_scroll(self): @@ -281,5 +265,5 @@ def _send_scroll(self): val = int(self.scroll_value_input.text() or "3") direction = self.scroll_dir_combo.currentText() if self.scroll_dir_combo else "scroll_down" mouse_scroll(val, scroll_direction=direction) - except (OSError, ValueError, TypeError, RuntimeError) as error: + except (AutoControlException, OSError, ValueError, TypeError, RuntimeError) as error: QMessageBox.warning(self, "Error", str(error)) diff --git a/je_auto_control/gui/_report_tab.py b/je_auto_control/gui/_report_tab.py index f598d30c..5c2318ce 100644 --- a/je_auto_control/gui/_report_tab.py +++ b/je_auto_control/gui/_report_tab.py @@ -1,9 +1,10 @@ """Report-generation tab builder (extracted mixin).""" from PySide6.QtWidgets import ( - QGroupBox, QHBoxLayout, QLabel, QLineEdit, QPushButton, + QGroupBox, QHBoxLayout, QLabel, QLineEdit, QTextEdit, QVBoxLayout, QWidget, ) +from je_auto_control.utils.exception.exceptions import AutoControlException from je_auto_control.utils.generate_report.generate_html_report import generate_html_report from je_auto_control.utils.generate_report.generate_json_report import generate_json_report from je_auto_control.utils.generate_report.generate_xml_report import generate_xml_report @@ -21,15 +22,11 @@ def _build_report_tab(self) -> QWidget: tab = QWidget() layout = QVBoxLayout() + # Enable/disable recording and report generation run from the + # Actions menu; the tab keeps only status, name input, and result. tr_group = self._tr(QGroupBox(), "test_record_status") tr_h = QHBoxLayout() - self.tr_enable_btn = self._tr(QPushButton(), "enable_test_record") - self.tr_enable_btn.clicked.connect(lambda: self._set_test_record(True)) - self.tr_disable_btn = self._tr(QPushButton(), "disable_test_record") - self.tr_disable_btn.clicked.connect(lambda: self._set_test_record(False)) self.tr_status_label = QLabel("OFF") - tr_h.addWidget(self.tr_enable_btn) - tr_h.addWidget(self.tr_disable_btn) tr_h.addWidget(self.tr_status_label) tr_group.setLayout(tr_h) layout.addWidget(tr_group) @@ -40,18 +37,6 @@ def _build_report_tab(self) -> QWidget: name_h.addWidget(self.report_name_input) layout.addLayout(name_h) - btn_h = QHBoxLayout() - self.html_report_btn = self._tr(QPushButton(), "generate_html_report") - self.html_report_btn.clicked.connect(self._gen_html) - self.json_report_btn = self._tr(QPushButton(), "generate_json_report") - self.json_report_btn.clicked.connect(self._gen_json) - self.xml_report_btn = self._tr(QPushButton(), "generate_xml_report") - self.xml_report_btn.clicked.connect(self._gen_xml) - btn_h.addWidget(self.html_report_btn) - btn_h.addWidget(self.json_report_btn) - btn_h.addWidget(self.xml_report_btn) - layout.addLayout(btn_h) - layout.addWidget(self._tr(QLabel(), "report_result")) self.report_result_text = QTextEdit() self.report_result_text.setReadOnly(True) @@ -64,12 +49,18 @@ def _set_test_record(self, enable: bool): test_record_instance.set_record_enable(enable) self.tr_status_label.setText("ON" if enable else "OFF") + def _enable_test_record(self): + self._set_test_record(True) + + def _disable_test_record(self): + self._set_test_record(False) + def _gen_html(self): try: name = self.report_name_input.text() or "autocontrol_report" generate_html_report(name) self.report_result_text.setText(f"HTML report generated: {name}") - except (OSError, ValueError, TypeError, RuntimeError) as error: + except (AutoControlException, OSError, ValueError, TypeError, RuntimeError) as error: self.report_result_text.setText(f"Error: {error}") def _gen_json(self): @@ -77,7 +68,7 @@ def _gen_json(self): name = self.report_name_input.text() or "autocontrol_report" generate_json_report(name) self.report_result_text.setText(f"JSON report generated: {name}") - except (OSError, ValueError, TypeError, RuntimeError) as error: + except (AutoControlException, OSError, ValueError, TypeError, RuntimeError) as error: self.report_result_text.setText(f"Error: {error}") def _gen_xml(self): @@ -85,5 +76,5 @@ def _gen_xml(self): name = self.report_name_input.text() or "autocontrol_report" generate_xml_report(name) self.report_result_text.setText(f"XML report generated: {name}") - except (OSError, ValueError, TypeError, RuntimeError) as error: + except (AutoControlException, OSError, ValueError, TypeError, RuntimeError) as error: self.report_result_text.setText(f"Error: {error}") diff --git a/je_auto_control/gui/a11y_audit_tab.py b/je_auto_control/gui/a11y_audit_tab.py index 078e2dfc..2bf6a662 100644 --- a/je_auto_control/gui/a11y_audit_tab.py +++ b/je_auto_control/gui/a11y_audit_tab.py @@ -7,7 +7,7 @@ from PySide6.QtCore import Qt from PySide6.QtWidgets import ( - QAbstractItemView, QHBoxLayout, QLabel, QLineEdit, QPushButton, + QAbstractItemView, QHBoxLayout, QLabel, QLineEdit, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, ) @@ -55,25 +55,28 @@ def _apply_headers(self) -> None: self._table.setHorizontalHeaderLabels([_t(k) for k in _COLS]) def _build_layout(self) -> None: + # Audit/contrast commands run from the Actions menu; the tab keeps + # only the inputs, the issue table, and the summary line. root = QVBoxLayout(self) row = QHBoxLayout() row.addWidget(QLabel(_t("audit_app"))) row.addWidget(self._app, stretch=1) - run_btn = self._tr(QPushButton(), "audit_run") - run_btn.clicked.connect(self._on_run) - row.addWidget(run_btn) root.addLayout(row) crow = QHBoxLayout() crow.addWidget(QLabel(_t("audit_contrast_label"))) crow.addWidget(self._fg) crow.addWidget(self._bg) - contrast_btn = self._tr(QPushButton(), "audit_contrast_run") - contrast_btn.clicked.connect(self._on_contrast) - crow.addWidget(contrast_btn) root.addLayout(crow) root.addWidget(self._table, stretch=1) root.addWidget(self._summary) + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("audit_run", self._on_run), + ("audit_contrast_run", self._on_contrast), + ] + def _on_run(self) -> None: app = self._app.text().strip() or None try: diff --git a/je_auto_control/gui/accessibility_tab.py b/je_auto_control/gui/accessibility_tab.py index 19c1c907..5a8e0919 100644 --- a/je_auto_control/gui/accessibility_tab.py +++ b/je_auto_control/gui/accessibility_tab.py @@ -4,7 +4,7 @@ from PySide6.QtCore import Qt from PySide6.QtWidgets import ( QAbstractItemView, QHBoxLayout, QHeaderView, QLabel, QLineEdit, - QMessageBox, QPushButton, QTableWidget, QTableWidgetItem, + QMessageBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, ) @@ -59,6 +59,8 @@ def _apply_table_headers(self) -> None: ]) def _build_layout(self) -> None: + # Refresh/click commands run from the Actions menu; the tab keeps + # only the filter inputs, the element table, and the status line. root = QVBoxLayout(self) row = QHBoxLayout() row.addWidget(self._tr(QLabel(), "a11y_app_label")) @@ -67,19 +69,17 @@ def _build_layout(self) -> None: row.addWidget(self._tr(QLabel(), "a11y_name_label")) self._name_filter.setPlaceholderText(_t("a11y_name_placeholder")) row.addWidget(self._name_filter, stretch=1) - refresh = self._tr(QPushButton(), "a11y_refresh") - refresh.clicked.connect(self._refresh) - row.addWidget(refresh) root.addLayout(row) root.addWidget(self._table, stretch=1) - action_row = QHBoxLayout() - click_btn = self._tr(QPushButton(), "a11y_click_selected") - click_btn.clicked.connect(self._click_selected) - action_row.addWidget(click_btn) - action_row.addStretch() - root.addLayout(action_row) root.addWidget(self._status) + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("a11y_refresh", self._refresh), + ("a11y_click_selected", self._click_selected), + ] + def _refresh(self) -> None: app = self._app_filter.text().strip() or None try: diff --git a/je_auto_control/gui/admin_console_tab.py b/je_auto_control/gui/admin_console_tab.py index 14af1087..183e44b1 100644 --- a/je_auto_control/gui/admin_console_tab.py +++ b/je_auto_control/gui/admin_console_tab.py @@ -6,7 +6,7 @@ from PySide6.QtGui import QIcon, QImage, QPixmap from PySide6.QtWidgets import ( QGroupBox, QHBoxLayout, QHeaderView, QLabel, QLineEdit, QListWidget, - QListWidgetItem, QMessageBox, QPushButton, QSpinBox, QTableWidget, + QListWidgetItem, QMessageBox, QSpinBox, QTableWidget, QTableWidgetItem, QTextEdit, QVBoxLayout, QWidget, ) @@ -106,22 +106,30 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self._apply_thumb_interval() def _build_layout(self) -> None: + # Add/remove/refresh/thumbnail/broadcast commands run from the + # Actions menu; the tab keeps only the inputs, tables, and output. root = QVBoxLayout(self) root.addWidget(self._build_add_group()) root.addWidget(self._table, stretch=1) - root.addLayout(self._build_button_row()) root.addWidget(self._build_thumbnails_group(), stretch=1) root.addWidget(self._build_broadcast_group(), stretch=1) + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("admin_add", self._on_add), + ("admin_remove", self._on_remove), + ("admin_refresh", self._on_refresh), + ("admin_thumb_refresh_now", self._refresh_thumbnails), + ("admin_broadcast_run", self._on_broadcast), + ] + def _build_thumbnails_group(self) -> QGroupBox: group = self._tr(QGroupBox(), "admin_thumb_group") layout = QVBoxLayout(group) controls = QHBoxLayout() controls.addWidget(self._tr(QLabel(), "admin_thumb_interval")) controls.addWidget(self._thumb_interval) - refresh = self._tr(QPushButton(), "admin_thumb_refresh_now") - refresh.clicked.connect(self._refresh_thumbnails) - controls.addWidget(refresh) controls.addStretch(1) layout.addLayout(controls) layout.addWidget(self._thumbnails, stretch=1) @@ -136,31 +144,13 @@ def _build_add_group(self) -> QGroupBox: form.addWidget(self._url_input, stretch=1) form.addWidget(self._tr(QLabel(), "admin_token")) form.addWidget(self._token_input) - add = self._tr(QPushButton(), "admin_add") - add.clicked.connect(self._on_add) - form.addWidget(add) return group - def _build_button_row(self) -> QHBoxLayout: - row = QHBoxLayout() - for key, handler in ( - ("admin_remove", self._on_remove), - ("admin_refresh", self._on_refresh), - ): - btn = self._tr(QPushButton(), key) - btn.clicked.connect(handler) - row.addWidget(btn) - row.addStretch(1) - return row - def _build_broadcast_group(self) -> QGroupBox: group = self._tr(QGroupBox(), "admin_broadcast_group") form = QVBoxLayout(group) form.addWidget(self._tr(QLabel(), "admin_actions_label")) form.addWidget(self._actions_input) - run = self._tr(QPushButton(), "admin_broadcast_run") - run.clicked.connect(self._on_broadcast) - form.addWidget(run) form.addWidget(self._tr(QLabel(), "admin_results_label")) form.addWidget(self._broadcast_output, stretch=1) return group @@ -199,6 +189,8 @@ def _on_refresh(self) -> None: worker.finished.connect(thread.quit) worker.failed.connect(thread.quit) thread.finished.connect(self._on_poll_thread_done) + thread.finished.connect(worker.deleteLater) + thread.finished.connect(thread.deleteLater) self._poll_thread = thread thread.start() @@ -247,6 +239,10 @@ def _refresh_thumbnails(self) -> None: worker.finished.connect(self._apply_thumbnails) worker.finished.connect(thread.quit) thread.finished.connect(self._on_thumb_thread_done) + # Without deleteLater the QThread (parented to self) and its worker + # accumulate one per poll tick — a fresh handle leak every interval. + thread.finished.connect(worker.deleteLater) + thread.finished.connect(thread.deleteLater) self._thumb_thread = thread thread.start() diff --git a/je_auto_control/gui/assertions_tab.py b/je_auto_control/gui/assertions_tab.py index 75629c71..bbc558f2 100644 --- a/je_auto_control/gui/assertions_tab.py +++ b/je_auto_control/gui/assertions_tab.py @@ -7,7 +7,7 @@ from typing import Any, Dict, Optional from PySide6.QtWidgets import ( - QCheckBox, QComboBox, QHBoxLayout, QLabel, QLineEdit, QPushButton, + QCheckBox, QComboBox, QHBoxLayout, QLabel, QLineEdit, QVBoxLayout, QWidget, ) @@ -51,6 +51,8 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self._sync_visibility() def _build_layout(self) -> None: + # The run command runs from the Actions menu; the tab keeps only + # the assertion form and the result label. root = QVBoxLayout(self) krow = QHBoxLayout() krow.addWidget(QLabel(_t("assert_kind"))) @@ -71,12 +73,15 @@ def _build_layout(self) -> None: root.addWidget(self._expect) root.addWidget(self._regex) - run_btn = self._tr(QPushButton(), "assert_run") - run_btn.clicked.connect(self._on_run) - root.addWidget(run_btn) root.addWidget(self._result) root.addStretch() + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("assert_run", self._on_run), + ] + def _current_kind(self) -> str: return _KINDS[self._kind.currentIndex()] diff --git a/je_auto_control/gui/audit_log_tab.py b/je_auto_control/gui/audit_log_tab.py index 75f428d6..f5a6776b 100644 --- a/je_auto_control/gui/audit_log_tab.py +++ b/je_auto_control/gui/audit_log_tab.py @@ -4,7 +4,7 @@ from PySide6.QtWidgets import ( QComboBox, QGroupBox, QHBoxLayout, QHeaderView, QLabel, QLineEdit, - QMessageBox, QPushButton, QSpinBox, QTableWidget, QTableWidgetItem, + QMessageBox, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, ) @@ -72,12 +72,21 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self._refresh() def _build_layout(self) -> None: + # Refresh/verify/clear commands run from the Actions menu; the + # tab keeps only the filter inputs, the table, and the status. root = QVBoxLayout(self) root.addWidget(self._build_filter_group()) root.addWidget(self._table, stretch=1) - root.addLayout(self._build_button_row()) root.addWidget(self._verify_status) + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("audit_refresh", self._refresh), + ("audit_verify", self._verify), + ("audit_clear", self._clear), + ] + def _build_filter_group(self) -> QGroupBox: group = self._tr(QGroupBox(), "audit_filter_group") row = QHBoxLayout(group) @@ -89,19 +98,6 @@ def _build_filter_group(self) -> QGroupBox: row.addWidget(self._limit_input) return group - def _build_button_row(self) -> QHBoxLayout: - row = QHBoxLayout() - for key, handler in ( - ("audit_refresh", self._refresh), - ("audit_verify", self._verify), - ("audit_clear", self._clear), - ): - btn = self._tr(QPushButton(), key) - btn.clicked.connect(handler) - row.addWidget(btn) - row.addStretch(1) - return row - def _refresh(self) -> None: self._apply_table_headers() # Pull a wide window so the dropdown reflects everything the user diff --git a/je_auto_control/gui/chatops_tab.py b/je_auto_control/gui/chatops_tab.py index 7d2debd6..e7d1ce61 100644 --- a/je_auto_control/gui/chatops_tab.py +++ b/je_auto_control/gui/chatops_tab.py @@ -2,7 +2,7 @@ from typing import Optional from PySide6.QtWidgets import ( - QFileDialog, QHBoxLayout, QLabel, QLineEdit, QPushButton, QTextEdit, + QFileDialog, QHBoxLayout, QLabel, QLineEdit, QTextEdit, QVBoxLayout, QWidget, ) @@ -39,26 +39,19 @@ def retranslate(self) -> None: self._apply_translations() def _build_layout(self) -> None: + # Browse/send commands run from the Actions menu; the tab keeps + # only the root/command inputs and the output log. root = QVBoxLayout(self) root_row = QHBoxLayout() - root_row.addWidget(QLabel(), stretch=0) self._root_label = QLabel() root_row.addWidget(self._root_label) root_row.addWidget(self._script_root, stretch=1) - browse = QPushButton() - browse.setObjectName("chatops_browse_btn") - browse.clicked.connect(self._on_browse) - root_row.addWidget(browse) root.addLayout(root_row) cmd_row = QHBoxLayout() self._cmd_label = QLabel() cmd_row.addWidget(self._cmd_label) cmd_row.addWidget(self._command_input, stretch=1) - send = QPushButton() - send.setObjectName("chatops_send_btn") - send.clicked.connect(self._on_send) - cmd_row.addWidget(send) root.addLayout(cmd_row) self._output_label = QLabel() @@ -66,14 +59,17 @@ def _build_layout(self) -> None: root.addWidget(self._output, stretch=1) self._apply_translations() + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("chatops_browse_btn", self._on_browse), + ("chatops_send_btn", self._on_send), + ] + def _apply_translations(self) -> None: self._root_label.setText(_t("chatops_root_label")) self._cmd_label.setText(_t("chatops_cmd_label")) self._output_label.setText(_t("chatops_output_label")) - for key in ("chatops_browse_btn", "chatops_send_btn"): - widget = self.findChild(QPushButton, key) - if widget is not None: - widget.setText(_t(key)) self._script_root.setPlaceholderText(_t("chatops_root_placeholder")) self._command_input.setPlaceholderText( _t("chatops_cmd_placeholder"), diff --git a/je_auto_control/gui/computer_use_tab.py b/je_auto_control/gui/computer_use_tab.py index 6e0761e2..8e693d01 100644 --- a/je_auto_control/gui/computer_use_tab.py +++ b/je_auto_control/gui/computer_use_tab.py @@ -4,7 +4,7 @@ from PySide6.QtCore import QObject, QThread, Signal from PySide6.QtWidgets import ( - QFormLayout, QHBoxLayout, QLabel, QLineEdit, QMessageBox, QPushButton, + QFormLayout, QLabel, QLineEdit, QMessageBox, QSpinBox, QTextEdit, QVBoxLayout, QWidget, ) @@ -58,7 +58,6 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self._max_tokens = QSpinBox() self._max_tokens.setRange(64, 8192) self._max_tokens.setValue(1024) - self._run_btn = QPushButton() self._output = QTextEdit() self._output.setReadOnly(True) self._status = QLabel() @@ -73,6 +72,8 @@ def retranslate(self) -> None: # --- layout ---------------------------------------------------- def _build_layout(self) -> None: + # The run command runs from the Actions menu; the tab keeps only + # the goal/model/limit inputs, the output view, and the status. root = QVBoxLayout(self) form = QFormLayout() self._goal_label = QLabel() @@ -86,11 +87,6 @@ def _build_layout(self) -> None: form.addRow(self._wall_seconds_label, self._wall_seconds) form.addRow(self._max_tokens_label, self._max_tokens) root.addLayout(form) - btn_row = QHBoxLayout() - self._run_btn.clicked.connect(self._on_run) - btn_row.addWidget(self._run_btn) - btn_row.addStretch() - root.addLayout(btn_row) root.addWidget(self._status) self._output_label = QLabel() root.addWidget(self._output_label) @@ -105,7 +101,12 @@ def _apply_translations(self) -> None: self._max_tokens_label.setText(_t("computer_use_max_tokens_label")) self._output_label.setText(_t("computer_use_output_label")) self._goal_input.setPlaceholderText(_t("computer_use_goal_placeholder")) - self._run_btn.setText(_t("computer_use_run_btn")) + + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("computer_use_run_btn", self._on_run), + ] # --- run path -------------------------------------------------- @@ -128,7 +129,6 @@ def _on_run(self) -> None: "max_tokens": int(self._max_tokens.value()), } self._status.setText(_t("computer_use_running")) - self._run_btn.setEnabled(False) self._spawn_worker(params) def _spawn_worker(self, params: dict) -> None: @@ -147,7 +147,6 @@ def _spawn_worker(self, params: dict) -> None: thread.start() def _on_worker_finished(self, data: dict) -> None: - self._run_btn.setEnabled(True) ok = bool(data.get("succeeded")) key = "computer_use_success" if ok else "computer_use_failure" self._status.setText(_t(key)) @@ -158,7 +157,6 @@ def _on_worker_finished(self, data: dict) -> None: self._worker = None def _on_worker_failed(self, message: str) -> None: - self._run_btn.setEnabled(True) self._status.setText(f"{_t('computer_use_error')}: {message}") self._thread = None self._worker = None diff --git a/je_auto_control/gui/dag_tab.py b/je_auto_control/gui/dag_tab.py index 19841139..9ad391be 100644 --- a/je_auto_control/gui/dag_tab.py +++ b/je_auto_control/gui/dag_tab.py @@ -4,7 +4,7 @@ from PySide6.QtCore import QObject, Qt, QThread, Signal from PySide6.QtWidgets import ( - QFileDialog, QHBoxLayout, QLabel, QPushButton, QSpinBox, + QFileDialog, QHBoxLayout, QLabel, QSpinBox, QTableWidget, QTableWidgetItem, QTextEdit, QVBoxLayout, QWidget, ) @@ -65,17 +65,10 @@ def retranslate(self) -> None: self._apply_translations() def _build_layout(self) -> None: + # Load/validate/run commands run from the Actions menu; the tab + # keeps only the editor, the parallel input, table, and status. root = QVBoxLayout(self) controls = QHBoxLayout() - for label_key, slot in ( - ("dag_load_btn", self._on_load), - ("dag_validate_btn", self._on_validate), - ("dag_run_btn", self._on_run), - ): - btn = QPushButton() - btn.setObjectName(label_key) - btn.clicked.connect(slot) - controls.addWidget(btn) controls.addWidget(QLabel(_t("dag_parallel_label"))) controls.addWidget(self._max_parallel) controls.addStretch() @@ -85,11 +78,15 @@ def _build_layout(self) -> None: root.addWidget(self._table, stretch=3) self._apply_translations() + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("dag_load_btn", self._on_load), + ("dag_validate_btn", self._on_validate), + ("dag_run_btn", self._on_run), + ] + def _apply_translations(self) -> None: - for key in ("dag_load_btn", "dag_validate_btn", "dag_run_btn"): - btn = self.findChild(QPushButton, key) - if btn is not None: - btn.setText(_t(key)) self._table.setHorizontalHeaderLabels( [_t(f"dag_col_{name}") for name in _COLUMNS], ) diff --git a/je_auto_control/gui/data_source_tab.py b/je_auto_control/gui/data_source_tab.py index 8845e084..8d2a1c5e 100644 --- a/je_auto_control/gui/data_source_tab.py +++ b/je_auto_control/gui/data_source_tab.py @@ -45,6 +45,8 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self._sync_visibility(self._kind.currentText()) def _build_layout(self) -> None: + # The load command runs from the Actions menu; the tab keeps the + # source form (with its kind-synced browse button) and the table. root = QVBoxLayout(self) row1 = QHBoxLayout() row1.addWidget(QLabel(_t("ds_kind"))) @@ -72,15 +74,18 @@ def _build_layout(self) -> None: row2 = QHBoxLayout() row2.addWidget(QLabel(_t("ds_limit"))) row2.addWidget(self._limit) - load_btn = self._tr(QPushButton(), "ds_load") - load_btn.clicked.connect(self._on_load) - row2.addWidget(load_btn) row2.addStretch() root.addLayout(row2) root.addWidget(self._table, stretch=1) root.addWidget(self._status) + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("ds_load", self._on_load), + ] + def _sync_visibility(self, kind: str) -> None: is_file = kind in ("csv", "json", "sqlite", "excel") for widget in (self._path_label, self._path, self._browse_btn): diff --git a/je_auto_control/gui/device_matrix_tab.py b/je_auto_control/gui/device_matrix_tab.py index addfc785..51755e5d 100644 --- a/je_auto_control/gui/device_matrix_tab.py +++ b/je_auto_control/gui/device_matrix_tab.py @@ -7,7 +7,7 @@ from PySide6.QtCore import Qt from PySide6.QtWidgets import ( - QAbstractItemView, QHBoxLayout, QLabel, QPlainTextEdit, QPushButton, + QAbstractItemView, QHBoxLayout, QLabel, QPlainTextEdit, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, ) @@ -58,6 +58,8 @@ def _apply_headers(self) -> None: self._table.setHorizontalHeaderLabels([_t(k) for k in _COLS]) def _build_layout(self) -> None: + # The run command runs from the Actions menu; the tab keeps only + # the device/action editors, the result table, and the summary. root = QVBoxLayout(self) root.addWidget(QLabel(_t("dm_devices"))) root.addWidget(self._devices) @@ -66,14 +68,17 @@ def _build_layout(self) -> None: row = QHBoxLayout() row.addWidget(QLabel(_t("dm_parallel"))) row.addWidget(self._parallel) - run_btn = self._tr(QPushButton(), "dm_run") - run_btn.clicked.connect(self._on_run) - row.addWidget(run_btn) row.addStretch() root.addLayout(row) root.addWidget(self._table, stretch=1) root.addWidget(self._summary) + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("dm_run", self._on_run), + ] + def _on_run(self) -> None: try: devices = json.loads(self._devices.toPlainText() or "[]") diff --git a/je_auto_control/gui/diagnostics_tab.py b/je_auto_control/gui/diagnostics_tab.py index 4f2ecd5e..89a205a9 100644 --- a/je_auto_control/gui/diagnostics_tab.py +++ b/je_auto_control/gui/diagnostics_tab.py @@ -3,7 +3,7 @@ from PySide6.QtGui import QBrush, QColor from PySide6.QtWidgets import ( - QHBoxLayout, QHeaderView, QLabel, QPushButton, QTableWidget, + QHeaderView, QLabel, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, ) @@ -42,16 +42,18 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self._refresh() def _build_layout(self) -> None: + # The run command runs from the Actions menu; the tab keeps only + # the summary label and the results table. root = QVBoxLayout(self) - header = QHBoxLayout() - run_btn = self._tr(QPushButton(), "diag_run") - run_btn.clicked.connect(self._refresh) - header.addWidget(run_btn) - header.addStretch(1) - root.addLayout(header) root.addWidget(self._summary_label) root.addWidget(self._table, stretch=1) + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("diag_run", self._refresh), + ] + def _apply_table_headers(self) -> None: self._table.setHorizontalHeaderLabels([ _t("diag_col_name"), _t("diag_col_severity"), diff --git a/je_auto_control/gui/email_triggers_tab.py b/je_auto_control/gui/email_triggers_tab.py index 275867fc..5d61f750 100644 --- a/je_auto_control/gui/email_triggers_tab.py +++ b/je_auto_control/gui/email_triggers_tab.py @@ -4,7 +4,7 @@ from PySide6.QtCore import QTimer, Qt from PySide6.QtWidgets import ( QAbstractItemView, QCheckBox, QFileDialog, QGroupBox, QHBoxLayout, - QHeaderView, QLabel, QLineEdit, QMessageBox, QPushButton, + QHeaderView, QLabel, QLineEdit, QMessageBox, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, ) @@ -80,19 +80,13 @@ def _apply_table_headers(self) -> None: ]) def _build_layout(self) -> None: + # Start/stop/poll/browse/register/remove commands run from the + # Actions menu; the tab keeps only the inputs, the table, and the + # status line. root = QVBoxLayout(self) engine_box = self._tr(QGroupBox(), "eml_engine_group") engine_layout = QHBoxLayout(engine_box) - start_btn = self._tr(QPushButton(), "eml_start") - start_btn.clicked.connect(self._on_start) - engine_layout.addWidget(start_btn) - stop_btn = self._tr(QPushButton(), "eml_stop") - stop_btn.clicked.connect(self._on_stop) - engine_layout.addWidget(stop_btn) - poll_btn = self._tr(QPushButton(), "eml_poll_now") - poll_btn.clicked.connect(self._on_poll_now) - engine_layout.addWidget(poll_btn) engine_layout.addWidget(self._status_label) engine_layout.addStretch() root.addWidget(engine_box) @@ -127,22 +121,21 @@ def _build_layout(self) -> None: script_row = QHBoxLayout() script_row.addWidget(self._tr(QLabel(), "eml_script_label")) script_row.addWidget(self._script_input) - browse_btn = self._tr(QPushButton(), "eml_browse") - browse_btn.clicked.connect(self._on_browse) - script_row.addWidget(browse_btn) - register_btn = self._tr(QPushButton(), "eml_register") - register_btn.clicked.connect(self._on_register) - script_row.addWidget(register_btn) add_layout.addLayout(script_row) root.addWidget(add_box) root.addWidget(self._table, stretch=1) - action_row = QHBoxLayout() - remove_btn = self._tr(QPushButton(), "eml_remove") - remove_btn.clicked.connect(self._on_remove) - action_row.addWidget(remove_btn) - action_row.addStretch() - root.addLayout(action_row) + + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("eml_start", self._on_start), + ("eml_stop", self._on_stop), + ("eml_poll_now", self._on_poll_now), + ("eml_browse", self._on_browse), + ("eml_register", self._on_register), + ("eml_remove", self._on_remove), + ] def _on_start(self) -> None: default_email_trigger_watcher.start() diff --git a/je_auto_control/gui/flakiness_tab.py b/je_auto_control/gui/flakiness_tab.py index 973b9ba9..6536a0c3 100644 --- a/je_auto_control/gui/flakiness_tab.py +++ b/je_auto_control/gui/flakiness_tab.py @@ -7,7 +7,7 @@ from PySide6.QtCore import Qt from PySide6.QtWidgets import ( - QAbstractItemView, QComboBox, QHBoxLayout, QHeaderView, QLabel, QPushButton, + QAbstractItemView, QComboBox, QHBoxLayout, QHeaderView, QLabel, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, ) @@ -64,6 +64,8 @@ def _apply_headers(self) -> None: self._table.setHorizontalHeaderLabels([_t(k) for k in _COLUMN_KEYS]) def _build_layout(self) -> None: + # The refresh command runs from the Actions menu; the tab keeps + # only the filter inputs, the table, and the status line. root = QVBoxLayout(self) controls = QHBoxLayout() controls.addWidget(QLabel(_t("flaky_limit"))) @@ -72,14 +74,17 @@ def _build_layout(self) -> None: controls.addWidget(self._min_runs) controls.addWidget(QLabel(_t("flaky_group_by"))) controls.addWidget(self._group_by) - refresh_btn = self._tr(QPushButton(), "flaky_refresh") - refresh_btn.clicked.connect(self._refresh) - controls.addWidget(refresh_btn) controls.addStretch() root.addLayout(controls) root.addWidget(self._table, stretch=1) root.addWidget(self._status) + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("flaky_refresh", self._refresh), + ] + def _refresh(self) -> None: report = analyze_flakiness( limit=self._limit.value(), diff --git a/je_auto_control/gui/flow_editor/tab.py b/je_auto_control/gui/flow_editor/tab.py index 96eb5cd6..248a995d 100644 --- a/je_auto_control/gui/flow_editor/tab.py +++ b/je_auto_control/gui/flow_editor/tab.py @@ -10,8 +10,8 @@ from PySide6.QtCore import Qt from PySide6.QtWidgets import ( - QFileDialog, QGraphicsView, QHBoxLayout, QLabel, QMessageBox, - QPushButton, QSplitter, QTextEdit, QVBoxLayout, QWidget, + QFileDialog, QGraphicsView, QLabel, QMessageBox, + QSplitter, QTextEdit, QVBoxLayout, QWidget, ) from je_auto_control.gui._i18n_helpers import TranslatableMixin @@ -50,21 +50,9 @@ def retranslate(self) -> None: self._apply_translations() def _build_layout(self) -> None: + # Open/save/zoom/fit commands run from the Actions menu; the tab + # keeps only the graph view, the inspector, and the status line. root = QVBoxLayout(self) - toolbar = QHBoxLayout() - for key, slot in ( - ("flow_open_btn", self._on_open), - ("flow_save_btn", self._on_save), - ("flow_zoom_in_btn", self._on_zoom_in), - ("flow_zoom_out_btn", self._on_zoom_out), - ("flow_fit_btn", self._on_fit), - ): - btn = QPushButton() - btn.setObjectName(key) - btn.clicked.connect(slot) - toolbar.addWidget(btn) - toolbar.addStretch() - root.addLayout(toolbar) splitter = QSplitter(Qt.Horizontal) splitter.addWidget(self._view) splitter.addWidget(self._inspector) @@ -74,13 +62,17 @@ def _build_layout(self) -> None: root.addWidget(self._status) self._apply_translations() + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("flow_open_btn", self._on_open), + ("flow_save_btn", self._on_save), + ("flow_zoom_in_btn", self._on_zoom_in), + ("flow_zoom_out_btn", self._on_zoom_out), + ("flow_fit_btn", self._on_fit), + ] + def _apply_translations(self) -> None: - for key in ("flow_open_btn", "flow_save_btn", - "flow_zoom_in_btn", "flow_zoom_out_btn", - "flow_fit_btn"): - widget = self.findChild(QPushButton, key) - if widget is not None: - widget.setText(_t(key)) self._inspector.setPlaceholderText(_t("flow_inspector_placeholder")) # --- public ---------------------------------------------------- diff --git a/je_auto_control/gui/hotkeys_tab.py b/je_auto_control/gui/hotkeys_tab.py index 161039a7..c301b52a 100644 --- a/je_auto_control/gui/hotkeys_tab.py +++ b/je_auto_control/gui/hotkeys_tab.py @@ -3,7 +3,7 @@ from PySide6.QtCore import QTimer from PySide6.QtWidgets import ( - QFileDialog, QHBoxLayout, QLabel, QLineEdit, QMessageBox, QPushButton, + QFileDialog, QHBoxLayout, QLabel, QLineEdit, QMessageBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, ) @@ -38,34 +38,28 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self._build_layout() def _build_layout(self) -> None: + # Bind/remove/start/stop commands run from the Actions menu; the + # tab keeps only the inputs, the bindings table, and the status. root = QVBoxLayout(self) form = QHBoxLayout() form.addWidget(self._tr(QLabel(), "hk_combo_label")) form.addWidget(self._combo_input) form.addWidget(self._tr(QLabel(), "hk_script_label")) form.addWidget(self._script_input, stretch=1) - browse = self._tr(QPushButton(), "browse") - browse.clicked.connect(self._browse) - form.addWidget(browse) - add = self._tr(QPushButton(), "hk_bind") - add.clicked.connect(self._on_bind) - form.addWidget(add) root.addLayout(form) root.addWidget(self._table, stretch=1) + root.addWidget(self._status) - ctl = QHBoxLayout() - for key, handler in ( + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("browse", self._browse), + ("hk_bind", self._on_bind), ("hk_remove_selected", self._on_remove), ("hk_start_daemon", self._on_start), ("hk_stop_daemon", self._on_stop), - ): - btn = self._tr(QPushButton(), key) - btn.clicked.connect(handler) - ctl.addWidget(btn) - ctl.addStretch() - root.addLayout(ctl) - root.addWidget(self._status) + ] def _apply_status_label(self) -> None: key = "hk_daemon_running" if self._daemon_running else "hk_daemon_stopped" diff --git a/je_auto_control/gui/inspector_tab.py b/je_auto_control/gui/inspector_tab.py index 35e5d594..a8c8a763 100644 --- a/je_auto_control/gui/inspector_tab.py +++ b/je_auto_control/gui/inspector_tab.py @@ -3,7 +3,7 @@ from PySide6.QtCore import QTimer from PySide6.QtWidgets import ( - QFormLayout, QGroupBox, QHBoxLayout, QHeaderView, QLabel, QPushButton, + QFormLayout, QGroupBox, QHeaderView, QLabel, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, ) @@ -48,12 +48,20 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self._timer.start() def _build_layout(self) -> None: + # Refresh/reset commands run from the Actions menu; the tab keeps + # only the summary, the metrics group, and the samples table. root = QVBoxLayout(self) root.addWidget(self._summary_label) root.addWidget(self._build_metrics_group()) - root.addLayout(self._build_button_row()) root.addWidget(self._table, stretch=1) + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("inspector_refresh", self._refresh), + ("inspector_reset", self._reset), + ] + def _build_metrics_group(self) -> QGroupBox: group = self._tr(QGroupBox(), "inspector_metrics_group") form = QFormLayout(group) @@ -64,18 +72,6 @@ def _build_metrics_group(self) -> QGroupBox: form.addRow(label_widget, value_widget) return group - def _build_button_row(self) -> QHBoxLayout: - row = QHBoxLayout() - for key, handler in ( - ("inspector_refresh", self._refresh), - ("inspector_reset", self._reset), - ): - btn = self._tr(QPushButton(), key) - btn.clicked.connect(handler) - row.addWidget(btn) - row.addStretch(1) - return row - def _apply_table_headers(self) -> None: self._table.setHorizontalHeaderLabels([ _t("inspector_col_age"), _t("inspector_metric_rtt_ms"), diff --git a/je_auto_control/gui/language_wrapper/english.py b/je_auto_control/gui/language_wrapper/english.py index 66da26d2..77be9539 100644 --- a/je_auto_control/gui/language_wrapper/english.py +++ b/je_auto_control/gui/language_wrapper/english.py @@ -935,7 +935,6 @@ # Profiler tab "prof_enable": "Enable profiler", - "prof_disable": "Disable profiler", "prof_reset": "Reset stats", "prof_refresh": "Refresh", "prof_running": "Profiler is recording.", @@ -1308,4 +1307,8 @@ "menu_language": "Language", "menu_help": "Help", "menu_help_about": "About AutoControlGUI", + "menu_actions": "Actions", + "menu_actions_none": "(No actions on this tab)", + "menu_choose_script_dir": "Choose Script Directory...", + "execute_editor_script": "Run Editor Content", } diff --git a/je_auto_control/gui/language_wrapper/japanese.py b/je_auto_control/gui/language_wrapper/japanese.py index 83dbfe6b..1f683e96 100644 --- a/je_auto_control/gui/language_wrapper/japanese.py +++ b/je_auto_control/gui/language_wrapper/japanese.py @@ -824,7 +824,6 @@ # Profiler tab "prof_enable": "プロファイラを有効化", - "prof_disable": "プロファイラを無効化", "prof_reset": "統計をリセット", "prof_refresh": "更新", "prof_running": "プロファイラ計測中。", @@ -1195,4 +1194,8 @@ "menu_language": "言語", "menu_help": "ヘルプ", "menu_help_about": "AutoControlGUI について", + "menu_actions": "アクション", + "menu_actions_none": "(このタブにアクションはありません)", + "menu_choose_script_dir": "スクリプトフォルダーを選択...", + "execute_editor_script": "エディター内容を実行", } diff --git a/je_auto_control/gui/language_wrapper/simplified_chinese.py b/je_auto_control/gui/language_wrapper/simplified_chinese.py index b4bb4930..94c5c5a3 100644 --- a/je_auto_control/gui/language_wrapper/simplified_chinese.py +++ b/je_auto_control/gui/language_wrapper/simplified_chinese.py @@ -813,7 +813,6 @@ # Profiler tab "prof_enable": "启用性能分析", - "prof_disable": "停用性能分析", "prof_reset": "清除统计", "prof_refresh": "刷新", "prof_running": "性能分析中。", @@ -1180,4 +1179,8 @@ "menu_language": "语言", "menu_help": "帮助", "menu_help_about": "关于 AutoControlGUI", + "menu_actions": "操作", + "menu_actions_none": "(此标签页没有操作)", + "menu_choose_script_dir": "选择脚本目录...", + "execute_editor_script": "运行编辑器内容", } diff --git a/je_auto_control/gui/language_wrapper/traditional_chinese.py b/je_auto_control/gui/language_wrapper/traditional_chinese.py index ca6012c8..af9881b0 100644 --- a/je_auto_control/gui/language_wrapper/traditional_chinese.py +++ b/je_auto_control/gui/language_wrapper/traditional_chinese.py @@ -814,7 +814,6 @@ # Profiler tab "prof_enable": "啟用效能分析", - "prof_disable": "停用效能分析", "prof_reset": "清除統計", "prof_refresh": "重新整理", "prof_running": "效能分析中。", @@ -1181,4 +1180,8 @@ "menu_language": "語言", "menu_help": "說明", "menu_help_about": "關於 AutoControlGUI", + "menu_actions": "操作", + "menu_actions_none": "(此分頁沒有操作)", + "menu_choose_script_dir": "選擇腳本資料夾...", + "execute_editor_script": "執行編輯器內容", } diff --git a/je_auto_control/gui/live_hud_tab.py b/je_auto_control/gui/live_hud_tab.py index 95649a22..58842925 100644 --- a/je_auto_control/gui/live_hud_tab.py +++ b/je_auto_control/gui/live_hud_tab.py @@ -3,7 +3,7 @@ from PySide6.QtCore import QTimer from PySide6.QtWidgets import ( - QGroupBox, QHBoxLayout, QLabel, QPushButton, QTextEdit, QVBoxLayout, QWidget, + QGroupBox, QLabel, QTextEdit, QVBoxLayout, QWidget, ) from je_auto_control.gui._i18n_helpers import TranslatableMixin @@ -48,6 +48,8 @@ def retranslate(self) -> None: self._apply_position_labels() def _build_layout(self) -> None: + # Start/stop/clear commands run from the Actions menu; the tab + # keeps only the watcher labels and the log view. root = QVBoxLayout(self) status_group = self._tr(QGroupBox(), "hud_watchers_group") status_layout = QVBoxLayout() @@ -56,21 +58,17 @@ def _build_layout(self) -> None: status_group.setLayout(status_layout) root.addWidget(status_group) - ctl = QHBoxLayout() - start_btn = self._tr(QPushButton(), "hud_start") - start_btn.clicked.connect(self._start) - stop_btn = self._tr(QPushButton(), "hud_stop") - stop_btn.clicked.connect(self._stop) - clear_btn = self._tr(QPushButton(), "hud_clear") - clear_btn.clicked.connect(self._log_view.clear) - for btn in (start_btn, stop_btn, clear_btn): - ctl.addWidget(btn) - ctl.addStretch() - root.addLayout(ctl) - root.addWidget(self._tr(QLabel(), "hud_recent_log")) root.addWidget(self._log_view, stretch=1) + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("hud_start", self._start), + ("hud_stop", self._stop), + ("hud_clear", self._log_view.clear), + ] + def _start(self) -> None: self._log_tail.attach(autocontrol_logger) self._timer.start() diff --git a/je_auto_control/gui/llm_planner_tab.py b/je_auto_control/gui/llm_planner_tab.py index 348ba218..83bc7352 100644 --- a/je_auto_control/gui/llm_planner_tab.py +++ b/je_auto_control/gui/llm_planner_tab.py @@ -10,7 +10,7 @@ from PySide6.QtCore import QObject, QThread, Signal from PySide6.QtWidgets import ( - QGroupBox, QHBoxLayout, QLabel, QLineEdit, QMessageBox, QPushButton, + QGroupBox, QHBoxLayout, QLabel, QLineEdit, QMessageBox, QTextEdit, QVBoxLayout, QWidget, ) @@ -69,13 +69,10 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self._result_view.setReadOnly(True) self._status = QLabel() self._planned_actions: Optional[list] = None - self._plan_btn: Optional[QPushButton] = None - self._run_btn: Optional[QPushButton] = None self._plan_thread: Optional[QThread] = None self._plan_worker: Optional[_PlanWorker] = None self._build_layout() self._apply_placeholders() - self._set_run_enabled(False) def retranslate(self) -> None: TranslatableMixin.retranslate(self) @@ -86,6 +83,8 @@ def _apply_placeholders(self) -> None: self._model.setPlaceholderText(_t("llm_model_placeholder")) def _build_layout(self) -> None: + # Plan/run commands run from the Actions menu; the tab keeps only + # the description/model inputs, the plan/result views, and status. root = QVBoxLayout(self) desc_group = self._tr(QGroupBox(), "llm_desc_group") @@ -95,15 +94,6 @@ def _build_layout(self) -> None: model_row.addWidget(self._tr(QLabel(), "llm_model_label")) model_row.addWidget(self._model, stretch=1) desc_layout.addLayout(model_row) - btn_row = QHBoxLayout() - self._plan_btn = self._tr(QPushButton(), "llm_plan_btn") - self._plan_btn.clicked.connect(self._on_plan) - self._run_btn = self._tr(QPushButton(), "llm_run_btn") - self._run_btn.clicked.connect(self._on_run) - btn_row.addWidget(self._plan_btn) - btn_row.addWidget(self._run_btn) - btn_row.addStretch() - desc_layout.addLayout(btn_row) desc_group.setLayout(desc_layout) root.addWidget(desc_group) @@ -121,9 +111,12 @@ def _build_layout(self) -> None: root.addWidget(self._status) - def _set_run_enabled(self, enabled: bool) -> None: - if self._run_btn is not None: - self._run_btn.setEnabled(enabled) + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("llm_plan_btn", self._on_plan), + ("llm_run_btn", self._on_run), + ] def _on_plan(self) -> None: description = self._description.toPlainText().strip() @@ -133,12 +126,9 @@ def _on_plan(self) -> None: if self._plan_thread is not None and self._plan_thread.isRunning(): return model = self._model.text().strip() or None - if self._plan_btn is not None: - self._plan_btn.setEnabled(False) self._status.setText(_t("llm_planning")) self._actions_view.clear() self._planned_actions = None - self._set_run_enabled(False) worker = _PlanWorker(description, model, sorted(executor.known_commands())) thread = QThread(self) worker.moveToThread(thread) @@ -160,11 +150,9 @@ def _on_plan_finished(self, actions: list) -> None: self._status.setText( _t("llm_plan_count").replace("{n}", str(len(actions))) ) - self._set_run_enabled(bool(actions)) def _on_plan_failed(self, message: str) -> None: self._planned_actions = None - self._set_run_enabled(False) QMessageBox.warning(self, _t("llm_plan_btn"), message) self._status.setText(message) @@ -175,8 +163,6 @@ def _on_thread_done(self) -> None: self._plan_worker.deleteLater() self._plan_thread = None self._plan_worker = None - if self._plan_btn is not None: - self._plan_btn.setEnabled(True) def _on_run(self) -> None: if not self._planned_actions: diff --git a/je_auto_control/gui/main_widget.py b/je_auto_control/gui/main_widget.py index 73497a1f..6483303c 100644 --- a/je_auto_control/gui/main_widget.py +++ b/je_auto_control/gui/main_widget.py @@ -5,7 +5,7 @@ from PySide6.QtCore import QTimer, Signal, QObject from PySide6.QtGui import QIntValidator, QDoubleValidator, QKeyEvent, Qt from PySide6.QtWidgets import ( - QWidget, QLineEdit, QPushButton, QVBoxLayout, QLabel, + QWidget, QLineEdit, QVBoxLayout, QLabel, QGridLayout, QHBoxLayout, QMessageBox, QTabWidget, QTextEdit, QFileDialog, QCheckBox, QGroupBox ) @@ -69,6 +69,7 @@ from je_auto_control.wrapper.auto_control_screen import screen_size, screenshot, get_pixel from je_auto_control.wrapper.auto_control_image import locate_all_image, locate_image_center, locate_and_click from je_auto_control.wrapper.auto_control_record import record, stop_record +from je_auto_control.utils.exception.exceptions import AutoControlException from je_auto_control.utils.executor.action_executor import execute_action, execute_files from je_auto_control.utils.json.json_file import read_action_json, write_action_json from je_auto_control.utils.file_process.get_dir_file_list import get_dir_files_as_list @@ -94,6 +95,7 @@ class _TabEntry: widget: QWidget category: str = "core" default_visible: bool = False + actions: tuple = () # ============================================================================= @@ -105,6 +107,7 @@ class AutoControlGUIWidget( """Owns the QTabWidget and exposes show/hide/list APIs for the menu bar.""" tabs_changed = Signal() + current_tab_changed = Signal() def __init__(self, parent=None): super().__init__(parent) @@ -123,19 +126,50 @@ def __init__(self, parent=None): # core tabs (auto_click / screenshot / image_detect) are still # registered and reachable from the View menu's "show tab" list. self._add_tab("auto_click", "tab_auto_click", self._build_auto_click_tab(), - category="core") + category="core", actions=( + ("start", self._start_auto_click), + ("stop", self._stop_auto_click), + ("get_position", self._get_mouse_pos), + ("hotkey_send", self._send_hotkey), + ("write_send", self._send_write), + ("scroll_send", self._send_scroll), + )) self._add_tab("screenshot", "tab_screenshot", self._build_screenshot_tab(), - category="core") + category="core", actions=( + ("take_screenshot", self._take_screenshot), + ("browse", self._browse_ss_path), + ("pick_region", self._pick_ss_region), + ("get_screen_size", self._get_screen_size), + ("get_pixel_label", self._get_pixel_color), + )) self._add_tab("image_detect", "tab_image_detect", self._build_image_detect_tab(), - category="core") + category="core", actions=( + ("browse", self._browse_img), + ("crop_template", self._crop_template), + ("locate_image", self._locate_image), + ("locate_all", self._locate_all), + ("locate_click", self._locate_click), + )) self._add_tab("record", "tab_record", self._build_record_tab(), - category="core", default_visible=True) + category="core", default_visible=True, actions=( + ("start_record", self._start_record), + ("stop_record", self._stop_record), + ("playback", self._playback_record), + ("save_record", self._save_record), + ("load_record", self._load_record), + )) self._add_tab("script_builder", "tab_script_builder", ScriptBuilderTab(), category="core", default_visible=True) self._add_tab("flow_editor", "tab_flow_editor", FlowEditorTab(), category="editing") self._add_tab("script", "tab_script", self._build_script_tab(), - category="editing") + category="editing", actions=( + ("load_script", self._browse_script), + ("execute_script", self._execute_script), + ("menu_choose_script_dir", self._browse_script_dir), + ("execute_dir", self._execute_dir), + ("execute_editor_script", self._execute_manual_script), + )) self._add_tab("recording_editor", "tab_recording_editor", RecordingEditorTab(), category="editing") self._add_tab("variables", "tab_variables", VariablesTab(), @@ -220,11 +254,19 @@ def __init__(self, parent=None): self._add_tab("diagnostics", "tab_diagnostics", DiagnosticsTab(), category="system") self._add_tab("report", "tab_report", self._build_report_tab(), - category="system") + category="system", actions=( + ("enable_test_record", self._enable_test_record), + ("disable_test_record", self._disable_test_record), + ("generate_html_report", self._gen_html), + ("generate_json_report", self._gen_json), + ("generate_xml_report", self._gen_xml), + )) layout.addWidget(self.tabs) self.setLayout(layout) + self.tabs.currentChanged.connect(self._on_current_tab_changed) + self.timer = QTimer() self.repeat_count = 0 self.repeat_max = 0 @@ -255,14 +297,40 @@ def _build_remote_desktop_tab() -> QWidget: def _add_tab( self, key: str, title_key: str, widget: QWidget, category: str = "core", default_visible: bool = False, + actions: tuple = (), ) -> None: self._tab_entries.append(_TabEntry( key=key, title_key=title_key, widget=widget, category=category, default_visible=default_visible, + actions=actions, )) if default_visible: self.tabs.addTab(widget, language_wrapper.translate(title_key, title_key)) + def _on_current_tab_changed(self, _index: int) -> None: + self.current_tab_changed.emit() + + def current_tab_menu_actions(self) -> list: + """Return ``[(label_key, callable), ...]`` for the active tab. + + Core tabs declare their actions at registration time; feature tabs + may instead expose a ``menu_actions()`` method returning the same + shape. The menu bar renders these under the Actions menu so tabs + stay button-free. + """ + widget = self.tabs.currentWidget() + if widget is None: + return [] + for entry in self._tab_entries: + if entry.widget is widget: + if entry.actions: + return list(entry.actions) + provider = getattr(widget, "menu_actions", None) + if callable(provider): + return list(provider()) + return [] + return [] + def _find_entry(self, key: str): for entry in self._tab_entries: if entry.key == key: @@ -363,44 +431,29 @@ def _build_screenshot_tab(self) -> QWidget: tab = QWidget() layout = QVBoxLayout() - # Screen size + # Screen size (read via Actions menu -> Get Screen Size) size_group = self._tr(QGroupBox(), "screen_size_label") sg = QHBoxLayout() self.screen_size_label = QLabel("--") - self.screen_size_btn = self._tr(QPushButton(), "get_screen_size") - self.screen_size_btn.clicked.connect(self._get_screen_size) sg.addWidget(self.screen_size_label) - sg.addWidget(self.screen_size_btn) size_group.setLayout(sg) layout.addWidget(size_group) - # Screenshot + # Screenshot inputs; capture runs from the Actions menu. ss_group = self._tr(QGroupBox(), "take_screenshot") ss_grid = QGridLayout() ss_grid.addWidget(self._tr(QLabel(), "file_path_label"), 0, 0) self.ss_path_input = QLineEdit() ss_grid.addWidget(self.ss_path_input, 0, 1) - self.ss_browse_btn = self._tr(QPushButton(), "browse") - self.ss_browse_btn.clicked.connect(self._browse_ss_path) - ss_grid.addWidget(self.ss_browse_btn, 0, 2) ss_grid.addWidget(self._tr(QLabel(), "region_label"), 1, 0) self.ss_region_input = QLineEdit() self.ss_region_input.setPlaceholderText("0, 0, 800, 600") ss_grid.addWidget(self.ss_region_input, 1, 1) - self.ss_pick_region_btn = self._tr(QPushButton(), "pick_region") - self.ss_pick_region_btn.clicked.connect(self._pick_ss_region) - ss_grid.addWidget(self.ss_pick_region_btn, 1, 2) - - btn_h = QHBoxLayout() - self.ss_take_btn = self._tr(QPushButton(), "take_screenshot") - self.ss_take_btn.clicked.connect(self._take_screenshot) - btn_h.addWidget(self.ss_take_btn) - ss_grid.addLayout(btn_h, 2, 0, 1, 3) ss_group.setLayout(ss_grid) layout.addWidget(ss_group) - # Get pixel + # Pixel probe inputs; lookup runs from the Actions menu. px_group = self._tr(QGroupBox(), "get_pixel_label") px_grid = QGridLayout() px_grid.addWidget(self._tr(QLabel(), "pixel_x"), 0, 0) @@ -411,15 +464,12 @@ def _build_screenshot_tab(self) -> QWidget: self.pixel_y_input = QLineEdit("0") self.pixel_y_input.setValidator(QIntValidator()) px_grid.addWidget(self.pixel_y_input, 0, 3) - self.pixel_btn = self._tr(QPushButton(), "get_pixel_label") - self.pixel_btn.clicked.connect(self._get_pixel_color) - px_grid.addWidget(self.pixel_btn, 1, 0, 1, 2) self.pixel_result_label = QLabel() self._pixel_result_suffix = " --" self.pixel_result_label.setText( self._translate("pixel_result") + self._pixel_result_suffix, ) - px_grid.addWidget(self.pixel_result_label, 1, 2, 1, 2) + px_grid.addWidget(self.pixel_result_label, 1, 0, 1, 4) px_group.setLayout(px_grid) layout.addWidget(px_group) @@ -487,16 +537,11 @@ def _build_image_detect_tab(self) -> QWidget: tab = QWidget() layout = QVBoxLayout() + # Detection inputs; locate/crop commands run from the Actions menu. grid = QGridLayout() grid.addWidget(self._tr(QLabel(), "template_image"), 0, 0) self.img_path_input = QLineEdit() grid.addWidget(self.img_path_input, 0, 1) - self.img_browse_btn = self._tr(QPushButton(), "browse") - self.img_browse_btn.clicked.connect(self._browse_img) - grid.addWidget(self.img_browse_btn, 0, 2) - self.img_crop_btn = self._tr(QPushButton(), "crop_template") - self.img_crop_btn.clicked.connect(self._crop_template) - grid.addWidget(self.img_crop_btn, 0, 3) grid.addWidget(self._tr(QLabel(), "threshold_label"), 1, 0) self.threshold_input = QLineEdit("0.8") @@ -507,18 +552,6 @@ def _build_image_detect_tab(self) -> QWidget: layout.addLayout(grid) - btn_h = QHBoxLayout() - self.locate_btn = self._tr(QPushButton(), "locate_image") - self.locate_btn.clicked.connect(self._locate_image) - self.locate_all_btn = self._tr(QPushButton(), "locate_all") - self.locate_all_btn.clicked.connect(self._locate_all) - self.locate_click_btn = self._tr(QPushButton(), "locate_click") - self.locate_click_btn.clicked.connect(self._locate_click) - btn_h.addWidget(self.locate_btn) - btn_h.addWidget(self.locate_all_btn) - btn_h.addWidget(self.locate_click_btn) - layout.addLayout(btn_h) - layout.addWidget(self._tr(QLabel(), "detection_result")) self.detect_result_text = QTextEdit() self.detect_result_text.setReadOnly(True) @@ -586,32 +619,12 @@ def _build_record_tab(self) -> QWidget: tab = QWidget() layout = QVBoxLayout() + # Record/playback/save/load all run from the Actions menu. self._record_status_key = "record_idle" self.record_status_label = QLabel() self._apply_record_status_label() layout.addWidget(self.record_status_label) - btn_h = QHBoxLayout() - self.rec_start_btn = self._tr(QPushButton(), "start_record") - self.rec_start_btn.clicked.connect(self._start_record) - self.rec_stop_btn = self._tr(QPushButton(), "stop_record") - self.rec_stop_btn.clicked.connect(self._stop_record) - self.rec_play_btn = self._tr(QPushButton(), "playback") - self.rec_play_btn.clicked.connect(self._playback_record) - btn_h.addWidget(self.rec_start_btn) - btn_h.addWidget(self.rec_stop_btn) - btn_h.addWidget(self.rec_play_btn) - layout.addLayout(btn_h) - - btn_h2 = QHBoxLayout() - self.rec_save_btn = self._tr(QPushButton(), "save_record") - self.rec_save_btn.clicked.connect(self._save_record) - self.rec_load_btn = self._tr(QPushButton(), "load_record") - self.rec_load_btn.clicked.connect(self._load_record) - btn_h2.addWidget(self.rec_save_btn) - btn_h2.addWidget(self.rec_load_btn) - layout.addLayout(btn_h2) - layout.addWidget(self._tr(QLabel(), "record_list_label")) self.record_list_text = QTextEdit() self.record_list_text.setReadOnly(True) @@ -634,7 +647,7 @@ def _start_record(self): record() self._record_status_key = "record_recording" self._apply_record_status_label() - except (OSError, ValueError, TypeError, RuntimeError) as error: + except (AutoControlException, OSError, ValueError, TypeError, RuntimeError) as error: QMessageBox.warning(self, "Error", str(error)) def _stop_record(self): @@ -643,7 +656,7 @@ def _stop_record(self): self._record_status_key = "record_idle" self._apply_record_status_label() self.record_list_text.setText(json.dumps(self._record_data, indent=2, ensure_ascii=False)) - except (OSError, ValueError, TypeError, RuntimeError) as error: + except (AutoControlException, OSError, ValueError, TypeError, RuntimeError) as error: QMessageBox.warning(self, "Error", str(error)) def _playback_record(self): @@ -652,7 +665,7 @@ def _playback_record(self): QMessageBox.warning(self, "Warning", "No recorded data") return execute_action(self._record_data) - except (OSError, ValueError, TypeError, RuntimeError) as error: + except (AutoControlException, OSError, ValueError, TypeError, RuntimeError) as error: QMessageBox.warning(self, "Error", str(error)) def _save_record(self): @@ -663,7 +676,7 @@ def _save_record(self): path, _ = QFileDialog.getSaveFileName(self, _t("save_record"), "", _JSON_FILE_FILTER) if path: write_action_json(path, self._record_data) - except (OSError, ValueError, TypeError, RuntimeError) as error: + except (AutoControlException, OSError, ValueError, TypeError, RuntimeError) as error: QMessageBox.warning(self, "Error", str(error)) def _load_record(self): @@ -672,7 +685,7 @@ def _load_record(self): if path: self._record_data = read_action_json(path) self.record_list_text.setText(json.dumps(self._record_data, indent=2, ensure_ascii=False)) - except (OSError, ValueError, TypeError, RuntimeError) as error: + except (AutoControlException, OSError, ValueError, TypeError, RuntimeError) as error: QMessageBox.warning(self, "Error", str(error)) # ========================================================================= @@ -682,26 +695,18 @@ def _build_script_tab(self) -> QWidget: tab = QWidget() layout = QVBoxLayout() + # Load/execute commands run from the Actions menu; the tab keeps + # only the path inputs, the editor, and the result view. file_h = QHBoxLayout() + file_h.addWidget(self._tr(QLabel(), "file_path_label")) self.script_path_input = QLineEdit() - self.script_browse_btn = self._tr(QPushButton(), "load_script") - self.script_browse_btn.clicked.connect(self._browse_script) - self.script_exec_btn = self._tr(QPushButton(), "execute_script") - self.script_exec_btn.clicked.connect(self._execute_script) file_h.addWidget(self.script_path_input) - file_h.addWidget(self.script_browse_btn) - file_h.addWidget(self.script_exec_btn) layout.addLayout(file_h) dir_h = QHBoxLayout() + dir_h.addWidget(self._tr(QLabel(), "execute_dir_label")) self.script_dir_input = QLineEdit() - self.script_dir_browse_btn = self._tr(QPushButton(), "execute_dir_label") - self.script_dir_browse_btn.clicked.connect(self._browse_script_dir) - self.script_dir_exec_btn = self._tr(QPushButton(), "execute_dir") - self.script_dir_exec_btn.clicked.connect(self._execute_dir) dir_h.addWidget(self.script_dir_input) - dir_h.addWidget(self.script_dir_browse_btn) - dir_h.addWidget(self.script_dir_exec_btn) layout.addLayout(dir_h) layout.addWidget(self._tr(QLabel(), "script_content")) @@ -709,10 +714,6 @@ def _build_script_tab(self) -> QWidget: self.script_editor.setPlaceholderText('[["AC_type_keyboard", {"keycode": "a"}]]') layout.addWidget(self.script_editor) - exec_btn = self._tr(QPushButton(), "execute_script") - exec_btn.clicked.connect(self._execute_manual_script) - layout.addWidget(exec_btn) - layout.addWidget(self._tr(QLabel(), "execution_result")) self.script_result_text = QTextEdit() self.script_result_text.setReadOnly(True) @@ -727,7 +728,7 @@ def _browse_script(self): try: data = read_action_json(path) self.script_editor.setText(json.dumps(data, indent=2, ensure_ascii=False)) - except (OSError, ValueError, TypeError, RuntimeError) as error: + except (AutoControlException, OSError, ValueError, TypeError, RuntimeError) as error: self.script_result_text.setText(f"Error loading: {error}") def _execute_script(self): @@ -738,7 +739,7 @@ def _execute_script(self): data = read_action_json(path) result = execute_action(data) self.script_result_text.setText(json.dumps(result, indent=2, default=str, ensure_ascii=False)) - except (OSError, ValueError, TypeError, RuntimeError) as error: + except (AutoControlException, OSError, ValueError, TypeError, RuntimeError) as error: self.script_result_text.setText(f"Error: {error}") def _browse_script_dir(self): @@ -754,7 +755,7 @@ def _execute_dir(self): files = get_dir_files_as_list(path) result = execute_files(files) self.script_result_text.setText(json.dumps(result, indent=2, default=str, ensure_ascii=False)) - except (OSError, ValueError, TypeError, RuntimeError) as error: + except (AutoControlException, OSError, ValueError, TypeError, RuntimeError) as error: self.script_result_text.setText(f"Error: {error}") def _execute_manual_script(self): diff --git a/je_auto_control/gui/main_window.py b/je_auto_control/gui/main_window.py index b228649d..863f5424 100644 --- a/je_auto_control/gui/main_window.py +++ b/je_auto_control/gui/main_window.py @@ -47,6 +47,10 @@ def __init__(self) -> None: self._user_font_pt: int = 0 # 0 means auto-detect from screen self.apply_stylesheet(self, "dark_amber.xml") + # qt_material writes the theme into this window's stylesheet; capture it + # so _apply_font_pt can append the font rule instead of replacing (and + # thereby wiping) the theme. + self._theme_stylesheet: str = self.styleSheet() self._apply_font_pt(self._user_font_pt) self.setWindowTitle(_t("application_name", "AutoControlGUI")) @@ -56,9 +60,14 @@ def __init__(self) -> None: self.setCentralWidget(self.auto_control_gui_widget) self._view_menu: QMenu = None + self._actions_menu: QMenu = None self._tab_actions: list = [] self._build_menu_bar() self.auto_control_gui_widget.tabs_changed.connect(self._rebuild_tabs_menu) + self.auto_control_gui_widget.tabs_changed.connect(self._rebuild_actions_menu) + self.auto_control_gui_widget.current_tab_changed.connect( + self._rebuild_actions_menu, + ) language_wrapper.add_listener(self._on_language_changed) # --- menu construction --------------------------------------------------- @@ -67,11 +76,34 @@ def _build_menu_bar(self) -> None: bar = self.menuBar() bar.clear() bar.addMenu(self._build_file_menu()) + bar.addMenu(self._build_actions_menu()) bar.addMenu(self._build_view_menu()) bar.addMenu(self._build_tools_menu()) bar.addMenu(self._build_language_menu()) bar.addMenu(self._build_help_menu()) + def _build_actions_menu(self) -> QMenu: + """Per-tab command menu: the active tab's operations live here + instead of as buttons inside the tab.""" + self._actions_menu = QMenu(_t("menu_actions", "Actions"), self) + self._rebuild_actions_menu() + return self._actions_menu + + def _rebuild_actions_menu(self) -> None: + if self._actions_menu is None: + return + self._actions_menu.clear() + entries = self.auto_control_gui_widget.current_tab_menu_actions() + if not entries: + placeholder = QAction( + _t("menu_actions_none", "(No actions on this tab)"), self, + ) + placeholder.setEnabled(False) + self._actions_menu.addAction(placeholder) + return + for label_key, handler in entries: + self._actions_menu.addAction(_t(label_key, label_key), handler) + def _build_file_menu(self) -> QMenu: menu = QMenu(_t("menu_file", "File"), self) open_action = QAction(_t("menu_file_open_script", "Open Script..."), self) @@ -142,8 +174,15 @@ def _detect_auto_font_pt(self) -> int: return 12 def _apply_font_pt(self, pt: int) -> None: + """Apply the font size on top of the active theme stylesheet. + + The theme lives in this window's stylesheet, so the font rule is + appended rather than assigned — assigning would replace (and wipe) the + qt_material theme on startup and on every text-size change. + """ effective = pt if pt > 0 else self._detect_auto_font_pt() - self.setStyleSheet(f"font-size: {effective}pt; font-family: 'Lato';") + font_rule = f"* {{ font-size: {effective}pt; font-family: 'Lato'; }}" + self.setStyleSheet(f"{self._theme_stylesheet}\n{font_rule}") def _on_text_size_selected(self) -> None: action = self.sender() diff --git a/je_auto_control/gui/media_checks_tab.py b/je_auto_control/gui/media_checks_tab.py index 20bc6218..ec00328d 100644 --- a/je_auto_control/gui/media_checks_tab.py +++ b/je_auto_control/gui/media_checks_tab.py @@ -7,7 +7,7 @@ from PySide6.QtWidgets import ( QCheckBox, QDoubleSpinBox, QFileDialog, QHBoxLayout, QLabel, QLineEdit, - QPushButton, QVBoxLayout, QWidget, + QVBoxLayout, QWidget, ) from je_auto_control.gui._i18n_helpers import TranslatableMixin @@ -47,6 +47,8 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self._build_layout() def _build_layout(self) -> None: + # Audio/video check and browse commands run from the Actions menu; + # the tab keeps only the inputs and the result label. root = QVBoxLayout(self) root.addWidget(QLabel(_t("media_audio_label"))) arow = QHBoxLayout() @@ -55,9 +57,6 @@ def _build_layout(self) -> None: arow.addWidget(QLabel(_t("media_audio_threshold"))) arow.addWidget(self._audio_threshold) arow.addWidget(self._audio_expect) - audio_btn = self._tr(QPushButton(), "media_audio_run") - audio_btn.clicked.connect(self._on_audio) - arow.addWidget(audio_btn) arow.addStretch() root.addLayout(arow) @@ -65,23 +64,25 @@ def _build_layout(self) -> None: vrow = QHBoxLayout() vrow.addWidget(QLabel(_t("media_video_path"))) vrow.addWidget(self._video_path, stretch=1) - browse_btn = self._tr(QPushButton(), "media_video_browse") - browse_btn.clicked.connect(self._on_browse) - vrow.addWidget(browse_btn) root.addLayout(vrow) vrow2 = QHBoxLayout() vrow2.addWidget(QLabel(_t("media_video_threshold"))) vrow2.addWidget(self._video_threshold) vrow2.addWidget(self._video_expect) - video_btn = self._tr(QPushButton(), "media_video_run") - video_btn.clicked.connect(self._on_video) - vrow2.addWidget(video_btn) vrow2.addStretch() root.addLayout(vrow2) root.addWidget(self._result) root.addStretch() + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("media_audio_run", self._on_audio), + ("media_video_browse", self._on_browse), + ("media_video_run", self._on_video), + ] + def _on_browse(self) -> None: path, _ = QFileDialog.getOpenFileName(self, _t("media_video_browse")) if path: diff --git a/je_auto_control/gui/ocr_tab.py b/je_auto_control/gui/ocr_tab.py index 4a0a4322..bc41c9c2 100644 --- a/je_auto_control/gui/ocr_tab.py +++ b/je_auto_control/gui/ocr_tab.py @@ -4,7 +4,7 @@ from typing import Optional from PySide6.QtWidgets import ( - QGroupBox, QHBoxLayout, QLabel, QLineEdit, QMessageBox, QPushButton, + QGroupBox, QHBoxLayout, QLabel, QLineEdit, QMessageBox, QTextEdit, QVBoxLayout, QWidget, ) @@ -59,15 +59,14 @@ def _apply_placeholders(self) -> None: self._regex.setPlaceholderText(_t("ocr_regex_placeholder")) def _build_layout(self) -> None: + # Pick/dump/find commands run from the Actions menu; the tab keeps + # only the region/params/regex inputs, the results view, and status. root = QVBoxLayout(self) region_group = self._tr(QGroupBox(), "ocr_region_group") region_layout = QHBoxLayout() region_layout.addWidget(self._tr(QLabel(), "ocr_region_label")) region_layout.addWidget(self._region, stretch=1) - pick_btn = self._tr(QPushButton(), "ocr_pick_region") - pick_btn.clicked.connect(self._on_pick_region) - region_layout.addWidget(pick_btn) region_group.setLayout(region_layout) root.addWidget(region_group) @@ -84,20 +83,18 @@ def _build_layout(self) -> None: regex_layout.addWidget(self._regex, stretch=1) root.addLayout(regex_layout) - btn_row = QHBoxLayout() - dump_btn = self._tr(QPushButton(), "ocr_dump_region") - dump_btn.clicked.connect(self._on_dump) - find_btn = self._tr(QPushButton(), "ocr_find_regex") - find_btn.clicked.connect(self._on_find_regex) - btn_row.addWidget(dump_btn) - btn_row.addWidget(find_btn) - btn_row.addStretch() - root.addLayout(btn_row) - root.addWidget(self._tr(QLabel(), "ocr_results_label")) root.addWidget(self._result, stretch=1) root.addWidget(self._status) + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("ocr_pick_region", self._on_pick_region), + ("ocr_dump_region", self._on_dump), + ("ocr_find_regex", self._on_find_regex), + ] + def _on_pick_region(self) -> None: region = open_region_selector(self) if region is None: diff --git a/je_auto_control/gui/plugins_tab.py b/je_auto_control/gui/plugins_tab.py index dd4f0a57..82baa40a 100644 --- a/je_auto_control/gui/plugins_tab.py +++ b/je_auto_control/gui/plugins_tab.py @@ -3,7 +3,7 @@ from PySide6.QtWidgets import ( QFileDialog, QHBoxLayout, QLabel, QLineEdit, QListWidget, QMessageBox, - QPushButton, QVBoxLayout, QWidget, + QVBoxLayout, QWidget, ) from je_auto_control.gui._i18n_helpers import TranslatableMixin @@ -33,21 +33,24 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self._build_layout() def _build_layout(self) -> None: + # Browse/load commands run from the Actions menu; the tab keeps + # only the directory input, the command list, and the status. root = QVBoxLayout(self) form = QHBoxLayout() form.addWidget(self._tr(QLabel(), "pl_dir_label")) form.addWidget(self._dir_input, stretch=1) - browse = self._tr(QPushButton(), "browse") - browse.clicked.connect(self._browse) - form.addWidget(browse) - load = self._tr(QPushButton(), "pl_load") - load.clicked.connect(self._on_load) - form.addWidget(load) root.addLayout(form) root.addWidget(self._tr(QLabel(), "pl_registered_label")) root.addWidget(self._list, stretch=1) root.addWidget(self._status) + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("browse", self._browse), + ("pl_load", self._on_load), + ] + def retranslate(self) -> None: TranslatableMixin.retranslate(self) if self._status_is_translatable: diff --git a/je_auto_control/gui/presence_tab.py b/je_auto_control/gui/presence_tab.py index 0d981506..844071ef 100644 --- a/je_auto_control/gui/presence_tab.py +++ b/je_auto_control/gui/presence_tab.py @@ -7,9 +7,9 @@ """ from typing import Optional -from PySide6.QtCore import Qt, QTimer +from PySide6.QtCore import Qt, QTimer, Signal from PySide6.QtWidgets import ( - QHBoxLayout, QHeaderView, QLabel, QPushButton, QTableWidget, + QHeaderView, QLabel, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, ) @@ -33,6 +33,10 @@ def _t(key: str) -> str: class PresenceTab(TranslatableMixin, QWidget): """Roster view + role controls for the multi-viewer presence registry.""" + # Emitted from the registry listener (which runs off the Qt thread); a + # queued connection bounces the refresh onto the GUI thread. + _registry_changed = Signal() + def __init__(self, parent: Optional[QWidget] = None) -> None: super().__init__(parent) self._tr_init() @@ -40,6 +44,7 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self._table = QTableWidget(0, len(_COLUMNS)) self._status = QLabel() self._build_layout() + self._registry_changed.connect(self.refresh) # Listener fires on every change; the timer is a belt-and-braces # refresh in case a listener exception drops us off. self._registry.add_listener(self._on_registry_event) @@ -56,32 +61,25 @@ def retranslate(self) -> None: # --- layout ---------------------------------------------------- def _build_layout(self) -> None: + # Refresh/promote/demote/kick commands run from the Actions menu; + # the tab keeps only the roster table and the status line. root = QVBoxLayout(self) - controls = QHBoxLayout() - for key, slot in ( - ("presence_refresh_btn", self.refresh), - ("presence_promote_btn", self._on_promote), - ("presence_demote_btn", self._on_demote), - ("presence_kick_btn", self._on_kick), - ): - btn = QPushButton() - btn.setObjectName(key) - btn.clicked.connect(slot) - controls.addWidget(btn) - controls.addStretch() - root.addLayout(controls) root.addWidget(self._table, stretch=1) root.addWidget(self._status) header = self._table.horizontalHeader() header.setSectionResizeMode(QHeaderView.Stretch) self._apply_translations() + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("presence_refresh_btn", self.refresh), + ("presence_promote_btn", self._on_promote), + ("presence_demote_btn", self._on_demote), + ("presence_kick_btn", self._on_kick), + ] + def _apply_translations(self) -> None: - for key in ("presence_refresh_btn", "presence_promote_btn", - "presence_demote_btn", "presence_kick_btn"): - btn = self.findChild(QPushButton, key) - if btn is not None: - btn.setText(_t(key)) self._table.setHorizontalHeaderLabels( [_t(f"presence_col_{col}") for col in _COLUMNS], ) @@ -110,8 +108,10 @@ def _populate_row(self, index: int, row: ViewerPresence) -> None: def _on_registry_event(self, _viewer_id: str, _row: Optional[ViewerPresence]) -> None: - # Listener runs off the Qt thread; bounce onto the GUI loop. - QTimer.singleShot(0, self.refresh) + # Listener runs off the Qt thread, which has no event loop, so + # QTimer.singleShot would never fire. Emit a signal — Qt queues the + # refresh onto the GUI thread. + self._registry_changed.emit() def _selected_viewer_id(self) -> Optional[str]: row = self._table.currentRow() diff --git a/je_auto_control/gui/profiler_tab.py b/je_auto_control/gui/profiler_tab.py index 853f41f7..9f6c5463 100644 --- a/je_auto_control/gui/profiler_tab.py +++ b/je_auto_control/gui/profiler_tab.py @@ -3,8 +3,8 @@ from PySide6.QtCore import QTimer, Qt from PySide6.QtWidgets import ( - QAbstractItemView, QHBoxLayout, QHeaderView, QLabel, QProgressBar, - QPushButton, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, + QAbstractItemView, QHeaderView, QLabel, QProgressBar, + QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, ) from je_auto_control.gui._i18n_helpers import TranslatableMixin @@ -69,23 +69,21 @@ def _apply_table_headers(self) -> None: ]) def _build_layout(self) -> None: + # Enable/reset/refresh commands run from the Actions menu; the tab + # keeps only the table, the total bar, and the status line. root = QVBoxLayout(self) - controls = QHBoxLayout() - self._enable_btn = self._tr(QPushButton(), "prof_enable") - self._enable_btn.clicked.connect(self._toggle_enable) - controls.addWidget(self._enable_btn) - reset_btn = self._tr(QPushButton(), "prof_reset") - reset_btn.clicked.connect(self._on_reset) - controls.addWidget(reset_btn) - refresh_btn = self._tr(QPushButton(), "prof_refresh") - refresh_btn.clicked.connect(self._refresh) - controls.addWidget(refresh_btn) - controls.addStretch() - root.addLayout(controls) root.addWidget(self._table, stretch=1) root.addWidget(self._totalbar) root.addWidget(self._status) + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("prof_enable", self._toggle_enable), + ("prof_reset", self._on_reset), + ("prof_refresh", self._refresh), + ] + def _toggle_enable(self) -> None: if default_profiler.enabled: default_profiler.disable() @@ -115,10 +113,6 @@ def _refresh(self) -> None: running_text = _t("prof_running") if default_profiler.enabled \ else _t("prof_paused") self._status.setText(running_text) - self._enable_btn.setText( - _t("prof_disable") if default_profiler.enabled - else _t("prof_enable"), - ) def _set_row(self, row: int, stats: ActionStats, share: float) -> None: values = ( diff --git a/je_auto_control/gui/recording_editor_tab.py b/je_auto_control/gui/recording_editor_tab.py index 3d325397..b22c9097 100644 --- a/je_auto_control/gui/recording_editor_tab.py +++ b/je_auto_control/gui/recording_editor_tab.py @@ -5,7 +5,7 @@ from PySide6.QtGui import QKeySequence, QShortcut from PySide6.QtWidgets import ( QFileDialog, QHBoxLayout, QInputDialog, QLabel, QLineEdit, QListWidget, - QMessageBox, QPushButton, QTextEdit, QVBoxLayout, QWidget, + QMessageBox, QTextEdit, QVBoxLayout, QWidget, ) from je_auto_control.gui._i18n_helpers import TranslatableMixin @@ -65,19 +65,12 @@ def _undo(self) -> None: self._refresh() def _build_layout(self) -> None: + # Load/save/export and every edit command run from the Actions + # menu; the tab keeps only inputs, the list, preview, and status. root = QVBoxLayout(self) top = QHBoxLayout() top.addWidget(self._tr(QLabel(), "re_file_label")) top.addWidget(self._path_input, stretch=1) - for key, handler in ( - ("re_browse", self._browse), - ("re_load", self._load), - ("re_save_as", self._save_as), - ("re_export_code", self._export_code), - ): - btn = self._tr(QPushButton(), key) - btn.clicked.connect(handler) - top.addWidget(btn) root.addLayout(top) root.addWidget(self._list, stretch=1) @@ -87,15 +80,6 @@ def _build_layout(self) -> None: ops1.addWidget(self._trim_start) ops1.addWidget(self._tr(QLabel(), "re_trim_end")) ops1.addWidget(self._trim_end) - trim_btn = self._tr(QPushButton(), "re_apply_trim") - trim_btn.clicked.connect(self._apply_trim) - ops1.addWidget(trim_btn) - remove_btn = self._tr(QPushButton(), "re_remove_selected") - remove_btn.clicked.connect(self._remove_selected) - ops1.addWidget(remove_btn) - undo_btn = self._tr(QPushButton(), "re_undo") - undo_btn.clicked.connect(self._undo) - ops1.addWidget(undo_btn) ops1.addStretch() root.addLayout(ops1) @@ -104,36 +88,42 @@ def _build_layout(self) -> None: ops2.addWidget(self._delay_factor) ops2.addWidget(self._tr(QLabel(), "re_floor_ms")) ops2.addWidget(self._delay_clamp) - delay_btn = self._tr(QPushButton(), "re_apply_delays") - delay_btn.clicked.connect(self._apply_delays) - ops2.addWidget(delay_btn) ops2.addWidget(self._tr(QLabel(), "re_scale_x")) ops2.addWidget(self._scale_x) ops2.addWidget(self._tr(QLabel(), "re_scale_y")) ops2.addWidget(self._scale_y) - scale_btn = self._tr(QPushButton(), "re_apply_scale") - scale_btn.clicked.connect(self._apply_scale) - ops2.addWidget(scale_btn) ops2.addStretch() root.addLayout(ops2) - ops3 = QHBoxLayout() - keep_mouse = self._tr(QPushButton(), "re_keep_mouse") - keep_mouse.clicked.connect(lambda: self._filter_prefix("AC_mouse")) - keep_keyboard = self._tr(QPushButton(), "re_keep_keyboard") - keep_keyboard.clicked.connect( - lambda: self._filter_prefix(("AC_type_keyboard", "AC_press_keyboard_key", - "AC_release_keyboard_key", "AC_hotkey", "AC_write")) - ) - ops3.addWidget(keep_mouse) - ops3.addWidget(keep_keyboard) - ops3.addStretch() - root.addLayout(ops3) - root.addWidget(self._tr(QLabel(), "re_preview")) root.addWidget(self._preview, stretch=1) root.addWidget(self._status) + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("re_browse", self._browse), + ("re_load", self._load), + ("re_save_as", self._save_as), + ("re_export_code", self._export_code), + ("re_apply_trim", self._apply_trim), + ("re_remove_selected", self._remove_selected), + ("re_undo", self._undo), + ("re_apply_delays", self._apply_delays), + ("re_apply_scale", self._apply_scale), + ("re_keep_mouse", self._keep_mouse), + ("re_keep_keyboard", self._keep_keyboard), + ] + + def _keep_mouse(self) -> None: + self._filter_prefix("AC_mouse") + + def _keep_keyboard(self) -> None: + self._filter_prefix(( + "AC_type_keyboard", "AC_press_keyboard_key", + "AC_release_keyboard_key", "AC_hotkey", "AC_write", + )) + def _browse(self) -> None: path, _ = QFileDialog.getOpenFileName( self, _t("re_dialog_open"), "", "JSON (*.json)", diff --git a/je_auto_control/gui/remote_desktop/webrtc_dialogs.py b/je_auto_control/gui/remote_desktop/webrtc_dialogs.py index 0e527b2d..68d3ff68 100644 --- a/je_auto_control/gui/remote_desktop/webrtc_dialogs.py +++ b/je_auto_control/gui/remote_desktop/webrtc_dialogs.py @@ -734,9 +734,13 @@ class LanBrowseDialog(QDialog): """ chosen = Signal(dict) + # Emitted from the zeroconf thread; a queued connection marshals the + # payload back onto the GUI thread (see _update_services). + _services_changed = Signal(dict) def __init__(self, parent: Optional[QWidget] = None) -> None: super().__init__(parent) + self._services_changed.connect(self._on_services_changed) self.setWindowTitle(_t("rd_webrtc_lan_title")) self.setMinimumSize(620, 260) self._services: dict = {} @@ -791,11 +795,14 @@ def _start_browser(self) -> None: self._browser = None def _update_services(self, services: dict) -> None: - # Called from zeroconf thread; marshal to GUI thread via signal-free - # workaround: invokeMethod is overkill here, just store + post. - self._services = dict(services) - from PySide6.QtCore import QTimer as _QTimer - _QTimer.singleShot(0, self._refresh) + # Called from the zeroconf browser thread, which has no Qt event loop: + # QTimer.singleShot would create a timer with that thread's affinity and + # never fire. Emit a signal instead — Qt queues it onto the GUI thread. + self._services_changed.emit(dict(services)) + + def _on_services_changed(self, services: dict) -> None: + self._services = services + self._refresh() def _refresh(self) -> None: items = sorted(self._services.values(), key=lambda s: s.get("host_id", "")) diff --git a/je_auto_control/gui/remote_desktop/webrtc_panel.py b/je_auto_control/gui/remote_desktop/webrtc_panel.py index 7b07cd4f..d5c226cd 100644 --- a/je_auto_control/gui/remote_desktop/webrtc_panel.py +++ b/je_auto_control/gui/remote_desktop/webrtc_panel.py @@ -110,6 +110,8 @@ class _PanelSignals(QObject): # Viewer-side file browser: list and op result. inbox_listing = Signal(object) # list[dict] inbox_op = Signal(str, bool, object) # name, ok, error + # Viewer-side: a file finished transferring (fired from the asyncio thread). + file_received = Signal(object) # path # Host-side: incoming viewer-shared screen frame viewer_video_frame = Signal(QImage) # Host-side: incoming annotation event from viewer @@ -1328,6 +1330,7 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self._signals.stats.connect(self._on_stats) self._signals.inbox_listing.connect(self._on_inbox_listing) self._signals.inbox_op.connect(self._on_inbox_op_result) + self._signals.file_received.connect(self._on_file_received_ui) self._build_ui() self._refresh_address_book() self._update_availability() @@ -2386,11 +2389,14 @@ def _build_viewer(self, token: str) -> WebRTCDesktopViewer: return viewer def _on_received_file(self, path) -> None: - # Called from the asyncio thread; marshal to Qt via a status update. - QTimer.singleShot( - 0, lambda: self._status_label.setText( - _t("rd_webrtc_file_received").format(name=str(path)), - ), + # Called from the asyncio thread, which has no Qt event loop: + # QTimer.singleShot would never fire. Emit a signal — Qt queues the + # status update onto the GUI thread. + self._signals.file_received.emit(path) + + def _on_file_received_ui(self, path) -> None: + self._status_label.setText( + _t("rd_webrtc_file_received").format(name=str(path)), ) def _stop_viewer_if_any(self) -> None: diff --git a/je_auto_control/gui/rest_api_tab.py b/je_auto_control/gui/rest_api_tab.py index a2ff701d..cd127441 100644 --- a/je_auto_control/gui/rest_api_tab.py +++ b/je_auto_control/gui/rest_api_tab.py @@ -8,7 +8,7 @@ from PySide6.QtGui import QGuiApplication from PySide6.QtWidgets import ( QCheckBox, QFileDialog, QGroupBox, QHBoxLayout, QLabel, QLineEdit, - QMessageBox, QPushButton, QSpinBox, QVBoxLayout, QWidget, + QMessageBox, QSpinBox, QVBoxLayout, QWidget, ) from je_auto_control.gui._i18n_helpers import TranslatableMixin @@ -52,12 +52,24 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self._timer.start() def _build_layout(self) -> None: + # Start/stop/copy/export/import commands run from the Actions + # menu; the tab keeps only the config inputs and the status group. root = QVBoxLayout(self) root.addWidget(self._build_config_group()) - root.addLayout(self._build_button_row()) root.addWidget(self._build_status_group()) root.addStretch(1) + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("rest_start", self._on_start), + ("rest_stop", self._on_stop), + ("rest_copy_url", self._on_copy_url), + ("rest_copy_token", self._on_copy_token), + ("rest_config_export", self._on_config_export), + ("rest_config_import", self._on_config_import), + ] + def _build_config_group(self) -> QGroupBox: group = self._tr(QGroupBox(), "rest_config_group") form = QVBoxLayout(group) @@ -75,29 +87,6 @@ def _build_config_group(self) -> QGroupBox: form.addWidget(self._audit_check) return group - def _build_button_row(self) -> QHBoxLayout: - row = QHBoxLayout() - start = self._tr(QPushButton(), "rest_start") - start.clicked.connect(self._on_start) - row.addWidget(start) - stop = self._tr(QPushButton(), "rest_stop") - stop.clicked.connect(self._on_stop) - row.addWidget(stop) - copy_url = self._tr(QPushButton(), "rest_copy_url") - copy_url.clicked.connect(self._on_copy_url) - row.addWidget(copy_url) - copy_token = self._tr(QPushButton(), "rest_copy_token") - copy_token.clicked.connect(self._on_copy_token) - row.addWidget(copy_token) - export_btn = self._tr(QPushButton(), "rest_config_export") - export_btn.clicked.connect(self._on_config_export) - row.addWidget(export_btn) - import_btn = self._tr(QPushButton(), "rest_config_import") - import_btn.clicked.connect(self._on_config_import) - row.addWidget(import_btn) - row.addStretch(1) - return row - def _on_config_export(self) -> None: path_str, _ = QFileDialog.getSaveFileName( self, _t("rest_config_export"), diff --git a/je_auto_control/gui/run_history_tab.py b/je_auto_control/gui/run_history_tab.py index baae3d75..4331892c 100644 --- a/je_auto_control/gui/run_history_tab.py +++ b/je_auto_control/gui/run_history_tab.py @@ -7,7 +7,7 @@ from PySide6.QtGui import QDesktopServices, QPixmap from PySide6.QtWidgets import ( QAbstractItemView, QComboBox, QFrame, QHBoxLayout, QHeaderView, QLabel, - QMessageBox, QPushButton, QSplitter, QTableWidget, QTableWidgetItem, + QMessageBox, QSplitter, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, ) @@ -129,20 +129,15 @@ def _apply_table_headers(self) -> None: ]) def _build_layout(self) -> None: + # Refresh/clear/open-artifact commands run from the Actions menu; + # the tab keeps only the filter, timeline, table, and preview. root = QVBoxLayout(self) top = QHBoxLayout() top.addWidget(self._tr(QLabel(), "rh_filter_label")) top.addWidget(self._filter) top.addStretch() - refresh_btn = self._tr(QPushButton(), "rh_refresh") - refresh_btn.clicked.connect(self._refresh) - top.addWidget(refresh_btn) - clear_btn = self._tr(QPushButton(), "rh_clear") - clear_btn.clicked.connect(self._on_clear) - top.addWidget(clear_btn) root.addLayout(top) - self._tr(QLabel(), "rh_timeline_heading") timeline_label = self._tr(QLabel(), "rh_timeline_heading") root.addWidget(timeline_label) root.addWidget(self._timeline) @@ -162,14 +157,16 @@ def _build_layout(self) -> None: self._table.cellDoubleClicked.connect(self._on_cell_double_clicked) self._table.itemSelectionChanged.connect(self._on_selection_changed) - open_row = QHBoxLayout() - self._open_artifact_btn = self._tr(QPushButton(), "rh_open_artifact") - self._open_artifact_btn.clicked.connect(self._open_selected_artifact) - open_row.addWidget(self._open_artifact_btn) - open_row.addStretch() - root.addLayout(open_row) root.addWidget(self._count_label) + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("rh_refresh", self._refresh), + ("rh_clear", self._on_clear), + ("rh_open_artifact", self._open_selected_artifact), + ] + def _on_clear(self) -> None: reply = QMessageBox.question( self, _t("rh_clear"), _t("rh_confirm_clear"), diff --git a/je_auto_control/gui/scheduler_tab.py b/je_auto_control/gui/scheduler_tab.py index 3d0bb8e5..15b4b63a 100644 --- a/je_auto_control/gui/scheduler_tab.py +++ b/je_auto_control/gui/scheduler_tab.py @@ -4,7 +4,7 @@ from PySide6.QtCore import QTimer from PySide6.QtWidgets import ( QCheckBox, QFileDialog, QHBoxLayout, QLabel, QLineEdit, QMessageBox, - QPushButton, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, + QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, ) from je_auto_control.gui._i18n_helpers import TranslatableMixin @@ -56,35 +56,29 @@ def retranslate(self) -> None: self._refresh_table() def _build_layout(self) -> None: + # Browse/add/remove/start/stop commands run from the Actions menu; + # the tab keeps only the inputs, the jobs table, and the status. root = QVBoxLayout(self) form = QHBoxLayout() form.addWidget(self._tr(QLabel(), "sch_script_label")) form.addWidget(self._path_input, stretch=1) - browse = self._tr(QPushButton(), "browse") - browse.clicked.connect(self._browse) - form.addWidget(browse) form.addWidget(self._tr(QLabel(), "sch_interval_label")) form.addWidget(self._interval_input) form.addWidget(self._repeat_check) - add_btn = self._tr(QPushButton(), "sch_add") - add_btn.clicked.connect(self._on_add) - form.addWidget(add_btn) root.addLayout(form) root.addWidget(self._table, stretch=1) + root.addWidget(self._status) - ctl = QHBoxLayout() - for key, handler in ( + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("browse", self._browse), + ("sch_add", self._on_add), ("sch_remove_selected", self._on_remove), ("sch_start", self._on_start), ("sch_stop", self._on_stop), - ): - btn = self._tr(QPushButton(), key) - btn.clicked.connect(handler) - ctl.addWidget(btn) - ctl.addStretch() - root.addLayout(ctl) - root.addWidget(self._status) + ] def _browse(self) -> None: path, _ = QFileDialog.getOpenFileName( diff --git a/je_auto_control/gui/script_builder/builder_tab.py b/je_auto_control/gui/script_builder/builder_tab.py index 904a7804..443f14da 100644 --- a/je_auto_control/gui/script_builder/builder_tab.py +++ b/je_auto_control/gui/script_builder/builder_tab.py @@ -21,6 +21,7 @@ from je_auto_control.gui.script_builder.step_model import ( Step, actions_to_steps, steps_to_actions, ) +from je_auto_control.utils.exception.exceptions import AutoControlException from je_auto_control.utils.executor.action_executor import execute_action from je_auto_control.utils.json.json_file import read_action_json, write_action_json @@ -129,7 +130,7 @@ def _on_save(self) -> None: actions = steps_to_actions(self._tree.root_steps()) write_action_json(path, actions) self._result.setPlainText(f"Saved: {path}") - except (OSError, ValueError, TypeError) as error: + except (AutoControlException, OSError, ValueError, TypeError) as error: QMessageBox.warning(self, "Error", str(error)) def _on_load(self) -> None: @@ -143,7 +144,7 @@ def _on_load(self) -> None: self._tree.load_steps(actions_to_steps(actions)) self._form.load_step(None) self._result.setPlainText(f"Loaded: {path}") - except (OSError, ValueError, TypeError) as error: + except (AutoControlException, OSError, ValueError, TypeError) as error: QMessageBox.warning(self, "Error", str(error)) def _on_run(self) -> None: @@ -156,5 +157,6 @@ def _on_run(self) -> None: self._result.setPlainText( json.dumps(result, indent=2, default=str, ensure_ascii=False) ) - except (OSError, ValueError, TypeError, RuntimeError) as error: + except (AutoControlException, OSError, ValueError, TypeError, + RuntimeError) as error: QMessageBox.warning(self, "Error", str(error)) diff --git a/je_auto_control/gui/script_builder/command_schema.py b/je_auto_control/gui/script_builder/command_schema.py index be1377da..283cc834 100644 --- a/je_auto_control/gui/script_builder/command_schema.py +++ b/je_auto_control/gui/script_builder/command_schema.py @@ -78,7 +78,6 @@ def _add_mouse_specs(specs: List[CommandSpec]) -> None: default="mouse_left"), FieldSpec("x", FieldType.INT, optional=True), FieldSpec("y", FieldType.INT, optional=True), - FieldSpec("times", FieldType.INT, optional=True, default=1, min_value=1), ), )) specs.append(CommandSpec( @@ -4840,7 +4839,9 @@ def _add_work_queue_specs(specs: List[CommandSpec]) -> None: )) specs.append(CommandSpec( "AC_execute_process", "Shell", "Start Executable", - fields=(FieldSpec("program_path", FieldType.FILE_PATH),), + # Must match start_exe(exe_path); the executor dispatches + # event(**params), so a renamed field is a guaranteed TypeError. + fields=(FieldSpec("exe_path", FieldType.FILE_PATH),), )) specs.append(CommandSpec( "AC_move_to_trash", "Shell", "Move File to Recycle Bin", diff --git a/je_auto_control/gui/script_builder/step_form_view.py b/je_auto_control/gui/script_builder/step_form_view.py index f336d4fd..014310bd 100644 --- a/je_auto_control/gui/script_builder/step_form_view.py +++ b/je_auto_control/gui/script_builder/step_form_view.py @@ -33,6 +33,14 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: super().__init__(parent) self._step: Optional[Step] = None self._editors: Dict[str, QWidget] = {} + # 填表期間必須抑制 commit。編輯器在建立時就接上 textChanged, + # 而 _populate_from_step 是一格一格填的,每填一格都會觸發 + # _commit_field,讀到的是還沒填完的表單。 + # Suppresses commits while populating. Editors are wired to + # textChanged/toggled at build time, and _populate_from_step fills them + # one at a time — so without this each setText() commits a half-filled + # form, overwriting params from fields that have not been set yet. + self._populating = False self._layout = QFormLayout(self) self._title = QLabel(_t("sb_no_step_selected")) self._layout.addRow(self._title) @@ -60,7 +68,11 @@ def load_step(self, step: Optional[Step]) -> None: editor = self._build_editor(field_spec) self._editors[field_spec.name] = editor self._layout.addRow(self._field_label(field_spec), editor) - self._populate_from_step(spec, step) + self._populating = True + try: + self._populate_from_step(spec, step) + finally: + self._populating = False def _clear(self) -> None: while self._layout.rowCount() > 1: @@ -149,18 +161,33 @@ def _populate_from_step(self, spec: CommandSpec, step: Step) -> None: _set_editor_value(editor, field_spec, value) def _commit_field(self) -> None: - if self._step is None: + if self._step is None or self._populating: return spec = COMMAND_SPECS.get(self._step.command) if spec is None: return - new_params: Dict[str, Any] = {} + # 以現有參數為基礎,只更新這張表單管得到的欄位。 + # Start from the params the step already has and update only the fields + # this form owns. Rebuilding from scratch silently dropped anything the + # schema has no field for — and a hand-authored action legitimately + # carries such params (nested `actions` lists, structural dicts). The + # drop fired merely on selecting a step, and builder_tab then wrote the + # truncated version straight back over the user's file. + new_params: Dict[str, Any] = dict(self._step.params) for field_spec in spec.fields: editor = self._editors.get(field_spec.name) if editor is None: continue - value = _read_editor_value(editor, field_spec) + ok, value = _read_field_value(editor, field_spec) + if not ok: + # Transient unparseable input (mid-typing, or a loaded "100.0" + # in an int field): keep this field's existing param instead of + # aborting the whole commit, which would freeze every edit. + continue if value is None and field_spec.optional: + # An emptied optional field means "no value" — drop the key + # rather than leaving the previous one behind. + new_params.pop(field_spec.name, None) continue new_params[field_spec.name] = value self._step.params = new_params @@ -249,6 +276,18 @@ def _read_editor_value(editor: QWidget, spec: FieldSpec) -> Any: return reader(editor) if reader is not None else None +def _read_field_value(editor: QWidget, spec: FieldSpec) -> tuple[bool, Any]: + """Read one field, returning ``(ok, value)``. + + ``ok`` is ``False`` when the current text cannot be parsed (int/float/rgb + fields), so the caller can skip that field instead of aborting the commit. + """ + try: + return True, _read_editor_value(editor, spec) + except (ValueError, TypeError): + return False, None + + _EDITOR_BUILDERS = { FieldType.STRING: StepFormView._build_string, FieldType.INT: StepFormView._build_int, diff --git a/je_auto_control/gui/script_builder/step_list_view.py b/je_auto_control/gui/script_builder/step_list_view.py index 2289715d..93d24e59 100644 --- a/je_auto_control/gui/script_builder/step_list_view.py +++ b/je_auto_control/gui/script_builder/step_list_view.py @@ -25,7 +25,14 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: super().__init__(parent) self.setHeaderLabels(["Step"]) self.setColumnCount(1) - self.setDragDropMode(QTreeWidget.DragDropMode.InternalMove) + # Item drag-drop is deliberately disabled: Qt's InternalMove reorders + # the QTreeWidgetItems without touching the Step model (``_roots`` and + # each ``Step.bodies``), so Save/Run would silently emit the pre-drag + # script, and a drop onto a plain step left the tree in a shape that + # crashed ``remove_selected``. Reordering is done through + # ``move_selected`` (the Up/Down commands), which keeps model and view + # in lock-step. + self.setDragDropMode(QTreeWidget.DragDropMode.NoDragDrop) self.setSelectionMode(QTreeWidget.SelectionMode.SingleSelection) self._roots: List[Step] = [] self.itemSelectionChanged.connect(self._emit_selection) @@ -79,9 +86,13 @@ def remove_selected(self) -> None: self.takeTopLevelItem(index) return body_key = parent.data(0, ROLE_BODY_KEY) - grandparent_step: Step = parent.parent().data(0, ROLE_STEP) + grandparent = parent.parent() + if grandparent is None: + parent.removeChild(item) + return + grandparent_step: Step = grandparent.data(0, ROLE_STEP) siblings = grandparent_step.bodies.get(body_key, []) - if step in siblings: + if step in siblings: # identity match — Step uses eq=False siblings.remove(step) parent.removeChild(item) diff --git a/je_auto_control/gui/script_builder/step_model.py b/je_auto_control/gui/script_builder/step_model.py index 58590c37..f70c08cd 100644 --- a/je_auto_control/gui/script_builder/step_model.py +++ b/je_auto_control/gui/script_builder/step_model.py @@ -5,12 +5,17 @@ from je_auto_control.gui.script_builder.command_schema import COMMAND_SPECS -@dataclass +@dataclass(eq=False) class Step: """A single node in the script tree. ``bodies`` maps body key (``body``, ``then``, ``else``) to child steps, mirroring the flow-control structure in the executor. + + Equality is identity-based (``eq=False``): the tree keeps its Steps in + plain lists and locates the *selected* one with ``list.remove``/``index``/ + ``in``. Value equality would match the first structurally-equal Step, so + deleting or moving one of several duplicate steps corrupted the model. """ command: str params: Dict[str, Any] = field(default_factory=dict) @@ -36,14 +41,26 @@ def step_to_action(step: Step) -> list: def action_to_step(action: list) -> Step: - """Convert a single action entry back to a Step.""" - if not action or not isinstance(action[0], str): + """Convert a single action entry back to a Step. + + Raises ``ValueError`` for anything that is not a ``[command, params?]`` + list. Without the ``isinstance(action, list)`` guard a string entry was + silently mis-parsed (``"auto_control"`` became command ``"a"``) and a dict + entry raised a bare ``KeyError`` instead of a clear message. + """ + if not isinstance(action, list) or not action or not isinstance(action[0], str): raise ValueError(f"Invalid action: {action!r}") command = action[0] raw_params: Mapping[str, Any] = action[1] if len(action) > 1 and isinstance(action[1], dict) else {} spec = COMMAND_SPECS.get(command) body_keys: Tuple[str, ...] = spec.body_keys if spec else () + params, bodies = _split_params(raw_params, body_keys) + return Step(command=command, params=params, bodies=bodies) + +def _split_params(raw_params: Mapping[str, Any], body_keys: Tuple[str, ...] + ) -> Tuple[Dict[str, Any], Dict[str, List[Step]]]: + """Partition raw params into scalar params and nested body step-lists.""" params: Dict[str, Any] = {} bodies: Dict[str, List[Step]] = {} for key, value in raw_params.items(): @@ -51,12 +68,31 @@ def action_to_step(action: list) -> Step: bodies[key] = [action_to_step(child) for child in value] else: params[key] = value - return Step(command=command, params=params, bodies=bodies) + return params, bodies -def actions_to_steps(actions: list) -> List[Step]: - """Convert a flat action list to a list of Steps.""" - return [action_to_step(entry) for entry in actions] +def actions_to_steps(actions: Any) -> List[Step]: + """Convert an action list (or ``{"auto_control": [...]}`` wrapper) to Steps. + + Mirrors what the executor accepts. Any other shape raises ``ValueError`` + rather than fabricating placeholder Steps that a later Save would write + back over the user's file. + """ + return [action_to_step(entry) for entry in _unwrap_action_list(actions)] + + +def _unwrap_action_list(actions: Any) -> list: + """Return the bare action list, unwrapping the ``auto_control`` mapping.""" + if isinstance(actions, dict): + wrapped = actions.get("auto_control") + if wrapped is None: + raise ValueError("Action mapping has no 'auto_control' key") + actions = wrapped + if not isinstance(actions, list): + raise ValueError( + f"Expected an action list, got {type(actions).__name__}" + ) + return actions def steps_to_actions(steps: List[Step]) -> list: diff --git a/je_auto_control/gui/secrets_tab.py b/je_auto_control/gui/secrets_tab.py index aae1362a..71244ab8 100644 --- a/je_auto_control/gui/secrets_tab.py +++ b/je_auto_control/gui/secrets_tab.py @@ -3,7 +3,7 @@ from PySide6.QtWidgets import ( QAbstractItemView, QGroupBox, QHBoxLayout, QInputDialog, QLabel, - QLineEdit, QListWidget, QListWidgetItem, QMessageBox, QPushButton, + QLineEdit, QListWidget, QListWidgetItem, QMessageBox, QVBoxLayout, QWidget, ) @@ -39,42 +39,35 @@ def retranslate(self) -> None: self._refresh_status() def _build_layout(self) -> None: + # Vault commands (init/unlock/lock/add/remove/change passphrase) + # run from the Actions menu; the tab keeps only passphrase input, + # the entry list, and the status line. root = QVBoxLayout(self) unlock_box = self._tr(QGroupBox(), "secret_unlock_group") unlock_layout = QHBoxLayout(unlock_box) unlock_layout.addWidget(self._tr(QLabel(), "secret_passphrase_label")) self._passphrase.setPlaceholderText(_t("secret_passphrase_placeholder")) unlock_layout.addWidget(self._passphrase) - init_btn = self._tr(QPushButton(), "secret_init") - init_btn.clicked.connect(self._on_init) - unlock_layout.addWidget(init_btn) - unlock_btn = self._tr(QPushButton(), "secret_unlock") - unlock_btn.clicked.connect(self._on_unlock) - unlock_layout.addWidget(unlock_btn) - lock_btn = self._tr(QPushButton(), "secret_lock") - lock_btn.clicked.connect(self._on_lock) - unlock_layout.addWidget(lock_btn) root.addWidget(unlock_box) manage_box = self._tr(QGroupBox(), "secret_manage_group") manage_layout = QVBoxLayout(manage_box) manage_layout.addWidget(self._list) - button_row = QHBoxLayout() - add_btn = self._tr(QPushButton(), "secret_add") - add_btn.clicked.connect(self._on_add) - button_row.addWidget(add_btn) - remove_btn = self._tr(QPushButton(), "secret_remove") - remove_btn.clicked.connect(self._on_remove) - button_row.addWidget(remove_btn) - change_btn = self._tr(QPushButton(), "secret_change_passphrase") - change_btn.clicked.connect(self._on_change_passphrase) - button_row.addWidget(change_btn) - button_row.addStretch() - manage_layout.addLayout(button_row) root.addWidget(manage_box, stretch=1) root.addWidget(self._status_label) + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("secret_unlock", self._on_unlock), + ("secret_lock", self._on_lock), + ("secret_add", self._on_add), + ("secret_remove", self._on_remove), + ("secret_init", self._on_init), + ("secret_change_passphrase", self._on_change_passphrase), + ] + def _refresh_status(self) -> None: manager = default_secret_manager if not manager.is_initialized: diff --git a/je_auto_control/gui/self_healing_tab.py b/je_auto_control/gui/self_healing_tab.py index 727c9cec..686b4bc6 100644 --- a/je_auto_control/gui/self_healing_tab.py +++ b/je_auto_control/gui/self_healing_tab.py @@ -8,7 +8,7 @@ from PySide6.QtCore import Qt from PySide6.QtWidgets import ( QCheckBox, QDoubleSpinBox, QFileDialog, QFormLayout, QGroupBox, - QHBoxLayout, QLabel, QLineEdit, QMessageBox, QPushButton, + QLabel, QLineEdit, QMessageBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, ) @@ -53,9 +53,10 @@ def retranslate(self) -> None: # --- layout ---------------------------------------------------- def _build_layout(self) -> None: + # Browse/locate/click/refresh/clear commands run from the Actions + # menu; the tab keeps only the inputs, the log table, and the status. root = QVBoxLayout(self) root.addWidget(self._build_form_group()) - root.addLayout(self._build_log_controls()) root.addWidget(self._table, stretch=1) root.addWidget(self._status) self._apply_translations() @@ -64,42 +65,22 @@ def _build_layout(self) -> None: def _build_form_group(self) -> QGroupBox: group = QGroupBox() form = QFormLayout(group) - template_row = QHBoxLayout() - template_row.addWidget(self._template_input, stretch=1) - browse = QPushButton() - browse.setObjectName("self_heal_browse_btn") - browse.clicked.connect(self._on_browse_template) - template_row.addWidget(browse) - form.addRow(QLabel(), template_row) + form.addRow(QLabel(), self._template_input) form.addRow(QLabel(), self._description_input) form.addRow(QLabel(), self._threshold) form.addRow(QLabel(), self._click_check) - run_row = QHBoxLayout() - locate_btn = QPushButton() - locate_btn.setObjectName("self_heal_locate_btn") - locate_btn.clicked.connect(self._on_locate) - run_btn = QPushButton() - run_btn.setObjectName("self_heal_click_btn") - run_btn.clicked.connect(self._on_click) - run_row.addWidget(locate_btn) - run_row.addWidget(run_btn) - run_row.addStretch() - form.addRow(QLabel(), run_row) self._group_box = group return group - def _build_log_controls(self) -> QHBoxLayout: - row = QHBoxLayout() - refresh = QPushButton() - refresh.setObjectName("self_heal_refresh_btn") - refresh.clicked.connect(self.refresh_log) - clear = QPushButton() - clear.setObjectName("self_heal_clear_btn") - clear.clicked.connect(self._on_clear_log) - row.addWidget(refresh) - row.addWidget(clear) - row.addStretch() - return row + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("self_heal_browse", self._on_browse_template), + ("self_heal_locate_btn", self._on_locate), + ("self_heal_click_btn", self._on_click), + ("self_heal_refresh", self.refresh_log), + ("self_heal_clear", self._on_clear_log), + ] # --- translation ----------------------------------------------- @@ -113,25 +94,14 @@ def _apply_translations(self) -> None: labels = ( "self_heal_template_label", "self_heal_desc_label", "self_heal_threshold_label", "", - "", ) for row, key in enumerate(labels): item = layout.itemAt(row, QFormLayout.LabelRole) if item is not None and isinstance(item.widget(), QLabel): item.widget().setText(_t(key) if key else "") - self._set_button_text("self_heal_browse_btn", "self_heal_browse") - self._set_button_text("self_heal_locate_btn", "self_heal_locate_btn") - self._set_button_text("self_heal_click_btn", "self_heal_click_btn") - self._set_button_text("self_heal_refresh_btn", "self_heal_refresh") - self._set_button_text("self_heal_clear_btn", "self_heal_clear") headers = [_t(f"self_heal_col_{name}") for name in _COLUMNS] self._table.setHorizontalHeaderLabels(headers) - def _set_button_text(self, object_name: str, translation_key: str) -> None: - widget = self.findChild(QPushButton, object_name) - if widget is not None: - widget.setText(_t(translation_key)) - # --- actions --------------------------------------------------- def _on_browse_template(self) -> None: diff --git a/je_auto_control/gui/test_suite_tab.py b/je_auto_control/gui/test_suite_tab.py index c2a88096..53f23f9b 100644 --- a/je_auto_control/gui/test_suite_tab.py +++ b/je_auto_control/gui/test_suite_tab.py @@ -8,8 +8,8 @@ from PySide6.QtCore import Qt from PySide6.QtWidgets import ( - QAbstractItemView, QFileDialog, QHBoxLayout, QLabel, QListWidget, - QPlainTextEdit, QPushButton, QTableWidget, QTableWidgetItem, QVBoxLayout, + QAbstractItemView, QFileDialog, QLabel, QListWidget, + QPlainTextEdit, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, ) @@ -18,6 +18,7 @@ language_wrapper, ) import je_auto_control as ac +from je_auto_control.utils.exception.exceptions import AutoControlException from je_auto_control.utils.quarantine import ( auto_quarantine_from_flakiness, default_quarantine_store, ) @@ -58,51 +59,49 @@ def _apply_headers(self) -> None: self._table.setHorizontalHeaderLabels([_t(k) for k in _COLS]) def _build_layout(self) -> None: + # Load/run/export and quarantine commands run from the Actions + # menu; the tab keeps only the spec editor, tables, and summary. root = QVBoxLayout(self) root.addWidget(QLabel(_t("suite_spec_label"))) root.addWidget(self._spec, stretch=1) - controls = QHBoxLayout() - load_btn = self._tr(QPushButton(), "suite_load_file") - load_btn.clicked.connect(self._on_load_file) - run_btn = self._tr(QPushButton(), "suite_run") - run_btn.clicked.connect(self._on_run) - junit_btn = self._tr(QPushButton(), "suite_junit") - junit_btn.clicked.connect(self._on_junit) - allure_btn = self._tr(QPushButton(), "suite_allure") - allure_btn.clicked.connect(self._on_allure) - for btn in (load_btn, run_btn, junit_btn, allure_btn): - controls.addWidget(btn) - controls.addStretch() - root.addLayout(controls) root.addWidget(self._table, stretch=2) root.addWidget(self._summary) root.addWidget(QLabel(_t("suite_q_label"))) root.addWidget(self._quarantine) - qrow = QHBoxLayout() - auto_btn = self._tr(QPushButton(), "suite_q_auto") - auto_btn.clicked.connect(self._on_auto_quarantine) - remove_btn = self._tr(QPushButton(), "suite_q_remove") - remove_btn.clicked.connect(self._on_release_selected) - clear_btn = self._tr(QPushButton(), "suite_q_clear") - clear_btn.clicked.connect(self._on_clear_quarantine) - for btn in (auto_btn, remove_btn, clear_btn): - qrow.addWidget(btn) - qrow.addStretch() - root.addLayout(qrow) + + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("suite_load_file", self._on_load_file), + ("suite_run", self._on_run), + ("suite_junit", self._on_junit), + ("suite_allure", self._on_allure), + ("suite_q_auto", self._on_auto_quarantine), + ("suite_q_remove", self._on_release_selected), + ("suite_q_clear", self._on_clear_quarantine), + ] def _parse_spec(self): return json.loads(self._spec.toPlainText() or "{}") def _on_load_file(self) -> None: path, _ = QFileDialog.getOpenFileName(self, _t("suite_load_file")) - if path: + if not path: + return + try: with open(path, encoding="utf-8") as handle: self._spec.setPlainText(handle.read()) + except (OSError, ValueError) as err: + # ValueError covers UnicodeDecodeError on a non-UTF-8 file; OSError + # covers unreadable/permission errors. Surface instead of escaping + # the Actions slot into the Qt event loop. + self._summary.setText(_t("suite_error").replace("{error}", str(err))) def _on_run(self) -> None: try: result = ac.run_suite(self._parse_spec()) - except (ValueError, OSError, RuntimeError) as err: + except (AutoControlException, ValueError, TypeError, + OSError, RuntimeError) as err: self._summary.setText(_t("suite_error").replace("{error}", str(err))) return self._last_result = result diff --git a/je_auto_control/gui/trace_replay_tab.py b/je_auto_control/gui/trace_replay_tab.py index eada9793..8e6f7bfc 100644 --- a/je_auto_control/gui/trace_replay_tab.py +++ b/je_auto_control/gui/trace_replay_tab.py @@ -12,7 +12,7 @@ from PySide6.QtCore import Qt from PySide6.QtGui import QPixmap from PySide6.QtWidgets import ( - QFileDialog, QHBoxLayout, QLabel, QPushButton, QSlider, + QFileDialog, QLabel, QSlider, QSplitter, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, ) @@ -58,21 +58,9 @@ def retranslate(self) -> None: # --- layout --------------------------------------------------- def _build_layout(self) -> None: + # Open/first/prev/next/last commands run from the Actions menu; + # the tab keeps only the slider, frame, table, and status. root = QVBoxLayout(self) - controls = QHBoxLayout() - for key, slot in ( - ("trace_open_btn", self._on_open), - ("trace_first_btn", self._on_first), - ("trace_prev_btn", self._on_prev), - ("trace_next_btn", self._on_next), - ("trace_last_btn", self._on_last), - ): - btn = QPushButton() - btn.setObjectName(key) - btn.clicked.connect(slot) - controls.addWidget(btn) - controls.addStretch() - root.addLayout(controls) root.addWidget(self._slider) splitter = QSplitter(Qt.Horizontal) splitter.addWidget(self._frame_label) @@ -83,12 +71,17 @@ def _build_layout(self) -> None: root.addWidget(self._status) self._apply_translations() + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("trace_open_btn", self._on_open), + ("trace_first_btn", self._on_first), + ("trace_prev_btn", self._on_prev), + ("trace_next_btn", self._on_next), + ("trace_last_btn", self._on_last), + ] + def _apply_translations(self) -> None: - for key in ("trace_open_btn", "trace_first_btn", - "trace_prev_btn", "trace_next_btn", "trace_last_btn"): - widget = self.findChild(QPushButton, key) - if widget is not None: - widget.setText(_t(key)) headers = [_t(f"trace_col_{col}") for col in _ACTION_COLUMNS] self._actions_table.setHorizontalHeaderLabels(headers) diff --git a/je_auto_control/gui/triggers_tab.py b/je_auto_control/gui/triggers_tab.py index 74b254ca..8ac27904 100644 --- a/je_auto_control/gui/triggers_tab.py +++ b/je_auto_control/gui/triggers_tab.py @@ -88,38 +88,32 @@ def retranslate(self) -> None: self._refresh() def _build_layout(self) -> None: + # Browse/add/remove/combine/start/stop commands run from the Actions + # menu; the tab keeps only the inputs, the table, and the status. root = QVBoxLayout(self) form_top = QHBoxLayout() form_top.addWidget(self._tr(QLabel(), "tr_script_label")) form_top.addWidget(self._script_input, stretch=1) - browse = self._tr(QPushButton(), "browse") - browse.clicked.connect(self._browse_script) - form_top.addWidget(browse) form_top.addWidget(self._repeat_check) form_top.addWidget(self._tr(QLabel(), "tr_type_label")) form_top.addWidget(self._type_combo) - add_btn = self._tr(QPushButton(), "tr_add") - add_btn.clicked.connect(self._on_add) - form_top.addWidget(add_btn) root.addLayout(form_top) root.addWidget(self._stack) root.addWidget(self._table, stretch=1) + root.addWidget(self._status) - ctl = QHBoxLayout() - for key, handler in ( + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("browse", self._browse_script), + ("tr_add", self._on_add), ("tr_remove_selected", self._on_remove), ("tr_combine_all", lambda: self._on_combine("all")), ("tr_combine_any", lambda: self._on_combine("any")), ("tr_combine_seq", lambda: self._on_combine("sequence")), ("tr_start_engine", self._on_start), ("tr_stop_engine", self._on_stop), - ): - btn = self._tr(QPushButton(), key) - btn.clicked.connect(handler) - ctl.addWidget(btn) - ctl.addStretch() - root.addLayout(ctl) - root.addWidget(self._status) + ] def _build_image_form(self) -> dict: widget = QWidget() diff --git a/je_auto_control/gui/usb_browser_tab.py b/je_auto_control/gui/usb_browser_tab.py index b2b0d738..c0aaffee 100644 --- a/je_auto_control/gui/usb_browser_tab.py +++ b/je_auto_control/gui/usb_browser_tab.py @@ -24,7 +24,7 @@ from PySide6.QtCore import QObject, QThread, Signal from PySide6.QtWidgets import ( QGroupBox, QHBoxLayout, QHeaderView, QLabel, QLineEdit, QMessageBox, - QPushButton, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, + QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, ) from je_auto_control.gui._i18n_helpers import TranslatableMixin @@ -167,12 +167,20 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self._apply_table_headers() def _build_layout(self) -> None: + # Fetch/open commands run from the Actions menu; the tab keeps + # only the target inputs, the status line, and the device table. root = QVBoxLayout(self) root.addWidget(self._build_target_group()) - root.addLayout(self._build_button_row()) root.addWidget(self._status_label) root.addWidget(self._table, stretch=1) + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("usb_browser_fetch", self._on_fetch), + ("usb_browser_open", self._on_open_selected), + ] + def _build_target_group(self) -> QGroupBox: group = self._tr(QGroupBox(), "usb_browser_target_group") form = QVBoxLayout(group) @@ -186,17 +194,6 @@ def _build_target_group(self) -> QGroupBox: form.addLayout(token_row) return group - def _build_button_row(self) -> QHBoxLayout: - row = QHBoxLayout() - refresh = self._tr(QPushButton(), "usb_browser_fetch") - refresh.clicked.connect(self._on_fetch) - row.addWidget(refresh) - open_btn = self._tr(QPushButton(), "usb_browser_open") - open_btn.clicked.connect(self._on_open_selected) - row.addWidget(open_btn) - row.addStretch(1) - return row - def _apply_table_headers(self) -> None: self._table.setHorizontalHeaderLabels([ _t("usb_browser_col_vid"), diff --git a/je_auto_control/gui/usb_devices_tab.py b/je_auto_control/gui/usb_devices_tab.py index 3effce4a..308169b4 100644 --- a/je_auto_control/gui/usb_devices_tab.py +++ b/je_auto_control/gui/usb_devices_tab.py @@ -3,7 +3,7 @@ from PySide6.QtCore import QTimer from PySide6.QtWidgets import ( - QCheckBox, QHBoxLayout, QHeaderView, QLabel, QPushButton, QTableWidget, + QCheckBox, QHBoxLayout, QHeaderView, QLabel, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, ) @@ -44,6 +44,8 @@ def __init__(self, parent: Optional[QWidget] = None) -> None: self._refresh() def _build_layout(self) -> None: + # The refresh command runs from the Actions menu; the tab keeps + # only the backend labels, the auto-refresh toggle, and the table. root = QVBoxLayout(self) header = QHBoxLayout() header.addWidget(self._tr(QLabel(), "usb_backend_label")) @@ -51,14 +53,17 @@ def _build_layout(self) -> None: header.addStretch(1) self._tr(self._auto_check, "usb_auto_refresh") header.addWidget(self._auto_check) - refresh = self._tr(QPushButton(), "usb_refresh") - refresh.clicked.connect(self._refresh) - header.addWidget(refresh) root.addLayout(header) root.addWidget(self._error_label) root.addWidget(self._events_label) root.addWidget(self._table, stretch=1) + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("usb_refresh", self._refresh), + ] + def _on_auto_toggled(self, on: bool) -> None: watcher = default_usb_watcher() if on: diff --git a/je_auto_control/gui/usb_passthrough_panel.py b/je_auto_control/gui/usb_passthrough_panel.py index 70404c40..16e64df4 100644 --- a/je_auto_control/gui/usb_passthrough_panel.py +++ b/je_auto_control/gui/usb_passthrough_panel.py @@ -22,7 +22,7 @@ from PySide6.QtCore import QObject, QThread, QTimer, Signal from PySide6.QtWidgets import ( QCheckBox, QComboBox, QFileDialog, QGroupBox, QHBoxLayout, QHeaderView, - QLabel, QLineEdit, QMessageBox, QPushButton, QTableWidget, + QLabel, QLineEdit, QMessageBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, ) @@ -116,49 +116,42 @@ def _default_loopback(self) -> UsbLoopback: # --- layout ------------------------------------------------------------ def _build_layout(self) -> None: + # Share/ACL/use/remote-fetch commands run from the Actions menu; + # the panel keeps only the badge, tables, inputs, and status. root = QHBoxLayout(self) root.setContentsMargins(16, 16, 16, 16) root.setSpacing(16) root.addWidget(self._build_host_section(), stretch=1) root.addWidget(self._build_viewer_section(), stretch=1) + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("usb_share_enable", self._enable_sharing), + ("usb_share_disable", self._disable_sharing), + ("usb_share_refresh_local", self._refresh_local_devices), + ("usb_share_allow", self._allow_selected), + ("usb_share_block", self._block_selected), + ("usb_share_export_acl", self._export_acl), + ("usb_share_import_acl", self._import_acl), + ("usb_share_fetch_shared", self._list_shared), + ("usb_share_open_selected", self._open_selected), + ("usb_share_remote_fetch", self._fetch_remote), + ] + def _build_host_section(self) -> QWidget: group = QGroupBox() self._tr(group, "usb_share_host_group", setter="setTitle") layout = QVBoxLayout(group) layout.setSpacing(8) layout.addWidget(self._host_badge) - btn_row = QHBoxLayout() - enable_btn = self._tr(QPushButton(), "usb_share_enable") - enable_btn.clicked.connect(self._enable_sharing) - disable_btn = self._tr(QPushButton(), "usb_share_disable") - disable_btn.clicked.connect(self._disable_sharing) - btn_row.addWidget(enable_btn, stretch=1) - btn_row.addWidget(disable_btn, stretch=1) - layout.addLayout(btn_row) layout.addWidget(self._tr(QLabel(), "usb_share_local_devices")) layout.addWidget(self._local_table, stretch=1) acl_row = QHBoxLayout() - refresh_btn = self._tr(QPushButton(), "usb_share_refresh_local") - refresh_btn.clicked.connect(self._refresh_local_devices) - allow_btn = self._tr(QPushButton(), "usb_share_allow") - allow_btn.clicked.connect(lambda: self._set_policy(True)) - block_btn = self._tr(QPushButton(), "usb_share_block") - block_btn.clicked.connect(lambda: self._set_policy(False)) - acl_row.addWidget(refresh_btn) - acl_row.addWidget(allow_btn) - acl_row.addWidget(block_btn) self._tr(self._auto_check, "usb_share_auto_refresh") acl_row.addWidget(self._auto_check) + acl_row.addStretch(1) layout.addLayout(acl_row) - io_row = QHBoxLayout() - export_btn = self._tr(QPushButton(), "usb_share_export_acl") - export_btn.clicked.connect(self._export_acl) - import_btn = self._tr(QPushButton(), "usb_share_import_acl") - import_btn.clicked.connect(self._import_acl) - io_row.addWidget(export_btn) - io_row.addWidget(import_btn) - layout.addLayout(io_row) return group def _build_viewer_section(self) -> QWidget: @@ -174,14 +167,6 @@ def _build_viewer_section(self) -> QWidget: source_row.addWidget(self._source_combo, stretch=1) layout.addLayout(source_row) layout.addWidget(self._shared_table, stretch=1) - use_row = QHBoxLayout() - list_btn = self._tr(QPushButton(), "usb_share_fetch_shared") - list_btn.clicked.connect(self._list_shared) - open_btn = self._tr(QPushButton(), "usb_share_open_selected") - open_btn.clicked.connect(self._open_selected) - use_row.addWidget(list_btn) - use_row.addWidget(open_btn) - layout.addLayout(use_row) layout.addWidget(self._viewer_status) layout.addWidget(self._build_remote_box()) return group @@ -198,9 +183,6 @@ def _build_remote_box(self) -> QWidget: token_row.addWidget(self._tr(QLabel(), "usb_share_remote_token")) token_row.addWidget(self._remote_token, stretch=1) layout.addLayout(token_row) - fetch_btn = self._tr(QPushButton(), "usb_share_remote_fetch") - fetch_btn.clicked.connect(self._fetch_remote) - layout.addWidget(fetch_btn) return box def _apply_local_headers(self) -> None: @@ -301,6 +283,12 @@ def _poll_hotplug(self) -> None: self._last_seen_seq = events[-1]["seq"] self._refresh_local_devices() + def _allow_selected(self) -> None: + self._set_policy(True) + + def _block_selected(self) -> None: + self._set_policy(False) + def _set_policy(self, allow: bool) -> None: row = _selected_row(self._local_table) if row is None: diff --git a/je_auto_control/gui/variables_tab.py b/je_auto_control/gui/variables_tab.py index dac0e592..08de6025 100644 --- a/je_auto_control/gui/variables_tab.py +++ b/je_auto_control/gui/variables_tab.py @@ -10,7 +10,7 @@ from PySide6.QtWidgets import ( QGroupBox, QHBoxLayout, QHeaderView, QLabel, QLineEdit, QMessageBox, - QPushButton, QTableWidget, QTableWidgetItem, QTextEdit, QVBoxLayout, + QTableWidget, QTableWidgetItem, QTextEdit, QVBoxLayout, QWidget, ) @@ -60,21 +60,14 @@ def _update_table_headers(self) -> None: ]) def _build_layout(self) -> None: + # Refresh/clear/set/seed commands run from the Actions menu; the + # tab keeps only the table, the inputs, and the status line. root = QVBoxLayout(self) view_group = self._tr(QGroupBox(), "vars_current_group") view_layout = QVBoxLayout() self._update_table_headers() view_layout.addWidget(self._table) - view_btns = QHBoxLayout() - refresh_btn = self._tr(QPushButton(), "vars_refresh") - refresh_btn.clicked.connect(self._refresh) - clear_btn = self._tr(QPushButton(), "vars_clear") - clear_btn.clicked.connect(self._on_clear) - view_btns.addWidget(refresh_btn) - view_btns.addWidget(clear_btn) - view_btns.addStretch() - view_layout.addLayout(view_btns) view_group.setLayout(view_layout) root.addWidget(view_group) @@ -84,23 +77,26 @@ def _build_layout(self) -> None: set_layout.addWidget(self._set_name, stretch=1) set_layout.addWidget(self._tr(QLabel(), "vars_value_label")) set_layout.addWidget(self._set_value, stretch=2) - set_btn = self._tr(QPushButton(), "vars_set_btn") - set_btn.clicked.connect(self._on_set_one) - set_layout.addWidget(set_btn) set_group.setLayout(set_layout) root.addWidget(set_group) seed_group = self._tr(QGroupBox(), "vars_seed_group") seed_layout = QVBoxLayout() seed_layout.addWidget(self._seed_text) - seed_btn = self._tr(QPushButton(), "vars_seed_btn") - seed_btn.clicked.connect(self._on_seed_json) - seed_layout.addWidget(seed_btn) seed_group.setLayout(seed_layout) root.addWidget(seed_group) root.addWidget(self._status) + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("vars_refresh", self._refresh), + ("vars_set_btn", self._on_set_one), + ("vars_seed_btn", self._on_seed_json), + ("vars_clear", self._on_clear), + ] + def _refresh(self) -> None: snapshot = executor.variables.as_dict() self._table.setRowCount(len(snapshot)) diff --git a/je_auto_control/gui/vlm_tab.py b/je_auto_control/gui/vlm_tab.py index 82aa41d5..785c06b5 100644 --- a/je_auto_control/gui/vlm_tab.py +++ b/je_auto_control/gui/vlm_tab.py @@ -2,7 +2,7 @@ from typing import Optional from PySide6.QtWidgets import ( - QHBoxLayout, QLabel, QLineEdit, QMessageBox, QPushButton, + QHBoxLayout, QLabel, QLineEdit, QMessageBox, QVBoxLayout, QWidget, ) @@ -42,6 +42,8 @@ def _apply_placeholders(self) -> None: self._model.setPlaceholderText(_t("vlm_model_placeholder")) def _build_layout(self) -> None: + # Locate/click commands run from the Actions menu; the tab keeps + # only the description/model inputs and the result labels. root = QVBoxLayout(self) desc_row = QHBoxLayout() desc_row.addWidget(self._tr(QLabel(), "vlm_desc_label")) @@ -51,19 +53,17 @@ def _build_layout(self) -> None: model_row.addWidget(self._tr(QLabel(), "vlm_model_label")) model_row.addWidget(self._model, stretch=1) root.addLayout(model_row) - btn_row = QHBoxLayout() - locate_btn = self._tr(QPushButton(), "vlm_locate") - locate_btn.clicked.connect(self._on_locate) - click_btn = self._tr(QPushButton(), "vlm_click") - click_btn.clicked.connect(self._on_click) - btn_row.addWidget(locate_btn) - btn_row.addWidget(click_btn) - btn_row.addStretch() - root.addLayout(btn_row) root.addWidget(self._last_result) root.addWidget(self._status) root.addStretch() + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("vlm_locate", self._on_locate), + ("vlm_click", self._on_click), + ] + def _collect_inputs(self): description = self._description.text().strip() if not description: diff --git a/je_auto_control/gui/webhooks_tab.py b/je_auto_control/gui/webhooks_tab.py index 131ff53b..2cc61c33 100644 --- a/je_auto_control/gui/webhooks_tab.py +++ b/je_auto_control/gui/webhooks_tab.py @@ -4,7 +4,7 @@ from PySide6.QtCore import QTimer, Qt from PySide6.QtWidgets import ( QAbstractItemView, QCheckBox, QFileDialog, QGroupBox, QHBoxLayout, - QHeaderView, QLabel, QLineEdit, QMessageBox, QPushButton, QSpinBox, + QHeaderView, QLabel, QLineEdit, QMessageBox, QSpinBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, ) @@ -73,6 +73,8 @@ def _apply_table_headers(self) -> None: ]) def _build_layout(self) -> None: + # Start/stop/browse/register/remove commands run from the Actions + # menu; the tab keeps only the inputs, the table, and the status. root = QVBoxLayout(self) server_box = self._tr(QGroupBox(), "wh_server_group") @@ -81,12 +83,6 @@ def _build_layout(self) -> None: server_layout.addWidget(self._host_input) server_layout.addWidget(self._tr(QLabel(), "wh_port_label")) server_layout.addWidget(self._port_input) - start_btn = self._tr(QPushButton(), "wh_start") - start_btn.clicked.connect(self._on_start) - server_layout.addWidget(start_btn) - stop_btn = self._tr(QPushButton(), "wh_stop") - stop_btn.clicked.connect(self._on_stop) - server_layout.addWidget(stop_btn) server_layout.addStretch() root.addWidget(server_box) root.addWidget(self._status_label) @@ -100,9 +96,6 @@ def _build_layout(self) -> None: script_row = QHBoxLayout() script_row.addWidget(self._tr(QLabel(), "wh_script_label")) script_row.addWidget(self._script_input) - browse_btn = self._tr(QPushButton(), "wh_browse") - browse_btn.clicked.connect(self._on_browse) - script_row.addWidget(browse_btn) add_layout.addLayout(script_row) method_row = QHBoxLayout() method_row.addWidget(self._tr(QLabel(), "wh_methods_label")) @@ -114,19 +107,20 @@ def _build_layout(self) -> None: token_row.addWidget(self._tr(QLabel(), "wh_token_label")) self._token_input.setPlaceholderText(_t("wh_token_placeholder")) token_row.addWidget(self._token_input) - register_btn = self._tr(QPushButton(), "wh_register") - register_btn.clicked.connect(self._on_register) - token_row.addWidget(register_btn) add_layout.addLayout(token_row) root.addWidget(add_box) root.addWidget(self._table, stretch=1) - action_row = QHBoxLayout() - remove_btn = self._tr(QPushButton(), "wh_remove") - remove_btn.clicked.connect(self._on_remove) - action_row.addWidget(remove_btn) - action_row.addStretch() - root.addLayout(action_row) + + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("wh_start", self._on_start), + ("wh_stop", self._on_stop), + ("wh_browse", self._on_browse), + ("wh_register", self._on_register), + ("wh_remove", self._on_remove), + ] def _on_start(self) -> None: host = self._host_input.text().strip() or "127.0.0.1" diff --git a/je_auto_control/gui/webrunner_tab.py b/je_auto_control/gui/webrunner_tab.py index ecd227c0..be0cda37 100644 --- a/je_auto_control/gui/webrunner_tab.py +++ b/je_auto_control/gui/webrunner_tab.py @@ -10,7 +10,7 @@ from PySide6.QtCore import Qt from PySide6.QtWidgets import ( QComboBox, QFileDialog, QFormLayout, QGroupBox, QHBoxLayout, QLabel, - QLineEdit, QListWidget, QMessageBox, QPushButton, QTextEdit, + QLineEdit, QListWidget, QMessageBox, QTextEdit, QVBoxLayout, QWidget, ) @@ -58,6 +58,8 @@ def retranslate(self) -> None: # --- layout ---------------------------------------------------- def _build_layout(self) -> None: + # Browse/open/quit/screenshot/run/refresh commands run from the + # Actions menu; the tab keeps only the inputs, list, and output. root = QVBoxLayout(self) root.addWidget(self._available_label) root.addWidget(self._build_convenience_group()) @@ -69,28 +71,23 @@ def _build_layout(self) -> None: root.addWidget(QLabel(_t("web_output_label"))) root.addWidget(self._output, stretch=2) + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("web_browse", self._on_browse_screenshot), + ("web_open_btn", self._on_open), + ("web_quit_btn", self._on_quit), + ("web_screenshot_btn", self._on_screenshot), + ("web_run_btn", self._on_run_freeform), + ("web_refresh_btn", self._on_refresh_commands), + ] + def _build_convenience_group(self) -> QGroupBox: group = QGroupBox(_t("web_convenience_title")) form = QFormLayout(group) form.addRow(QLabel(_t("web_url_label")), self._url_input) form.addRow(QLabel(_t("web_browser_label")), self._browser_input) - shot_row = QHBoxLayout() - shot_row.addWidget(self._screenshot_input, stretch=1) - browse = QPushButton(_t("web_browse")) - browse.clicked.connect(self._on_browse_screenshot) - shot_row.addWidget(browse) - form.addRow(QLabel(_t("web_screenshot_label")), shot_row) - actions_row = QHBoxLayout() - for label_key, slot in ( - ("web_open_btn", self._on_open), - ("web_quit_btn", self._on_quit), - ("web_screenshot_btn", self._on_screenshot), - ): - btn = QPushButton(_t(label_key)) - btn.clicked.connect(slot) - actions_row.addWidget(btn) - actions_row.addStretch() - form.addRow(QLabel(), actions_row) + form.addRow(QLabel(_t("web_screenshot_label")), self._screenshot_input) return group def _build_freeform_group(self) -> QGroupBox: @@ -102,15 +99,6 @@ def _build_freeform_group(self) -> QGroupBox: ) form.addRow(QLabel(_t("web_action_label")), self._action_input) form.addRow(QLabel(_t("web_params_label")), self._params_input) - run_row = QHBoxLayout() - run = QPushButton(_t("web_run_btn")) - run.clicked.connect(self._on_run_freeform) - refresh = QPushButton(_t("web_refresh_btn")) - refresh.clicked.connect(self._on_refresh_commands) - run_row.addWidget(run) - run_row.addWidget(refresh) - run_row.addStretch() - form.addRow(QLabel(), run_row) return group # --- availability --------------------------------------------- diff --git a/je_auto_control/gui/window_tab.py b/je_auto_control/gui/window_tab.py index 716fbdb0..d6b74353 100644 --- a/je_auto_control/gui/window_tab.py +++ b/je_auto_control/gui/window_tab.py @@ -3,7 +3,7 @@ from PySide6.QtCore import QTimer from PySide6.QtWidgets import ( - QHBoxLayout, QLabel, QLineEdit, QMessageBox, QPushButton, QTableWidget, + QHBoxLayout, QLabel, QLineEdit, QMessageBox, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget, ) @@ -58,25 +58,22 @@ def retranslate(self) -> None: self._apply_status() def _build_layout(self) -> None: + # Refresh/focus/close commands run from the Actions menu; the tab + # keeps only the filter input, the window table, and the status. root = QVBoxLayout(self) top = QHBoxLayout() - refresh = self._tr(QPushButton(), "win_refresh") - refresh.clicked.connect(self.refresh) - top.addWidget(refresh) top.addWidget(self._filter, stretch=1) root.addLayout(top) root.addWidget(self._table, stretch=1) - actions = QHBoxLayout() - for key, handler in ( + root.addWidget(self._status) + + def menu_actions(self) -> list: + """Expose tab commands to the window-level Actions menu.""" + return [ + ("win_refresh", self.refresh), ("win_focus_selected", self._on_focus), ("win_close_selected", self._on_close), - ): - btn = self._tr(QPushButton(), key) - btn.clicked.connect(handler) - actions.addWidget(btn) - actions.addStretch() - root.addLayout(actions) - root.addWidget(self._status) + ] def refresh(self) -> None: try: diff --git a/je_auto_control/linux_wayland/listener.py b/je_auto_control/linux_wayland/listener.py index 78be4a15..1c44dfa1 100644 --- a/je_auto_control/linux_wayland/listener.py +++ b/je_auto_control/linux_wayland/listener.py @@ -28,4 +28,21 @@ def hook_keyboard(*_args, **_kwargs): ) -__all__ = ["check_key_press", "hook_keyboard"] +def check_key_is_press(keycode: int | None = None) -> bool: + """Best-effort key-state query; on Wayland this always reports ``False``. + + Every other backend exposes ``check_key_is_press`` and the wrapper's + critical-exit watcher calls it on a timer. Wayland cannot read the + global key state from an unprivileged client, so rather than omitting + the name (which would ``AttributeError`` and kill the critical-exit + thread) this reports ``False`` — the panic key is inert on Wayland, + but callers degrade gracefully instead of crashing. + + :param keycode: key to query; ignored because no query is possible. + :return: always ``False``. + """ + del keycode + return False + + +__all__ = ["check_key_is_press", "check_key_press", "hook_keyboard"] diff --git a/je_auto_control/linux_wayland/mouse.py b/je_auto_control/linux_wayland/mouse.py index c271c3fe..0d94e617 100644 --- a/je_auto_control/linux_wayland/mouse.py +++ b/je_auto_control/linux_wayland/mouse.py @@ -16,7 +16,14 @@ from je_auto_control.utils.exception.exceptions import AutoControlException -# ydotool ``click`` accepts hex bitmasks. Hold-bit (0x40) | press (0x00). +# ydotool ``click`` accepts hex bitmasks: the low nibble selects the +# button (0 left, 1 right, 2 middle) and the high bits toggle the edge — +# ``0x40`` press-down, ``0x80`` release-up. The constants below carry both +# edges set (a full down+up click); ``_press_code`` / ``_release_code`` +# isolate a single edge so a press+move+release drag actually holds. +_YDOTOOL_DOWN_BIT = 0x40 +_YDOTOOL_UP_BIT = 0x80 + wayland_mouse_left = 0xC0 wayland_mouse_middle = 0xC2 wayland_mouse_right = 0xC1 @@ -92,16 +99,26 @@ def _try_libei(): return None +def _press_code(mouse_keycode: int) -> int: + """Button-down only: set the down-bit, clear the up-bit.""" + return (int(mouse_keycode) | _YDOTOOL_DOWN_BIT) & ~_YDOTOOL_UP_BIT + + +def _release_code(mouse_keycode: int) -> int: + """Button-up only: set the up-bit, clear the down-bit.""" + return (int(mouse_keycode) | _YDOTOOL_UP_BIT) & ~_YDOTOOL_DOWN_BIT + + def press_mouse(mouse_keycode: int) -> None: - """Press a mouse button (hold).""" + """Press a mouse button and hold it (down edge only).""" time.sleep(0.01) - _run([_require_ydotool(), "click", f"{int(mouse_keycode) | 0x40:#x}"]) + _run([_require_ydotool(), "click", f"{_press_code(mouse_keycode):#x}"]) def release_mouse(mouse_keycode: int) -> None: - """Release a held mouse button.""" + """Release a held mouse button (up edge only).""" time.sleep(0.01) - _run([_require_ydotool(), "click", f"{int(mouse_keycode) & ~0x40:#x}"]) + _run([_require_ydotool(), "click", f"{_release_code(mouse_keycode):#x}"]) def click_mouse(mouse_keycode: int, x: Optional[int] = None, @@ -113,13 +130,23 @@ def click_mouse(mouse_keycode: int, x: Optional[int] = None, _run([_require_ydotool(), "click", f"{int(mouse_keycode):#x}"]) -def scroll(direction: int, x: Optional[int] = None, - y: Optional[int] = None) -> None: - """Scroll by ``direction`` notches (positive = up, negative = down).""" - if x is not None and y is not None: - set_position(int(x), int(y)) - _run([_require_ydotool(), "mousemove", "--wheel", - "-y", str(int(direction))]) +def scroll(scroll_value: int, + scroll_direction: int = wayland_scroll_direction_down) -> None: + """Scroll ``scroll_value`` notches in ``scroll_direction``. + + The signature mirrors the x11 backend's ``scroll(scroll_value, + scroll_direction)`` because the wrapper treats every Linux backend + uniformly — it cannot tell Wayland from X11 by ``sys.platform``. The + previous ``(direction, x, y)`` shape silently bound the wrapper's + direction to ``x`` and then dropped it, so every scroll went the same way. + + ``scroll_direction`` carries axis and sign, per this module's + ``wayland_scroll_direction_*`` constants: +-1 vertical, +-2 horizontal. + """ + direction = int(scroll_direction) + axis = "-y" if abs(direction) == 1 else "-x" + amount = abs(int(scroll_value)) * (1 if direction > 0 else -1) + _run([_require_ydotool(), "mousemove", "--wheel", axis, str(amount)]) def send_mouse_event_to_window(*_args, **_kwargs) -> None: diff --git a/je_auto_control/linux_wayland/screen.py b/je_auto_control/linux_wayland/screen.py index 623e39ba..4ae4dc18 100644 --- a/je_auto_control/linux_wayland/screen.py +++ b/je_auto_control/linux_wayland/screen.py @@ -55,9 +55,12 @@ def _run(argv: list, *, timeout: float = 10.0) -> bytes: return completed.stdout or b"" -def screen_size() -> Tuple[int, int]: +def size() -> Tuple[int, int]: """Return the primary monitor's pixel size. + Named ``size`` to match the backend contract the wrapper calls + (``screen.size()``), as the windows / osx / x11 backends all do. + Tries ``wlr-randr`` first (sway / hyprland) then falls back to grim's PNG header so the call still works on GNOME / KDE without extra dependencies. @@ -68,6 +71,22 @@ def screen_size() -> Tuple[int, int]: return _size_from_grim_capture() +def get_pixel(x: int, y: int) -> Tuple[int, int, int]: + """Return the ``(r, g, b)`` colour at ``(x, y)``. + + grim can capture an arbitrary region, so a 1x1 grab at the requested + point is the cheapest way to read a single pixel. Returns RGB to match + the x11 backend. + """ + grim = _require_grim() + data = _run([grim, "-g", f"{int(x)},{int(y)} 1x1", "-"], timeout=10.0) + if not data: + raise AutoControlException("grim produced no output") + from io import BytesIO + with Image.open(BytesIO(data)) as image: + return image.convert("RGB").getpixel((0, 0)) + + def screenshot(file_path: Optional[str] = None, screen_region: Optional[List[int]] = None) -> Optional[str]: """Capture the screen with ``grim``. @@ -113,4 +132,4 @@ def _size_from_grim_capture() -> Tuple[int, int]: return int(image.width), int(image.height) -__all__ = ["screen_size", "screenshot"] +__all__ = ["size", "get_pixel", "screenshot"] diff --git a/je_auto_control/linux_with_x11/mouse/x11_linux_mouse_control.py b/je_auto_control/linux_with_x11/mouse/x11_linux_mouse_control.py index e05592a3..dff94b70 100644 --- a/je_auto_control/linux_with_x11/mouse/x11_linux_mouse_control.py +++ b/je_auto_control/linux_with_x11/mouse/x11_linux_mouse_control.py @@ -91,13 +91,17 @@ def scroll(scroll_value: int, scroll_direction: int) -> None: 模擬滑鼠滾動 :param scroll_value: number of scroll units 滾動次數 + 方向由 scroll_direction 決定,因此這裡只取絕對值。 + Direction comes from scroll_direction, so only the magnitude is used + here: range() on a negative value is empty, which silently scrolled + nothing at all. :param scroll_direction: scroll direction 滾動方向 4 = up 上 5 = down 下 6 = left 左 7 = right 右 """ - for _ in range(scroll_value): + for _ in range(abs(int(scroll_value))): click_mouse(scroll_direction) diff --git a/je_auto_control/linux_with_x11/screen/x11_linux_screen.py b/je_auto_control/linux_with_x11/screen/x11_linux_screen.py index 0d4560e0..d509fbdf 100644 --- a/je_auto_control/linux_with_x11/screen/x11_linux_screen.py +++ b/je_auto_control/linux_with_x11/screen/x11_linux_screen.py @@ -13,6 +13,22 @@ from je_auto_control.linux_with_x11.core.utils.x11_linux_display import display +def _decode_pixel(data: bytes) -> Tuple[int, int, int]: + """Decode a little-endian TrueColor pixel buffer into ``(R, G, B)``. + + ``root.get_image`` returns the pixel in the server's native byte + order; on the dominant little-endian TrueColor visual the bytes are + laid out blue, green, red(, unused), so the first three must be + reversed to yield true RGB — matching the windows / osx backends, + which all return ``(R, G, B)``. + + :param data: raw ZPixmap bytes (at least three: B, G, R) + :return: (R, G, B) + """ + blue, green, red = data[0], data[1], data[2] + return red, green, blue + + def size() -> Tuple[int, int]: """ Get screen size @@ -23,11 +39,15 @@ def size() -> Tuple[int, int]: return display.screen().width_in_pixels, display.screen().height_in_pixels -def get_pixel_rgb(x: int, y: int) -> Tuple[int, int, int]: +def get_pixel(x: int, y: int) -> Tuple[int, int, int]: """ Get RGB value of pixel at given coordinates 取得指定座標的像素 RGB 值 + 名稱需與 wrapper 呼叫的 ``screen.get_pixel`` 一致。 + Named to match the backend contract the wrapper calls + (``screen.get_pixel``), as the windows / osx backends do. + :param x: X coordinate X 座標 :param y: Y coordinate Y 座標 :return: (R, G, B) 三原色值 @@ -38,6 +58,5 @@ def get_pixel_rgb(x: int, y: int) -> Tuple[int, int, int]: # 取得影像資料 Get image data raw = root.get_image(x, y, 1, 1, X.ZPixmap, 0xffffffff) - # raw.data 是 bytes,需要轉換成 RGB - pixel = tuple(raw.data[:3]) # (R, G, B) - return pixel \ No newline at end of file + # raw.data 是 little-endian BGR(X) 位元組,需轉成 RGB + return _decode_pixel(raw.data) \ No newline at end of file diff --git a/je_auto_control/osx/mouse/osx_mouse.py b/je_auto_control/osx/mouse/osx_mouse.py index a7aaf28e..a2ce61a4 100644 --- a/je_auto_control/osx/mouse/osx_mouse.py +++ b/je_auto_control/osx/mouse/osx_mouse.py @@ -24,10 +24,26 @@ def position() -> Tuple[int, int]: Get current mouse position 取得目前滑鼠座標位置 - :return: (x, y) 滑鼠座標 + NSEvent.mouseLocation() 的原點在左下角,但本模組送出的 CGEvent 事件 + 以左上角為原點,因此必須翻轉 y。未翻轉時,未指定座標的點擊會落在 + 垂直鏡像的位置(只有游標剛好在畫面正中央時才正確)。 + NSEvent.mouseLocation() has a bottom-left origin, while the CGEvents this + module posts use a top-left origin — as do the windows and x11 backends. + Without flipping y, a coordinate read here and fed back into press/click + (which is exactly what mouse_preprocess does when x/y are omitted) lands + at the vertically mirrored point. + + :return: (x, y) 滑鼠座標,原點為左上角 top-left origin """ loc = Quartz.NSEvent.mouseLocation() - return int(loc.x), int(loc.y) + # 用點(point)為單位的顯示高度翻轉 y。CGDisplayPixelsHigh 回傳的是像素 + # 高度,在 Retina/HiDPI 螢幕上是點高度的 2 倍,會讓翻轉後的 y 落在錯誤位置。 + # mouseLocation() 與這裡送出的 CGEvent 都以點為單位。 + # Flip y using the point-based display height. CGDisplayPixelsHigh returns + # pixels (2x the point height on Retina/HiDPI), which would offset the + # flipped y; mouseLocation() and the posted CGEvents are both in points. + height = Quartz.CGDisplayBounds(Quartz.CGMainDisplayID()).size.height + return int(loc.x), int(height - loc.y) def mouse_event(event: int, x: int, y: int, mouse_button: int) -> None: diff --git a/je_auto_control/osx/screen/osx_screen.py b/je_auto_control/osx/screen/osx_screen.py index bb966cef..a687388c 100644 --- a/je_auto_control/osx/screen/osx_screen.py +++ b/je_auto_control/osx/screen/osx_screen.py @@ -14,6 +14,26 @@ import Quartz +class CGPoint(ctypes.Structure): + """CoreGraphics CGPoint (two doubles).""" + _fields_ = [("x", ctypes.c_double), ("y", ctypes.c_double)] + + +class CGSize(ctypes.Structure): + """CoreGraphics CGSize (two doubles).""" + _fields_ = [("width", ctypes.c_double), ("height", ctypes.c_double)] + + +class CGRect(ctypes.Structure): + """CoreGraphics CGRect. + + Must be a real Structure, not ``c_double * 4``: ctypes passes an array + argument as a *pointer*, whereas CGWindowListCreateImage takes its CGRect + **by value**. The array form made the capture rect garbage. + """ + _fields_ = [("origin", CGPoint), ("size", CGSize)] + + def size() -> Tuple[int, int]: """ Get screen size @@ -27,24 +47,27 @@ def size() -> Tuple[int, int]: ) -def get_pixel(x: int, y: int) -> Tuple[int, int, int, int]: +def get_pixel(x: int, y: int) -> Tuple[int, int, int]: """ - Get RGBA value of pixel at given coordinates - 取得指定座標的像素 RGBA 值 + Get RGB value of pixel at given coordinates + 取得指定座標的像素 RGB 值 + + 回傳 RGB 以與 windows / x11 / wayland 後端一致:先前回傳 RGBA, + 使得 ``r, g, b = get_pixel(...)`` 只在 macOS 上失敗。 + Returns RGB to match the windows / x11 / wayland backends. This previously + returned RGBA, so ``r, g, b = get_pixel(...)`` unpacked fine everywhere + except macOS. :param x: X coordinate X 座標 :param y: Y coordinate Y 座標 - :return: (R, G, B, A) 四原色值 + :return: (R, G, B) 三原色值 """ # 載入 CoreGraphics 與 CoreFoundation 函式庫 cg = ctypes.CDLL("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics") cf = ctypes.CDLL("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation") - # 定義 CGRect 結構 (x, y, width, height) - cg_rect_t = ctypes.c_double * 4 - # 設定函式簽名 Function signatures - cg.CGWindowListCreateImage.argtypes = [cg_rect_t, c_uint32, c_uint32, c_uint32] + cg.CGWindowListCreateImage.argtypes = [CGRect, c_uint32, c_uint32, c_uint32] cg.CGWindowListCreateImage.restype = c_void_p cg.CGImageGetDataProvider.argtypes = [c_void_p] @@ -69,7 +92,7 @@ def get_pixel(x: int, y: int) -> Tuple[int, int, int, int]: window_image_default = 0 # 建立擷取範圍 Create capture rect - rect = cg_rect_t(x, y, 1.0, 1.0) + rect = CGRect(CGPoint(float(x), float(y)), CGSize(1.0, 1.0)) # 擷取螢幕影像 Capture screen image img = cg.CGWindowListCreateImage( @@ -83,10 +106,14 @@ def get_pixel(x: int, y: int) -> Tuple[int, int, int, int]: "Unable to capture screen image. 請確認已授予螢幕錄製權限" ) - provider = None cfdata = None try: # 取得影像資料供應器 Get data provider + # CGImageGetDataProvider 是 "Get" 函式,呼叫端不擁有這個參考, + # 因此絕對不可以 CFRelease,否則會過度釋放。 + # Per the CoreFoundation Get Rule, a "Get" function does not transfer + # ownership: releasing `provider` here underflowed its refcount, and + # releasing `img` below (which owns the provider) then freed it again. provider = cg.CGImageGetDataProvider(img) # 複製影像資料 Copy image data @@ -100,14 +127,17 @@ def get_pixel(x: int, y: int) -> Tuple[int, int, int, int]: # 取得 byte pointer Get byte pointer buf = cf.CFDataGetBytePtr(cfdata) - # 預設像素格式為 BGRA Default pixel format is BGRA - b, g, r, a = buf[0], buf[1], buf[2], buf[3] + # 預設像素格式為 BGRA;捨棄 alpha 以符合其他後端的 RGB 回傳。 + # Pixel format is BGRA; drop alpha so the return matches the other + # backends' RGB. + b, g, r = buf[0], buf[1], buf[2] - return r, g, b, a + return r, g, b finally: - # 釋放 CoreFoundation 物件 Release CF objects + # 只釋放自己擁有的物件:cfdata 來自 "Copy"、img 來自 "Create"。 + # provider 來自 "Get",不屬於我們,不能釋放。 + # Release only what we own: cfdata came from a Copy function and img + # from a Create function. provider came from a Get function. if cfdata: cf.CFRelease(cfdata) - if provider: - cf.CFRelease(provider) cf.CFRelease(img) \ No newline at end of file diff --git a/je_auto_control/utils/acme_v2/client.py b/je_auto_control/utils/acme_v2/client.py index 2cc96469..3809fcdf 100644 --- a/je_auto_control/utils/acme_v2/client.py +++ b/je_auto_control/utils/acme_v2/client.py @@ -20,6 +20,7 @@ _USER_AGENT = "autocontrol-acme/1.0" _JOSE_CONTENT_TYPE = "application/jose+json" +_BAD_NONCE_ERROR = "urn:ietf:params:acme:error:badNonce" class AcmeError(RuntimeError): @@ -266,6 +267,22 @@ def _signed_post(self, url: str, *, use_jwk: bool = False, accept: Optional[str] = None, ) -> tuple: + status, parsed, headers = self._post_once( + url, payload, use_jwk=use_jwk, accept=accept, + ) + if _is_bad_nonce(status, parsed): + # RFC 8555 §6.5: a badNonce rejection is retryable. The error + # response carried a fresh Replay-Nonce (already cached by + # _post_once), so retry exactly once with it. + status, parsed, headers = self._post_once( + url, payload, use_jwk=use_jwk, accept=accept, + ) + return status, parsed, headers + + def _post_once(self, url: str, + payload: Optional[Mapping[str, Any]], + *, use_jwk: bool, accept: Optional[str]) -> tuple: + """Sign and POST once; cache the server's next Replay-Nonce.""" nonce = self._nonce or self._fresh_nonce() self._nonce = None try: @@ -327,6 +344,12 @@ def _http(self, method: str, url: str, *, return status, body_value, resp_headers +def _is_bad_nonce(status: int, parsed: Any) -> bool: + """Whether an ACME response is a retryable badNonce error (RFC 8555 §6.5).""" + return (status >= 400 and isinstance(parsed, Mapping) + and parsed.get("type") == _BAD_NONCE_ERROR) + + def _build_authorization(url: str, body: Mapping[str, Any] ) -> AcmeAuthorization: challenges = [ diff --git a/je_auto_control/utils/admin/admin_client.py b/je_auto_control/utils/admin/admin_client.py index c3fa3973..493c3fc5 100644 --- a/je_auto_control/utils/admin/admin_client.py +++ b/je_auto_control/utils/admin/admin_client.py @@ -11,6 +11,7 @@ import json import os +import tempfile import threading import time import urllib.request @@ -234,27 +235,71 @@ def _load(self) -> None: autocontrol_logger.warning("admin: load %s failed: %r", self._path, error) return + # 一個損毀的檔案必須退化成空簿,而不是讓 __init__ 崩潰。原本只有 + # json.loads 在 try 內:非物件的頂層 JSON(如 [] / null)會讓 + # payload.get 拋 AttributeError,而多/缺鍵的 entry 會讓 + # AdminHost(**entry) 拋 TypeError,兩者都逸出建構子;而 + # default_admin_console() 只在成功時快取,所以會每次重拋。 + # A corrupt file must degrade to an empty book, not crash __init__. + # Only json.loads was inside the try: a non-object top-level JSON + # (e.g. [] or null) makes payload.get raise AttributeError, and an + # entry with extra/missing keys makes AdminHost(**entry) raise + # TypeError — both escaped the constructor, and default_admin_console() + # caches only on success, so it re-raised on every later call. + if not isinstance(payload, dict): + autocontrol_logger.warning( + "admin: %s is not a JSON object; ignoring", self._path) + return + hosts: Dict[str, AdminHost] = {} + for entry in payload.get("hosts", []): + if not (isinstance(entry, dict) and entry.get("label")): + continue + try: + hosts[entry["label"]] = AdminHost(**entry) + except TypeError as error: + # Skip a malformed entry (extra/missing field) but keep the + # rest of the book usable. + autocontrol_logger.warning( + "admin: skipping malformed host %r in %s: %r", + entry.get("label"), self._path, error) with self._lock: - self._hosts = { - entry["label"]: AdminHost(**entry) - for entry in payload.get("hosts", []) - if isinstance(entry, dict) and entry.get("label") - } + self._hosts = hosts def _save(self) -> None: + # Snapshot and write under the same lock. Splitting them let a stale + # snapshot from one add_host overwrite a newer write from a concurrent + # add_host, silently losing a host. The write itself is atomic (temp + + # os.replace) so a crash mid-write can never truncate the token file. with self._lock: payload = {"hosts": [asdict(h) for h in self._hosts.values()]} + try: + self._write_atomic(payload) + except OSError as error: + autocontrol_logger.warning("admin: save %s failed: %r", + self._path, error) + + def _write_atomic(self, payload: Dict[str, Any]) -> None: + """Write ``payload`` as JSON to ``self._path`` atomically and 0o600.""" + self._path.parent.mkdir(parents=True, exist_ok=True) + data = json.dumps(payload, indent=2, ensure_ascii=False) + # mkstemp creates the file 0o600 on POSIX, so the tokens are never + # briefly world-readable in the gap before os.replace. + fd, tmp_name = tempfile.mkstemp( + prefix=".admin_hosts_", suffix=".tmp", + dir=str(self._path.parent), + ) try: - self._path.parent.mkdir(parents=True, exist_ok=True) - self._path.write_text( - json.dumps(payload, indent=2, ensure_ascii=False), - encoding="utf-8", - ) + with os.fdopen(fd, "w", encoding="utf-8") as handle: + handle.write(data) + os.replace(tmp_name, self._path) if os.name == "posix": os.chmod(self._path, 0o600) - except OSError as error: - autocontrol_logger.warning("admin: save %s failed: %r", - self._path, error) + except OSError: + try: + os.unlink(tmp_name) + except OSError: + pass + raise _default_console: Optional[AdminConsoleClient] = None diff --git a/je_auto_control/utils/agent/agent_loop.py b/je_auto_control/utils/agent/agent_loop.py index 6fe7ebb1..a96935e1 100644 --- a/je_auto_control/utils/agent/agent_loop.py +++ b/je_auto_control/utils/agent/agent_loop.py @@ -5,6 +5,8 @@ from dataclasses import dataclass, field from typing import Any, Callable, Dict, List, Optional, Sequence +from je_auto_control.utils.exception.exceptions import AutoControlException + @dataclass class AgentBudget: @@ -158,7 +160,12 @@ def _dispatch_tool(self, index: int, tool: str, ): try: step.result = self._tool_runner(tool, args) - except (ValueError, RuntimeError, OSError) as error: + # A failing AC_* tool raises AutoControl*Exception (all subclass + # AutoControlException), and a hallucinated kwarg raises TypeError. + # Record the error per-step so the loop keeps going instead of + # crashing the whole run. + except (AutoControlException, TypeError, + ValueError, RuntimeError, OSError) as error: step.error = f"{type(error).__name__}: {error}" return step diff --git a/je_auto_control/utils/agent/backends/anthropic.py b/je_auto_control/utils/agent/backends/anthropic.py index a5590823..670eabdd 100644 --- a/je_auto_control/utils/agent/backends/anthropic.py +++ b/je_auto_control/utils/agent/backends/anthropic.py @@ -71,6 +71,21 @@ def decide_next_action(self, goal: str, tools=self._tools, messages=self._conversation, max_tokens=self._max_tokens, + # 這個迴圈一次只執行一個工具:_handle_response 只回傳第一個 + # tool_use,_ingest_history 也只附上一筆 tool_result。平行 + # 工具呼叫預設是開啟的,一旦模型回傳兩個 tool_use,下一次 + # 請求就會因為有 tool_use 沒有對應的 tool_result 而 400。 + # This loop executes exactly one tool per step: _handle_response + # returns only the first tool_use block and _ingest_history + # appends exactly one tool_result. Parallel tool use is on by + # default, and every tool_use must be answered — so a response + # with two tool_use blocks made the *next* create() 400 with an + # unanswered tool_use id, killing the run. Disabling it matches + # the API to the loop this backend actually implements. + tool_choice={ + "type": "auto", + "disable_parallel_tool_use": True, + }, ) except Exception as exc: # noqa: BLE001 rewrap to a clear backend error raise AgentBackendError( @@ -95,7 +110,11 @@ def _handle_response(self, response: Any) -> Dict[str, Any]: "input": _attr(block, "input") or {}, "_tool_use_id": _attr(block, "id"), } - # No tool_use — interpret the text as a final answer + stop. + # No tool_use — interpret the text as a final answer + stop, unless + # the turn was cut short (default max_tokens can be hit mid-plan, or + # the model may refuse). Surfacing a truncated reply as a successful + # final answer would silently end the run with a half-formed message. + _raise_if_truncated(response) text_parts: List[str] = [] for block in content: block_type = ( @@ -159,6 +178,18 @@ def _attr(block: Any, name: str) -> Any: return getattr(block, name, None) +_TRUNCATION_STOP_REASONS = frozenset({"max_tokens", "refusal"}) + + +def _raise_if_truncated(response: Any) -> None: + """Reject an incomplete turn instead of treating it as a final answer.""" + stop_reason = _attr(response, "stop_reason") + if stop_reason in _TRUNCATION_STOP_REASONS: + raise AgentBackendError( + f"anthropic response was incomplete (stop_reason={stop_reason!r})", + ) + + def _last_tool_use_id(conversation: Sequence[Dict[str, Any]]) -> Optional[str]: """Walk the conversation backwards for the most recent tool_use id.""" for msg in reversed(conversation): diff --git a/je_auto_control/utils/agent/backends/anthropic_computer_use.py b/je_auto_control/utils/agent/backends/anthropic_computer_use.py index f57599dc..11691726 100644 --- a/je_auto_control/utils/agent/backends/anthropic_computer_use.py +++ b/je_auto_control/utils/agent/backends/anthropic_computer_use.py @@ -140,6 +140,15 @@ def decide_next_action(self, tools=[self._tool_schema], messages=self._conversation, max_tokens=self._max_tokens, + # This loop answers exactly one tool_use per turn, so parallel + # tool use must stay off: a response with two computer tool_use + # blocks would leave the second unanswered and the next + # create() would 400 on the dangling tool_use id, aborting the + # run. Mirror AnthropicAgentBackend's fix. + tool_choice={ + "type": "auto", + "disable_parallel_tool_use": True, + }, ) except Exception as exc: # noqa: BLE001 rewrap to backend error raise AgentBackendError( @@ -161,7 +170,10 @@ def _handle_response(self, response: Any) -> Dict[str, Any]: payload = _attr(block, "input") or {} self._pending_tool_use_id = _attr(block, "id") return _decision_from_computer_action(payload) - # No tool_use → final answer + stop. + # No tool_use → final answer + stop, unless the turn was cut short + # (default max_tokens can be hit mid-plan, or the model may refuse): + # a truncated reply must not be reported as a successful final answer. + _raise_if_truncated(response) text_parts: List[str] = [ _attr(b, "text") or "" for b in content if _block_type(b) == "text" @@ -221,10 +233,10 @@ def _action_type(payload): def _action_wait(payload): - return { - "tool": "AC_sleep", - "input": {"seconds": float(payload.get("duration") or 1.0)}, - } + duration = payload.get("duration") + # An explicit 0 is a valid "don't wait" — only fall back when unset. + seconds = float(duration) if duration is not None else 1.0 + return {"tool": "AC_sleep", "input": {"seconds": seconds}} _CLICK_ACTIONS = frozenset({ @@ -277,7 +289,9 @@ def _drag_decision(payload: Dict[str, Any]) -> Dict[str, Any]: def _scroll_decision(payload: Dict[str, Any]) -> Dict[str, Any]: direction = str(payload.get("scroll_direction") or "down").lower() - amount = int(payload.get("scroll_amount") or 3) + raw_amount = payload.get("scroll_amount") + # An explicit 0 means "no scroll" — only default when the key is absent. + amount = int(raw_amount) if raw_amount is not None else 3 delta = amount if direction == "up" else -amount return {"tool": "AC_mouse_scroll", "input": {"scroll_value": delta}} @@ -303,6 +317,23 @@ def _hold_key_decision(payload: Dict[str, Any]) -> Dict[str, Any]: } +def _mouse_button_decision(tool: str, payload: Dict[str, Any]) -> Dict[str, Any]: + """Map a bare press/release verb to an ``AC_press/release_mouse`` call.""" + inputs: Dict[str, Any] = {"mouse_keycode": "mouse_left"} + coordinate = payload.get("coordinate") + if coordinate is not None: + inputs["x"], inputs["y"] = _xy(coordinate) + return {"tool": tool, "input": inputs} + + +def _mouse_down_decision(payload: Dict[str, Any]) -> Dict[str, Any]: + return _mouse_button_decision("AC_press_mouse", payload) + + +def _mouse_up_decision(payload: Dict[str, Any]) -> Dict[str, Any]: + return _mouse_button_decision("AC_release_mouse", payload) + + # Dispatch table — populated after every handler is defined so the # table reads its targets at module load time. _ACTION_HANDLERS: Dict[str, Callable[[Dict[str, Any]], Dict[str, Any]]] = { @@ -315,9 +346,27 @@ def _hold_key_decision(payload: Dict[str, Any]) -> Dict[str, Any]: "scroll": _scroll_decision, "key": _key_decision, "hold_key": _hold_key_decision, + # computer_20250124 press/release verbs — without these an otherwise + # valid action raised AgentBackendError outside the create() try and + # aborted the run. + "left_mouse_down": _mouse_down_decision, + "left_mouse_up": _mouse_up_decision, } +_TRUNCATION_STOP_REASONS = frozenset({"max_tokens", "refusal"}) + + +def _raise_if_truncated(response: Any) -> None: + """Reject an incomplete turn instead of treating it as a final answer.""" + stop_reason = _attr(response, "stop_reason") + if stop_reason in _TRUNCATION_STOP_REASONS: + raise AgentBackendError( + "anthropic computer-use response was incomplete " + f"(stop_reason={stop_reason!r})", + ) + + # --- helpers -------------------------------------------------------- def _xy(coordinate: Any) -> Tuple[int, int]: diff --git a/je_auto_control/utils/agent/backends/openai.py b/je_auto_control/utils/agent/backends/openai.py index a9c80755..0dcad0b4 100644 --- a/je_auto_control/utils/agent/backends/openai.py +++ b/je_auto_control/utils/agent/backends/openai.py @@ -68,6 +68,11 @@ def decide_next_action(self, goal: str, messages=self._messages, tools=self._tools, tool_choice="auto", + # This loop executes and answers only tool_calls[0] each turn, + # yet the whole assistant message (with every tool_call) is + # replayed. Parallel calls would leave the rest unanswered and + # the next request would 400, ending the run. + parallel_tool_calls=False, ) except Exception as exc: # noqa: BLE001 rewrap to a clear backend error raise AgentBackendError( @@ -114,11 +119,22 @@ def _handle_response(self, response: Any) -> Dict[str, Any]: args = {} self._pending_tool_call_id = call.id return {"tool": fn.name, "input": args} - # No tool call → final answer. + # No tool call → final answer, unless the turn was truncated at the + # token cap: returning a length-cut reply as the final answer would + # silently end the run mid-plan. + _raise_if_truncated(choice) text = getattr(message, "content", None) or "" return {"stop": True, "message": text.strip() if isinstance(text, str) else ""} +def _raise_if_truncated(choice: Any) -> None: + """Reject a length-truncated turn instead of returning it as final.""" + if getattr(choice, "finish_reason", None) == "length": + raise AgentBackendError( + "openai response truncated (finish_reason='length')", + ) + + def _build_user_content(screenshot: Optional[bytes]) -> List[Dict[str, Any]]: blocks: List[Dict[str, Any]] = [] encoded = encode_screenshot_b64(screenshot) diff --git a/je_auto_control/utils/anchor_locator/locator.py b/je_auto_control/utils/anchor_locator/locator.py index feb156f8..79afdf32 100644 --- a/je_auto_control/utils/anchor_locator/locator.py +++ b/je_auto_control/utils/anchor_locator/locator.py @@ -17,6 +17,16 @@ from dataclasses import asdict, dataclass from typing import Any, Dict, List, Optional, Tuple +from je_auto_control.utils.exception.exceptions import AutoControlException + + +# Errors a backend adapter treats as "simply not on screen" and turns into an +# empty result. ``AutoControlException`` covers ``ImageNotFoundException`` (from +# locate_image_center / locate_all_image) and ``AutoControlActionException`` +# (from locate_text_center) — without it a plain not-found would crash the +# whole anchor locate instead of returning found=False. +_LOCATE_ERRORS = (OSError, RuntimeError, ValueError, AutoControlException) + # Spatial relations the wrapper understands. REL_ABOVE = "above" @@ -262,6 +272,10 @@ def _ranked(anchor_point: Tuple[int, int], if not _matches_relation(anchor_point, bbox, relation): continue distance = _euclid(anchor_point, bbox.center) + # max_distance is a proximity radius for REL_NEAR only; directional + # relations ("the 3rd row below the header") intentionally return + # matches at any distance, sorted by distance, so ordinal selection and + # long-table row picking keep working past the default radius. if relation == REL_NEAR and distance > max_distance: continue matches.append((bbox.center, distance)) @@ -305,7 +319,7 @@ def _image_center(locator: Locator) -> Optional[Tuple[int, int]]: locator.template_path, detect_threshold=locator.detect_threshold, ) - except (OSError, RuntimeError, ValueError): + except _LOCATE_ERRORS: return None @@ -318,7 +332,7 @@ def _image_candidates(locator: Locator) -> List[_Bbox]: locator.template_path, detect_threshold=locator.detect_threshold, ) - except (OSError, RuntimeError, ValueError): + except _LOCATE_ERRORS: return [] return [_Bbox(*map(int, row[:4])) for row in rows if isinstance(row, (list, tuple)) and len(row) >= 4] @@ -332,7 +346,7 @@ def _ocr_center(locator: Locator) -> Optional[Tuple[int, int]]: region=list(locator.region) if locator.region else None, min_confidence=locator.min_confidence, ) - except (OSError, RuntimeError, ValueError): + except _LOCATE_ERRORS: return None @@ -344,7 +358,7 @@ def _ocr_candidates(locator: Locator) -> List[_Bbox]: region=list(locator.region) if locator.region else None, min_confidence=locator.min_confidence, ) - except (OSError, RuntimeError, ValueError): + except _LOCATE_ERRORS: return [] return [_Bbox(x1=m.x, y1=m.y, x2=m.x + m.width, y2=m.y + m.height) for m in matches] @@ -356,7 +370,7 @@ def _vlm_point(locator: Locator) -> Optional[Tuple[int, int]]: return locate_by_description( locator.description, model=locator.model, ) - except (OSError, RuntimeError, ValueError): + except _LOCATE_ERRORS: return None @@ -368,7 +382,7 @@ def _a11y_point(locator: Locator) -> Optional[Tuple[int, int]]: element = find_accessibility_element( name=locator.name, role=locator.role, app_name=locator.app_name, ) - except (OSError, RuntimeError, ValueError): + except _LOCATE_ERRORS: return None if element is None: return None diff --git a/je_auto_control/utils/assertion/combinators.py b/je_auto_control/utils/assertion/combinators.py index eb3c7645..05460a40 100644 --- a/je_auto_control/utils/assertion/combinators.py +++ b/je_auto_control/utils/assertion/combinators.py @@ -185,7 +185,14 @@ def assert_eventually(spec: Mapping[str, Any], """ if timeout < 0: raise AutoControlAssertionException("timeout must be non-negative") - poll = max(float(interval), 0.0) + # timeout 有驗證,interval 卻被 max(..., 0.0) 靜默吞掉:負值或 0 + # 會變成 sleep(0),迴圈空轉燒滿一顆核心(實測 0.4 秒內 244k 次)。 + # timeout was validated but interval was silently clamped by + # max(..., 0.0), so 0 or a negative turned the loop into a busy-spin — + # measured at 244k attempts in 0.4s, each a real assertion evaluation. + if interval <= 0: + raise AutoControlAssertionException("interval must be positive") + poll = float(interval) deadline = time.monotonic() + float(timeout) attempts = 0 last: Optional[AssertionResult] = None diff --git a/je_auto_control/utils/callback/callback_function_executor.py b/je_auto_control/utils/callback/callback_function_executor.py index c5be0d90..e6703dae 100644 --- a/je_auto_control/utils/callback/callback_function_executor.py +++ b/je_auto_control/utils/callback/callback_function_executor.py @@ -3,7 +3,7 @@ # utils cv2_utils from je_auto_control.utils.cv2_utils.screenshot import pil_screenshot from je_auto_control.utils.exception.exception_tags import get_bad_trigger_method_error_message, get_bad_trigger_function_error_message -from je_auto_control.utils.exception.exceptions import CallbackExecutorException +from je_auto_control.utils.exception.exceptions import AutoControlException, CallbackExecutorException from je_auto_control.utils.logging.logging_instance import autocontrol_logger # executor from je_auto_control.utils.executor.action_executor import execute_action, execute_files @@ -151,35 +151,49 @@ def callback_function( :param callback_function_param: 回呼函式的參數 (dict 或 list) :param callback_param_method: 回呼函式參數傳遞方式 ("kwargs" 或 "args") :param kwargs: 傳給 trigger_function 的參數 - :return: trigger_function 的回傳值 + :return: trigger_function 的回傳值 (若 trigger 失敗則為 None) """ try: if trigger_function_name not in self.event_dict: raise CallbackExecutorException(get_bad_trigger_function_error_message) - - # 執行 trigger function - execute_return_value = self.event_dict[trigger_function_name](**kwargs) - - # 呼叫 callback function - if callback_function_param is not None: - if callback_param_method not in ["kwargs", "args"]: - raise CallbackExecutorException(get_bad_trigger_method_error_message) - if callback_param_method == "kwargs": - callback_function(**callback_function_param) - else: - callback_function(*callback_function_param) - else: - callback_function() - - return execute_return_value - + if (callback_function_param is not None + and callback_param_method not in ("kwargs", "args")): + raise CallbackExecutorException(get_bad_trigger_method_error_message) except CallbackExecutorException as error: autocontrol_logger.error("callback_function config error: %r", error) return None - except (TypeError, ValueError, RuntimeError) as error: - autocontrol_logger.error("callback_function execution failed: %r", error) + + # Run the trigger in its own scope: a trigger failure (including the + # framework family — mouse / image / assertion errors) returns None and + # is never confused with a later callback failure. + try: + execute_return_value = self.event_dict[trigger_function_name](**kwargs) + except (AutoControlException, TypeError, ValueError, RuntimeError) as error: + autocontrol_logger.error("callback_function trigger failed: %r", error) return None + # Run the callback in its own scope: a callback failure must not discard + # the already-computed trigger result. + try: + self._invoke_callback( + callback_function, callback_function_param, callback_param_method) + except (AutoControlException, TypeError, ValueError, RuntimeError) as error: + autocontrol_logger.error("callback_function callback failed: %r", error) + + return execute_return_value + + @staticmethod + def _invoke_callback(callback_function: Callable, + callback_function_param: dict | None, + callback_param_method: str) -> None: + """Invoke ``callback_function`` with the configured parameter passing.""" + if callback_function_param is None: + callback_function() + elif callback_param_method == "kwargs": + callback_function(**callback_function_param) + else: + callback_function(*callback_function_param) + # === 全域 Callback Executor 實例 Global Instance === callback_executor = CallbackFunctionExecutor() diff --git a/je_auto_control/utils/chatops/handlers.py b/je_auto_control/utils/chatops/handlers.py index 8a14ea62..2615a456 100644 --- a/je_auto_control/utils/chatops/handlers.py +++ b/je_auto_control/utils/chatops/handlers.py @@ -88,12 +88,24 @@ def cmd_status(_argv: List[str], return CommandResult(text="no recent runs.") lines = [ f" [{row.status}] {row.source_type}:{row.source_id} " - f"@ {row.started_at} ({row.duration_seconds:.1f}s)" + f"@ {row.started_at} ({_format_duration(row.duration_seconds)})" for row in rows ] return CommandResult(text="recent runs:\n" + "\n".join(lines)) +def _format_duration(duration_seconds: Any) -> str: + """Render a run duration, tolerating an in-flight/crashed run's ``None``. + + ``duration_seconds`` is ``None`` while a run is still going or when the + process died before ``finish_run`` — formatting that with ``:.1f`` raised + an uncaught ``TypeError`` that took the whole bot poll loop down. + """ + if duration_seconds is None: + return "in progress" + return f"{duration_seconds:.1f}s" + + def cmd_screenshot(argv: List[str], _context: Dict[str, Any]) -> CommandResult: """``/screenshot [path]`` — capture the screen and return the path.""" diff --git a/je_auto_control/utils/chatops/router.py b/je_auto_control/utils/chatops/router.py index 5d4e7ba8..0521bdc7 100644 --- a/je_auto_control/utils/chatops/router.py +++ b/je_auto_control/utils/chatops/router.py @@ -10,10 +10,13 @@ from __future__ import annotations import shlex +import sqlite3 import threading from dataclasses import asdict, dataclass, field from typing import Any, Callable, Dict, List, Optional +from je_auto_control.utils.exception.exceptions import AutoControlException + # Built-in commands always available even when the operator only # registers their own scripts. Keep this set small — listing scripts @@ -133,11 +136,16 @@ def _dispatch_argv(self, argv: List[str], f"{spec.required_role!r}; you do not have it."), succeeded=False, ) + # The router is the containment boundary for handler failures: a bad + # script (AutoControlException) or a run-history read error + # (sqlite3.Error) must come back as a chat reply, not escape and kill + # the transport's poll loop. try: return spec.handler(rest, context) except ChatOpsError as error: return CommandResult(text=f"{name}: {error}", succeeded=False) - except (RuntimeError, OSError, ValueError) as error: + except (RuntimeError, OSError, ValueError, TypeError, LookupError, + AttributeError, AutoControlException, sqlite3.Error) as error: return CommandResult( text=f"{name} failed: {type(error).__name__}: {error}", succeeded=False, diff --git a/je_auto_control/utils/chatops/slack_bot.py b/je_auto_control/utils/chatops/slack_bot.py index 9ace4bb2..52ddedca 100644 --- a/je_auto_control/utils/chatops/slack_bot.py +++ b/je_auto_control/utils/chatops/slack_bot.py @@ -27,6 +27,7 @@ from typing import Any, Dict, Optional from je_auto_control.utils.chatops.router import CommandResult, CommandRouter +from je_auto_control.utils.exception.exceptions import AutoControlException from je_auto_control.utils.logging.logging_instance import autocontrol_logger @@ -72,18 +73,21 @@ def poll_once(self) -> int: # Slack returns newest-first; reverse so we route in chronological order. ordered = list(reversed(messages)) dispatched = 0 - latest_ts = self.last_seen_ts or "0" for msg in ordered: ts = str(msg.get("ts") or "") if not ts: continue - latest_ts = max(latest_ts, ts) + # Commit progress *before* running the command. A command's side + # effects (running a script, taking a screenshot) happen exactly + # once inside _route_one; if the handler or the reply post then + # fails, the next poll must not re-execute it. Advancing last_seen_ts + # here (Slack's `oldest` is exclusive) guarantees at-most-once. + self.last_seen_ts = max(self.last_seen_ts or "0", ts) if self._is_self(msg): continue text = str(msg.get("text") or "") if self._route_one(text, msg) is not None: dispatched += 1 - self.last_seen_ts = latest_ts return dispatched def run_forever(self, *, max_iterations: Optional[int] = None) -> None: @@ -110,13 +114,16 @@ def stop(self) -> None: def _route_one(self, text: str, message: Dict[str, Any]) -> Optional[CommandResult]: + # The router already converts handler failures into CommandResults; + # this is defence in depth so a framework error surfaces as a chat + # reply rather than escaping poll_once and stopping run_forever. try: result = self.router.dispatch( text, context={"slack_user": message.get("user"), "slack_ts": message.get("ts"), "slack_channel": self.channel_id}, ) - except (RuntimeError, ValueError) as error: + except (RuntimeError, ValueError, AutoControlException) as error: self.post_message(f"router error: {error}") return None if result is None: diff --git a/je_auto_control/utils/codegen/codegen.py b/je_auto_control/utils/codegen/codegen.py index 72a2f6b6..e5986197 100644 --- a/je_auto_control/utils/codegen/codegen.py +++ b/je_auto_control/utils/codegen/codegen.py @@ -72,14 +72,24 @@ def _body(actions: Sequence, style: str) -> str: raise ValueError(f"unknown codegen style: {style!r}") -def _render_pytest(actions: Sequence, name: str, style: str) -> str: - body = textwrap.indent(_body(actions, style), " ") +def _render_pytest(actions: Sequence, name: str, style: str, + failure_bundle: bool = False) -> str: + raw_body = _body(actions, style) + if failure_bundle: + body = (" with ac.failure_bundle_on_error(\n" + f" {(_slug(name) + '-failure.zip')!r},\n" + f" context={{'generated_test': {_slug(name)!r}}}):\n" + + textwrap.indent(raw_body, " ")) + else: + body = textwrap.indent(raw_body, " ") return (f'"""{_HEADER}"""\n' - "import je_auto_control as ac\n\n\n" - f"def test_{_slug(name)}():\n{body}\n") + + ("import je_auto_control.api as ac\n\n\n" if failure_bundle + else "import je_auto_control as ac\n\n\n") + + f"def test_{_slug(name)}():\n{body}\n") -def _render_python(actions: Sequence, name: str, style: str) -> str: +def _render_python(actions: Sequence, name: str, style: str, + _failure_bundle: bool = False) -> str: slug = _slug(name) body = textwrap.indent(_body(actions, style), " ") return (f'"""{_HEADER}"""\n' @@ -89,7 +99,8 @@ def _render_python(actions: Sequence, name: str, style: str) -> str: f" {slug}()\n") -def _render_robot(actions: Sequence, name: str, _style: str) -> str: +def _render_robot(actions: Sequence, name: str, _style: str, + _failure_bundle: bool = False) -> str: payload = json.dumps([list(action) for action in actions], ensure_ascii=False) test_name = name.replace("_", " ").strip().title() or "Recorded Flow" @@ -113,22 +124,27 @@ def _render_robot(actions: Sequence, name: str, _style: str) -> str: def generate_code(actions: Sequence, target: str = "pytest", - name: str = "recorded_flow", style: str = "calls") -> str: + name: str = "recorded_flow", style: str = "calls", + failure_bundle: bool = False) -> str: """Render ``actions`` as source code for ``target`` (pytest/python/robot).""" if not isinstance(actions, list) or not actions: raise ValueError("actions must be a non-empty list") renderer = _RENDERERS.get(target) if renderer is None: raise ValueError(f"unknown codegen target: {target!r}") - return renderer(actions, name, style) + if failure_bundle and target != "pytest": + raise ValueError("failure_bundle is currently supported for pytest only") + return renderer(actions, name, style, failure_bundle) def generate_code_file(source, output_path: str, target: str = "pytest", - name: str = "recorded_flow", style: str = "calls") -> str: + name: str = "recorded_flow", style: str = "calls", + failure_bundle: bool = False) -> str: """Generate code from a list or JSON action-file path; write and return it.""" actions = source if isinstance(source, list) else read_action_json( os.path.realpath(source)) - code = generate_code(actions, target=target, name=name, style=style) + code = generate_code(actions, target=target, name=name, style=style, + failure_bundle=failure_bundle) with open(os.path.realpath(output_path), "w", encoding="utf-8") as handle: handle.write(code) return code diff --git a/je_auto_control/utils/config_redaction/config_redaction.py b/je_auto_control/utils/config_redaction/config_redaction.py index 9222a2c7..6559fbaf 100644 --- a/je_auto_control/utils/config_redaction/config_redaction.py +++ b/je_auto_control/utils/config_redaction/config_redaction.py @@ -44,6 +44,21 @@ def redact_config(obj: Any, *, mask: str = _DEFAULT_MASK) -> Any: def redact_secret_text(text: str, *, mask: str = _DEFAULT_MASK) -> str: """Mask secret-looking tokens within a free-text string (e.g. a log line).""" + # Explicit credential syntax must be masked even when the value is short or + # low-entropy and therefore intentionally below the generic scanner's + # threshold (common in tests, local deployments, and leaked error text). + text = re.sub( + r"(?i)(\bauthorization\s*:\s*bearer\s+)[^\s,;]+", + lambda match: match.group(1) + mask, + text or "", + ) + text = re.sub( + r"(?i)(\b(?:api[_-]?key|access[_-]?token|token|password|passwd|secret)" + r"\s*[=:]\s*)([^\s,;]+)", + lambda match: match.group(1) + mask, + text, + ) + def _replace(match: "re.Match[str]") -> str: token = match.group(0) core = token.strip(_PUNCT) @@ -51,4 +66,4 @@ def _replace(match: "re.Match[str]") -> str: return token.replace(core, mask) return token - return re.sub(r"\S+", _replace, text or "") + return re.sub(r"\S+", _replace, text) diff --git a/je_auto_control/utils/critical_exit/critical_exit.py b/je_auto_control/utils/critical_exit/critical_exit.py index 89e1c925..2f11775c 100644 --- a/je_auto_control/utils/critical_exit/critical_exit.py +++ b/je_auto_control/utils/critical_exit/critical_exit.py @@ -1,10 +1,15 @@ import _thread -from threading import Thread +from threading import Event, Thread +from typing import Union from je_auto_control.utils.logging.logging_instance import autocontrol_logger -from je_auto_control.wrapper.auto_control_keyboard import keyboard_keys_table +from je_auto_control.wrapper.auto_control_keyboard import _resolve_keycode from je_auto_control.wrapper.platform_wrapper import keyboard_check +# 輪詢間隔,避免佔滿一顆 CPU 核心 +# Poll interval; without it the listener busy-spins and pegs a CPU core. +_POLL_INTERVAL_SECONDS: float = 0.02 + class CriticalExit(Thread): """ @@ -20,37 +25,68 @@ def __init__(self, default_daemon: bool = True): Initialize CriticalExit :param default_daemon: 是否設為守護執行緒 (程式結束時自動停止) + :raises AutoControlCantFindKeyException: 平台鍵盤對應表缺少預設的 f7 鍵 """ super().__init__() self.daemon = default_daemon # 預設退出鍵為 F7 Default exit key is F7 - self._exit_check_key: int = keyboard_keys_table.get("f7") + self._exit_check_key: int = _resolve_keycode("f7") + self._stop_event = Event() - def set_critical_key(self, keycode: int | str = None) -> None: + def set_critical_key(self, keycode: Union[int, str] = None) -> None: """ 設定退出鍵 Set critical exit key + 傳入 None 時維持原本的按鍵不變。 + Passing None leaves the current key unchanged. + :param keycode: 可傳入 int (keycode) 或 str (鍵名) + :raises AutoControlCantFindKeyException: 找不到對應的鍵名 + """ + if keycode is None: + return + # 解析失敗必須立刻拋出:靜默存入 None 會讓監聽執行緒在輪詢時死亡, + # 使緊急退出鍵無聲失效。 + # Resolve eagerly: silently storing None would kill the listener + # thread on its first poll, leaving the panic key dead with no signal. + self._exit_check_key = _resolve_keycode(keycode) + + def stop(self) -> None: + """ + 停止監聽器 + Stop the listener loop """ - if isinstance(keycode, int): - self._exit_check_key = keycode - elif isinstance(keycode, str): - self._exit_check_key = keyboard_keys_table.get(keycode) + self._stop_event.set() def run(self) -> None: """ 執行監聽迴圈 Run listener loop - - 持續監聽指定鍵盤按鍵 - - 當按下時觸發中斷主程式 + - 以固定間隔輪詢指定鍵盤按鍵 + - 按下時中斷主程式一次後結束監聽 """ try: - while True: + # wait() 兼作節流與停止訊號:回傳 True 代表已呼叫 stop()。 + # wait() doubles as the throttle and the stop signal: it returns + # True only once stop() has been called. + while not self._stop_event.wait(_POLL_INTERVAL_SECONDS): if keyboard_check.check_key_is_press(self._exit_check_key): - _thread.interrupt_main() # 中斷主程式 Interrupt main thread - except (OSError, RuntimeError) as error: - autocontrol_logger.error("critical exit listener failed: %r", error) + # 只中斷一次。持續中斷會打斷主程式的 KeyboardInterrupt + # 處理與清理程式碼。 + # Interrupt once. Firing every poll while the key is still + # held would interrupt the main thread's own + # KeyboardInterrupt handler and its cleanup code. + _thread.interrupt_main() + return + # 守護執行緒無法將例外往外拋,靜默死亡會讓緊急退出鍵失效, + # 因此刻意攔截所有例外並完整記錄。 + # A daemon listener cannot propagate anything to the caller; dying + # silently is the exact failure this class must avoid, so catch + # broadly and log loudly. + except Exception as error: # noqa: BLE001 # reason: see comment above + autocontrol_logger.error( + "critical exit listener failed: %r", error, exc_info=True) def init_critical_exit(self) -> None: """ diff --git a/je_auto_control/utils/cv2_utils/screen_record.py b/je_auto_control/utils/cv2_utils/screen_record.py index 22cc04d3..e46bd51a 100644 --- a/je_auto_control/utils/cv2_utils/screen_record.py +++ b/je_auto_control/utils/cv2_utils/screen_record.py @@ -33,13 +33,15 @@ def start_new_record( :param frame_per_sec: 每秒幀數 :param resolution: 解析度 (寬, 高) """ - record_thread = ScreenRecordThread(path_and_filename, codec, frame_per_sec, resolution) - - # 如果已有同名錄影器,先停止舊的 - old_record = self.running_recorder.get(recorder_name) + # 先停止並等待舊的同名錄影器,避免兩個 VideoWriter 同時寫入同一檔案。 + # Stop (and wait for) any existing recorder first so we never open a + # second VideoWriter on the same file before the old one releases it. + old_record = self.running_recorder.pop(recorder_name, None) if old_record is not None: old_record.stop() + old_record.join(timeout=5.0) + record_thread = ScreenRecordThread(path_and_filename, codec, frame_per_sec, resolution) record_thread.daemon = True record_thread.start() self.running_recorder[recorder_name] = record_thread @@ -49,9 +51,13 @@ def stop_record(self, recorder_name: str): Stop a specific recorder 停止指定的錄影器 """ - if recorder_name in self.running_recorder: - self.running_recorder[recorder_name].stop() - del self.running_recorder[recorder_name] + record_thread = self.running_recorder.pop(recorder_name, None) + if record_thread is not None: + # Join after stopping so the VideoWriter is released (its run() + # finally) before we return — otherwise the .avi may still be + # unflushed when the caller reads/uploads it. + record_thread.stop() + record_thread.join(timeout=5.0) class ScreenRecordThread(threading.Thread): @@ -65,13 +71,16 @@ def __init__(self, path_and_filename, codec, frame_per_sec, resolution: Tuple[in super().__init__() self.fourcc = cv2.VideoWriter.fourcc(*codec) self.video_writer = cv2.VideoWriter(path_and_filename, self.fourcc, frame_per_sec, resolution) - self.record_flag = False + # 用 Event 而非布林旗標:run() 之前呼叫 stop() 也能被遵守,不會被覆寫。 + # An Event, not a bool flag, so a stop() that lands before run() starts + # is honoured instead of being overwritten by run() (which would leave + # the recorder unstoppable and the VideoWriter never released). + self._stop_event = threading.Event() self.resolution = resolution def run(self) -> None: - self.record_flag = True try: - while self.record_flag: + while not self._stop_event.is_set(): # 擷取螢幕畫面 Capture screen frame image = screenshot() @@ -89,4 +98,4 @@ def stop(self) -> None: Stop recording 停止錄影 """ - self.record_flag = False \ No newline at end of file + self._stop_event.set() \ No newline at end of file diff --git a/je_auto_control/utils/cv2_utils/screenshot.py b/je_auto_control/utils/cv2_utils/screenshot.py index c6158091..0c10ccbd 100644 --- a/je_auto_control/utils/cv2_utils/screenshot.py +++ b/je_auto_control/utils/cv2_utils/screenshot.py @@ -1,7 +1,32 @@ from PIL import ImageGrab, Image from typing import List, Optional -from je_auto_control.utils.logging.logging_instance import autocontrol_logger +from je_auto_control.utils.exception.exceptions import AutoControlScreenException + + +def _validate_region(screen_region: List[int]) -> None: + """Reject a region PIL would turn into an empty image. + + 擷取區域必須是 [左, 上, 右, 下] 且寬高都大於 0。 + A zero-area bbox makes ImageGrab return an empty image, and the caller's + cv2.cvtColor then raises a raw ``cv2.error`` — which subclasses only + ``Exception``, so it escapes the screenshot wrapper's except tuple and + reaches callers that were told to expect AutoControlScreenException. + Reject it here, at the boundary, with a message that says what is wrong. + """ + try: + left, top, right, bottom = (int(value) for value in screen_region) + except (TypeError, ValueError) as error: + raise AutoControlScreenException( + f"screen_region must be 4 ints [left, top, right, bottom]; " + f"got {screen_region!r}" + ) from error + if right <= left or bottom <= top: + raise AutoControlScreenException( + f"screen_region must have positive width and height; got " + f"[{left}, {top}, {right}, {bottom}] " + f"(width={right - left}, height={bottom - top})" + ) def pil_screenshot(file_path: Optional[str] = None, screen_region: Optional[List[int]] = None) -> Image.Image: @@ -17,15 +42,20 @@ def pil_screenshot(file_path: Optional[str] = None, screen_region: Optional[List """ # 擷取螢幕畫面 Capture screen if screen_region is not None: + _validate_region(screen_region) image = ImageGrab.grab(bbox=screen_region) else: image = ImageGrab.grab() - # 如果指定了存檔路徑,則存檔 Save if file_path is provided + # 如果指定了存檔路徑,則存檔 Save if file_path is provided. + # Fail fast: a swallowed save error would leave the caller believing the + # requested screenshot file exists when it does not. if file_path: try: image.save(file_path) except (OSError, ValueError) as error: - autocontrol_logger.error("Failed to save screenshot: %r", error) + raise AutoControlScreenException( + f"Failed to save screenshot to {file_path!r}: {error}" + ) from error return image \ No newline at end of file diff --git a/je_auto_control/utils/dag/runner.py b/je_auto_control/utils/dag/runner.py index e2f6f8a6..08719d9d 100644 --- a/je_auto_control/utils/dag/runner.py +++ b/je_auto_control/utils/dag/runner.py @@ -16,6 +16,7 @@ from je_auto_control.utils.dag.graph import ( DagDefinition, DagDefinitionError, DagNode, parse_definition, ) +from je_auto_control.utils.exception.exceptions import AutoControlException from je_auto_control.utils.logging.logging_instance import autocontrol_logger @@ -118,22 +119,21 @@ def _execute_with_pool(dag: DagDefinition, """Schedule nodes whose deps are all done; cascade skip on failure.""" pending: Set[str] = set(results) inflight: Dict[Future, str] = {} - failed_ancestors: Dict[str, Set[str]] = _ancestor_index(dag) + ancestors: Dict[str, Set[str]] = _ancestor_index(dag) nodes_by_id = dag.by_id() - failed: Set[str] = set() with ThreadPoolExecutor(max_workers=max_parallel) as pool: while pending or inflight: _spawn_ready_nodes( - pending, inflight, results, failed, - failed_ancestors, nodes_by_id, local, remote, pool, + pending, inflight, results, + ancestors, nodes_by_id, local, remote, pool, ) if not inflight: continue - _harvest_one(inflight, results, failed) + _harvest_one(inflight, results) def _spawn_ready_nodes(pending: Set[str], inflight: Dict[Future, str], - results: Dict[str, NodeResult], failed: Set[str], + results: Dict[str, NodeResult], ancestors: Dict[str, Set[str]], nodes_by_id: Dict[str, DagNode], local: NodeRunner, remote: NodeRunner, @@ -143,7 +143,7 @@ def _spawn_ready_nodes(pending: Set[str], inflight: Dict[Future, str], deps_done = {results[d].status for d in node.depends_on} if {STATUS_PENDING, STATUS_RUNNING} & deps_done: continue - if ancestors[nid] & failed: + if _blocked_by_ancestor(ancestors[nid], results): results[nid].status = STATUS_SKIPPED pending.discard(nid) continue @@ -155,27 +155,44 @@ def _spawn_ready_nodes(pending: Set[str], inflight: Dict[Future, str], pending.discard(nid) +def _blocked_by_ancestor(ancestor_ids: Set[str], + results: Dict[str, NodeResult]) -> bool: + """True if any ancestor already failed or was skipped. + + Reads the ancestors' live status rather than a separately-maintained + "failed" set, so the skip cascade is deterministic even when a node + fails fast inside its worker thread before the harvest loop observes it. + """ + return any( + results[aid].status in (STATUS_FAILED, STATUS_SKIPPED) + for aid in ancestor_ids + ) + + def _harvest_one(inflight: Dict[Future, str], - results: Dict[str, NodeResult], - failed: Set[str]) -> None: + results: Dict[str, NodeResult]) -> None: finished = next(iter(inflight)) nid = inflight.pop(finished) try: finished.result() - except (RuntimeError, OSError, ValueError) as error: + except (AutoControlException, RuntimeError, OSError, ValueError) as error: # Defensive: _run_one swallows tool errors into NodeResult. - # This branch only catches pool / runner-side faults. + # This branch only catches pool / runner-side faults. It includes + # AutoControlException so a framework failure that slips past + # _run_one still fails the node instead of tearing down run_dag. results[nid].status = STATUS_FAILED results[nid].error = repr(error) - if results[nid].status == STATUS_FAILED: - failed.add(nid) def _run_one(node: DagNode, result: NodeResult, runner: NodeRunner, _nodes: Dict[str, DagNode]) -> None: try: outcome = runner(node, _build_proxy_definition(node, _nodes)) - except (RuntimeError, OSError, ValueError) as error: + except (AutoControlException, RuntimeError, OSError, ValueError) as error: + # AutoControlException covers validate_actions / locate / assert + # failures raised by the executor: a node failure becomes a failed + # NodeResult (and a downstream skip cascade), not a raw crash that + # leaves the node stuck "running" and returns no DagRunResult. result.status = STATUS_FAILED result.error = f"{type(error).__name__}: {error}" autocontrol_logger.warning( diff --git a/je_auto_control/utils/data_source/data_source.py b/je_auto_control/utils/data_source/data_source.py index 54e525b4..80f24de4 100644 --- a/je_auto_control/utils/data_source/data_source.py +++ b/je_auto_control/utils/data_source/data_source.py @@ -24,8 +24,10 @@ import json import os import sqlite3 +from contextlib import closing from pathlib import Path from typing import Any, Callable, Dict, List, Optional +from urllib.parse import quote _READ_ONLY_SQL_PREFIXES = ("select", "with") @@ -86,8 +88,15 @@ def _load_sqlite(source: Dict[str, Any]) -> List[Dict[str, Any]]: """Run a read-only SELECT against a SQLite file and return dict rows.""" path = _resolve_path(source["path"]) query = _validate_select(str(source["query"])) - uri = f"file:{path}?mode=ro" - with sqlite3.connect(uri, uri=True) as conn: + # SQLite treats ``?`` as the query separator and percent-decodes ``%XX`` in + # the path, so a raw f-string opens the wrong file when the path contains + # ``?``/``#``/``%``. Percent-encode those while keeping the separators and + # the Windows drive colon literal. + safe_path = quote(str(path), safe="/:\\") + uri = f"file:{safe_path}?mode=ro" + # closing(): the sqlite3 context manager commits/rolls back but never closes + # the connection, so the file handle leaks until GC. Close it explicitly. + with closing(sqlite3.connect(uri, uri=True)) as conn: conn.row_factory = sqlite3.Row rows = conn.execute(query).fetchall() return [dict(row) for row in rows] diff --git a/je_auto_control/utils/dedup_window/dedup_window.py b/je_auto_control/utils/dedup_window/dedup_window.py index d9f2ab10..b4778810 100644 --- a/je_auto_control/utils/dedup_window/dedup_window.py +++ b/je_auto_control/utils/dedup_window/dedup_window.py @@ -31,7 +31,9 @@ def seen(self, message_id: str) -> bool: """Whether ``message_id`` is in the window and not expired.""" now = self._clock() self._purge(now) - return message_id in self._seen + # Coerce on lookup exactly as mark() coerces on store, so an int id + # (123) matches the "123" that was stored instead of never deduping. + return str(message_id) in self._seen def mark(self, message_id: str) -> None: """Record ``message_id`` as seen now.""" @@ -41,9 +43,10 @@ def check_and_mark(self, message_id: str) -> bool: """Atomically return ``True`` if first-seen (and mark), else ``False``.""" now = self._clock() self._purge(now) - if message_id in self._seen: + key = str(message_id) + if key in self._seen: return False - self._seen[str(message_id)] = now + self._seen[key] = now return True def purge_expired(self) -> int: diff --git a/je_auto_control/utils/deprecation.py b/je_auto_control/utils/deprecation.py new file mode 100644 index 00000000..c4084c68 --- /dev/null +++ b/je_auto_control/utils/deprecation.py @@ -0,0 +1,35 @@ +"""Consistent deprecation warnings for public AutoControl APIs.""" +from __future__ import annotations + +import functools +import warnings +from typing import Callable, TypeVar, cast + +F = TypeVar("F", bound=Callable) + + +class AutoControlDeprecationWarning(FutureWarning): + """A user-visible warning for an API scheduled for removal.""" + + +def deprecated(*, since: str, removal: str, replacement: str = ""): + """Mark a callable deprecated with actionable lifecycle metadata.""" + def decorate(func: F) -> F: + message = f"{func.__qualname__} is deprecated since {since}" + message += f" and will be removed in {removal}." + if replacement: + message += f" Use {replacement} instead." + + @functools.wraps(func) + def wrapped(*args, **kwargs): + warnings.warn(message, AutoControlDeprecationWarning, + stacklevel=2) + return func(*args, **kwargs) + wrapped.__deprecated__ = { # type: ignore[attr-defined] + "since": since, "removal": removal, "replacement": replacement, + } + return cast(F, wrapped) + return decorate + + +__all__ = ["AutoControlDeprecationWarning", "deprecated"] diff --git a/je_auto_control/utils/edge_lines/edge_lines.py b/je_auto_control/utils/edge_lines/edge_lines.py index 0682cc31..92308751 100644 --- a/je_auto_control/utils/edge_lines/edge_lines.py +++ b/je_auto_control/utils/edge_lines/edge_lines.py @@ -52,9 +52,10 @@ def find_lines(haystack: Optional[ImageSource] = None, *, segments = _segments(_haystack_gray(haystack, region), int(min_length), int(max_gap)) out: List[Dict[str, Any]] = [] - if segments is None: + if segments is None or len(segments) == 0: return out - for x1, y1, x2, y2 in segments[:, 0]: + # OpenCV 4 returns (N, 1, 4); OpenCV 5 flattened it to (N, 4). + for x1, y1, x2, y2 in segments.reshape(-1, 4): angle = math.degrees(math.atan2(int(y2) - int(y1), int(x2) - int(x1))) kind = _orientation(angle) if orientation not in ("any", kind): diff --git a/je_auto_control/utils/exception/exceptions.py b/je_auto_control/utils/exception/exceptions.py index 6a4a755b..a5e8891c 100644 --- a/je_auto_control/utils/exception/exceptions.py +++ b/je_auto_control/utils/exception/exceptions.py @@ -1,99 +1,106 @@ # general class AutoControlException(Exception): - pass + """Base class for every AutoControl runtime error. + + All framework exceptions derive from this so that containment boundaries + (executor, background poll loops, request handlers, GUI slots) can catch + the whole family with a single ``except AutoControlException``. Do not add + a sibling that inherits ``Exception`` directly — that silently escapes + every such boundary. + """ # Keyboard -class AutoControlKeyboardException(Exception): +class AutoControlKeyboardException(AutoControlException): pass -class AutoControlCantFindKeyException(Exception): +class AutoControlCantFindKeyException(AutoControlException): pass # Mouse -class AutoControlMouseException(Exception): +class AutoControlMouseException(AutoControlException): pass # Screen -class AutoControlScreenException(Exception): +class AutoControlScreenException(AutoControlException): pass # Image detect -class ImageNotFoundException(Exception): +class ImageNotFoundException(AutoControlException): pass # Record -class AutoControlRecordException(Exception): +class AutoControlRecordException(AutoControlException): pass # Execute action -class AutoControlExecuteActionException(Exception): +class AutoControlExecuteActionException(AutoControlException): pass -class AutoControlJsonActionException(Exception): +class AutoControlJsonActionException(AutoControlException): pass -class AutoControlActionNullException(Exception): +class AutoControlActionNullException(AutoControlException): pass -class AutoControlActionException(Exception): +class AutoControlActionException(AutoControlException): pass -class AutoControlAddCommandException(Exception): +class AutoControlAddCommandException(AutoControlException): pass -class AutoControlAssertionException(Exception): +class AutoControlAssertionException(AutoControlException): """Raised when an ``AC_assert_*`` check fails.""" -class AutoControlArgparseException(Exception): +class AutoControlArgparseException(AutoControlException): pass # html exception -class AutoControlHTMLException(Exception): +class AutoControlHTMLException(AutoControlException): pass # Json Exception -class AutoControlJsonException(Exception): +class AutoControlJsonException(AutoControlException): pass -class AutoControlGenerateJsonReportException(Exception): +class AutoControlGenerateJsonReportException(AutoControlException): pass # XML -class XMLException(Exception): +class XMLException(AutoControlException): pass -class XMLTypeException(Exception): +class XMLTypeException(AutoControlException): pass # Execute callback -class CallbackExecutorException(Exception): +class CallbackExecutorException(AutoControlException): pass diff --git a/je_auto_control/utils/executor/action_executor.py b/je_auto_control/utils/executor/action_executor.py index 639a3acf..065e714e 100644 --- a/je_auto_control/utils/executor/action_executor.py +++ b/je_auto_control/utils/executor/action_executor.py @@ -7,7 +7,8 @@ ) from je_auto_control.utils.exception.exceptions import ( AutoControlActionException, AutoControlAddCommandException, - AutoControlActionNullException + AutoControlActionNullException, AutoControlAssertionException, + AutoControlException ) from je_auto_control.utils.accessibility.accessibility_api import ( click_accessibility_element, find_accessibility_element, @@ -52,7 +53,7 @@ from je_auto_control.utils.run_history.history_store import default_history_store from je_auto_control.utils.secrets import default_secret_manager from je_auto_control.utils.script_vars.interpolate import ( - interpolate_actions, interpolate_value, + interpolate_value, ) from je_auto_control.utils.script_vars.scope import VariableScope from je_auto_control.utils.http_client.http_client import http_request @@ -4677,7 +4678,15 @@ def _expect_poll(action: Any, key: Any = None, op: str = "truthy", matcher = builders.get(str(op), to_be_truthy)() def getter(): - record = executor.execute_action([list(action)]) + try: + record = executor.execute_action([list(action)], raise_on_error=True) + except (AutoControlException, OSError, RuntimeError, ValueError, + LookupError): + # A failed polled action is "not yet satisfied", not a value. + # With raise_on_error=False the record would hold repr(error) — a + # truthy string that satisfies the default truthy matcher and ends + # the poll on the first attempt instead of waiting for success. + return None value = next(iter(record.values()), None) if key is not None and isinstance(value, dict): return value.get(key) @@ -6827,8 +6836,11 @@ class Executor: # Args keys that hold nested action lists; runtime interpolation must # leave them untouched so each iteration re-reads current variable state. + # ``catch``/``finally`` belong here too: AC_try only binds ``error_var`` + # once the body has failed, so eagerly interpolating them at dispatch + # would resolve ${err} before it exists. _DEFERRED_ARG_KEYS: frozenset = frozenset( - {"body", "then", "else", "branches"}) + {"body", "then", "else", "branches", "catch", "finally"}) def __init__(self): self._block_commands = BLOCK_COMMANDS @@ -7705,12 +7717,14 @@ def known_commands(self) -> set: def _resolve_runtime_args(self, args: Any) -> Any: """Interpolate ``${var}`` placeholders against the current scope. - Keys inside :attr:`_DEFERRED_ARG_KEYS` (``body``/``then``/``else``) - are left as-is so nested action lists keep their placeholders for - per-iteration evaluation. + Keys inside :attr:`_DEFERRED_ARG_KEYS` are left as-is so nested + action lists keep their placeholders for per-iteration evaluation. + + An empty scope is not a reason to skip interpolation: ``${secrets.*}`` + resolves through the vault without consulting the scope at all, and an + unknown ``${var}`` must raise rather than reach the automation as a + literal placeholder. """ - if not self.variables: - return args if isinstance(args, dict): resolved: Dict[str, Any] = {} for key, value in args.items(): @@ -7802,6 +7816,15 @@ def _run_one_action(self, action: list, record: Dict[str, Any], """Execute a single action, recording the result or raising.""" import time as _time key = "execute: " + str(action) + if key in record: + # Two byte-identical actions would otherwise share one record slot, + # so an earlier failure is silently overwritten by a later success. + # The first occurrence keeps the bare key (unchanged for callers); + # repeats get a numeric suffix so every outcome is preserved. + suffix = 2 + while f"{key} #{suffix}" in record: + suffix += 1 + key = f"{key} #{suffix}" action_name = action[0] if action and isinstance(action[0], str) else "" started = _time.monotonic() try: @@ -7810,10 +7833,22 @@ def _run_one_action(self, action: list, record: Dict[str, Any], _observe_executor_metrics(action_name, started, error=None) except (LoopBreak, LoopContinue): raise - except (AutoControlActionException, OSError, RuntimeError, - AttributeError, TypeError, ValueError) as error: + # LookupError covers the KeyError/IndexError raised by block handlers + # that subscript a required arg directly (``args["name"]``). Without + # it a merely malformed action escaped raise_on_error=False and + # aborted every remaining action instead of being recorded. + # ``AutoControlException`` is the family base: image/mouse/keyboard/ + # screen/null-action failures all subclass it, so an incidental error + # is contained (recorded) here rather than aborting the whole script. + except (AutoControlException, OSError, RuntimeError, + AttributeError, TypeError, ValueError, LookupError) as error: _observe_executor_metrics(action_name, started, error=error) - if raise_on_error: + # A failed ``AC_assert_*`` (raise_on_fail=True) is a deliberate + # fail signal, not an incidental error, so it still propagates even + # under raise_on_error=False — otherwise the assertion would be + # silently neutralised. ``AC_try``/``AC_retry`` run their body with + # raise_on_error=True and still catch it via their own tuples. + if raise_on_error or isinstance(error, AutoControlAssertionException): raise autocontrol_logger.info( f"execute_action failed, action: {action}, error: {repr(error)}" @@ -7867,12 +7902,16 @@ def execute_files(execute_files_list: list) -> List[Dict[str, str]]: def execute_action_with_vars(action_list: list, variables: dict ) -> Dict[str, str]: - """Interpolate ``${name}`` placeholders with ``variables`` and execute. - - The same mapping seeds the runtime variable scope so flow-control - commands (``AC_set_var``/``AC_if_var``/...) can read and mutate the - same values during execution. + """Seed ``variables`` into the runtime scope and execute. + + Interpolation happens at dispatch time through the executor's runtime + resolver, which defers nested action bodies (loops/branches/try). Doing a + separate eager pre-pass over the whole tree here resolved deferred-body + placeholders (``${item}``, loop counters, ``error_var``) against the seed + mapping — where they do not exist yet — raising ``Unknown variable`` before + execution, and resolved ``${secrets.*}`` at load time so the plaintext then + landed in logs and record keys. Seeding the scope and letting the runtime + resolver interpolate per action fixes both. """ - resolved = interpolate_actions(action_list, variables) executor.variables.update_many(variables) - return executor.execute_action(resolved) + return executor.execute_action(action_list) diff --git a/je_auto_control/utils/executor/action_schema.py b/je_auto_control/utils/executor/action_schema.py index 458b9354..1bb9ae79 100644 --- a/je_auto_control/utils/executor/action_schema.py +++ b/je_auto_control/utils/executor/action_schema.py @@ -9,6 +9,13 @@ from je_auto_control.utils.exception.exceptions import AutoControlActionException +# Every command that runs a nested action list must appear in one of the two +# maps below. The runners execute those bodies with ``_validated=True``, +# asserting someone already validated them — so a command missing from both +# has its body validated by nobody, and a malformed nested action surfaces as +# a raw IndexError at dispatch instead of a clean rejection. + +# Keys whose value is a flat action list: [[name, params], ...] FLOW_BODY_KEYS = { "AC_if_image_found": ("then", "else"), "AC_if_pixel": ("then", "else"), @@ -19,6 +26,16 @@ "AC_retry": ("body",), "AC_try": ("body", "catch", "finally"), "AC_for_each": ("body",), + "AC_for_each_row": ("body",), + "AC_assert_duration": ("body",), + "AC_define_macro": ("body",), +} + +# Keys whose value is a LIST OF action lists — one nesting level deeper than +# FLOW_BODY_KEYS. Validating these as if they were flat would reject every +# valid action, since each element is itself a list rather than a name. +FLOW_BRANCH_LIST_KEYS = { + "AC_parallel": ("branches",), } @@ -53,10 +70,24 @@ def _validate_single(action: Any, known: Set[str], trail: str) -> None: def _validate_nested_bodies(name: str, action: list, known: Set[str], trail: str) -> None: - if name not in FLOW_BODY_KEYS or len(action) < 2 or not isinstance(action[1], dict): + if len(action) < 2 or not isinstance(action[1], dict): + return + params = action[1] + for body_key in FLOW_BODY_KEYS.get(name, ()): + body = params.get(body_key) + if body is not None: + _validate_list(body, known, f"{trail}.{body_key}") + for list_key in FLOW_BRANCH_LIST_KEYS.get(name, ()): + _validate_branch_list(params.get(list_key), known, f"{trail}.{list_key}") + + +def _validate_branch_list(branches: Any, known: Set[str], trail: str) -> None: + """Validate a list of action lists (e.g. ``AC_parallel``'s ``branches``).""" + if branches is None: + return + # The visual builder may pass a JSON string, which the runtime parses via + # _as_list. Leave that shape to the runtime rather than reject it here. + if not isinstance(branches, list): return - for body_key in FLOW_BODY_KEYS[name]: - body = action[1].get(body_key) - if body is None: - continue - _validate_list(body, known, f"{trail}.{body_key}") + for idx, branch in enumerate(branches): + _validate_list(branch, known, f"{trail}[{idx}]") diff --git a/je_auto_control/utils/executor/flow_control.py b/je_auto_control/utils/executor/flow_control.py index b40a28ab..87a816ec 100644 --- a/je_auto_control/utils/executor/flow_control.py +++ b/je_auto_control/utils/executor/flow_control.py @@ -11,7 +11,7 @@ from typing import Any, Callable, Dict, Mapping, Optional, Sequence from je_auto_control.utils.exception.exceptions import ( - AutoControlActionException, ImageNotFoundException, + AutoControlActionException, AutoControlException, ImageNotFoundException, ) from je_auto_control.utils.logging.logging_instance import autocontrol_logger from je_auto_control.wrapper.auto_control_image import locate_image_center @@ -51,10 +51,29 @@ def _run_branch(executor: Any, body: Optional[list]) -> Any: def _run_strict(executor: Any, body: list) -> Any: - """Execute a nested body, re-raising the first error.""" + """Execute a nested body, re-raising the first error. + + An empty body is a valid no-op (matching :func:`_run_branch`); it must + not reach ``execute_action``, which rejects an empty action list. + """ + if not body: + return None return executor.execute_action(body, raise_on_error=True, _validated=True) +def _run_loop_body(executor: Any, body: Optional[list]) -> None: + """Execute a loop body once, treating an empty body as a no-op. + + A loop with an empty ``body`` is valid (it does nothing each pass) and + must not reach ``execute_action``, which raises on an empty action list. + ``AC_break`` / ``AC_continue`` raised from within the body still + propagate to the caller's loop handler. + """ + if not body: + return + executor.execute_action(body, _validated=True) + + def exec_if_image_found(executor: Any, args: Mapping[str, Any]) -> Any: """Run ``then`` when the image is present, else run ``else``.""" image = args["image"] @@ -117,7 +136,7 @@ def exec_loop(executor: Any, args: Mapping[str, Any]) -> int: completed = 0 for _ in range(times): try: - executor.execute_action(body, _validated=True) + _run_loop_body(executor, body) except LoopContinue: completed += 1 continue @@ -136,7 +155,7 @@ def exec_while_image(executor: Any, args: Mapping[str, Any]) -> int: iterations = 0 while iterations < max_iter and _image_present(image, threshold): try: - executor.execute_action(body, _validated=True) + _run_loop_body(executor, body) except LoopContinue: pass except LoopBreak: @@ -154,7 +173,7 @@ def exec_retry(executor: Any, args: Mapping[str, Any]) -> Any: for attempt in range(max_attempts): try: return _run_strict(executor, body) - except (AutoControlActionException, OSError, RuntimeError, + except (AutoControlException, OSError, RuntimeError, AttributeError, TypeError, ValueError) as error: last_error = error autocontrol_logger.info( @@ -171,9 +190,14 @@ def exec_retry(executor: Any, args: Mapping[str, Any]) -> Any: # Errors a protected block may raise that ``AC_try`` is willing to catch. # ``LoopBreak`` / ``LoopContinue`` are deliberately excluded so loop # control-flow still propagates through a try block. +# ``LookupError`` keeps this aligned with the executor's own catch tuple, so +# a KeyError/IndexError from a malformed nested action is catchable by +# ``AC_try`` rather than tearing down the whole script. ``AutoControlException`` +# is the family base (every framework error subclasses it), so image/mouse/ +# keyboard/screen/assertion failures inside the body are recoverable too. _TRY_CATCHABLE = ( - AutoControlActionException, OSError, RuntimeError, - AttributeError, TypeError, ValueError, + AutoControlException, OSError, RuntimeError, + AttributeError, TypeError, ValueError, LookupError, ) @@ -302,7 +326,7 @@ def exec_while_var(executor: Any, args: Mapping[str, Any]) -> int: iterations = 0 while iterations < max_iter and _compare_var(executor, args, "AC_while_var"): try: - executor.execute_action(body, _validated=True) + _run_loop_body(executor, body) except LoopContinue: pass except LoopBreak: @@ -324,7 +348,7 @@ def exec_for_each(executor: Any, args: Mapping[str, Any]) -> int: for item in items: executor.variables.set(var_name, item) try: - executor.execute_action(body, _validated=True) + _run_loop_body(executor, body) except LoopContinue: iterations += 1 continue @@ -355,7 +379,7 @@ def exec_for_each_row(executor: Any, args: Mapping[str, Any]) -> int: for row in rows: executor.variables.set(var_name, row) try: - executor.execute_action(body, _validated=True) + _run_loop_body(executor, body) except LoopContinue: iterations += 1 continue @@ -378,10 +402,18 @@ def exec_shell_to_var(executor: Any, args: Mapping[str, Any]) -> Dict[str, Any]: command = args.get("command", args.get("shell_command")) argv = ([str(part) for part in command] if isinstance(command, list) else shlex.split(str(command), posix=(os.name != "nt"))) - completed = subprocess.run( # nosec B603 — argv list, no shell # nosemgrep: python.lang.security.audit.dangerous-subprocess-use-audit.dangerous-subprocess-use-audit - argv, capture_output=True, check=False, - timeout=float(args.get("timeout", 30.0)), - ) + timeout_s = float(args.get("timeout", 30.0)) + try: + completed = subprocess.run( # nosec B603 — argv list, no shell # nosemgrep: python.lang.security.audit.dangerous-subprocess-use-audit.dangerous-subprocess-use-audit + argv, capture_output=True, check=False, timeout=timeout_s, + ) + except subprocess.TimeoutExpired as error: + # TimeoutExpired subclasses SubprocessError (not AutoControlException + # nor OSError), so without this it escapes every executor containment + # boundary and aborts the whole script. + raise AutoControlActionException( + f"AC_shell_to_var: command timed out after {timeout_s}s" + ) from error output = completed.stdout.decode("utf-8", errors="replace").strip() var_name = args.get("var", "shell_output") executor.variables.set(var_name, output) @@ -602,15 +634,42 @@ def exec_parallel(executor: Any, args: Mapping[str, Any]) -> Dict[str, Any]: ``branches`` is a list of action lists (or a JSON string of one). Each branch runs on a fresh :class:`Executor` with a separate variable scope, - so concurrent branches never race on shared state. + so concurrent branches never race on shared state. Custom commands + (registered via ``add_command_to_executor``) and ``AC_define_macro`` + macros are copied from the parent so a branch recognises them — otherwise + a branch's fresh executor only has the stock command set and rejects them + at runtime even though validation (against the parent) passed. """ - del executor from je_auto_control.utils.executor.action_executor import Executor + # Snapshot before spawning threads so branch workers never read the + # parent's live command/macro maps concurrently. + parent_event_dict = dict(executor.event_dict) + parent_macros = dict(executor.macros) branches = _as_list(args.get("branches")) results: list = [None] * len(branches) + errors: Dict[int, str] = {} def _run(index: int, branch: Any) -> None: - results[index] = Executor().execute_action(branch, _validated=True) + # An exception here would otherwise only kill this worker thread, + # leaving results[index] as None — indistinguishable from a branch + # that legitimately returned None, and reported as success. + try: + branch_executor = Executor() + # setdefault (not update): copy the parent's *custom* commands/macros + # while keeping the branch executor's own self-bound stock commands. + # A blind update() would overwrite AC_execute_action/AC_execute_files + # (bound to the parent) so a nested execute inside a branch would run + # against the parent's variable scope, defeating the isolation this + # function promises and reintroducing the cross-branch race. + for _name, _handler in parent_event_dict.items(): + branch_executor.event_dict.setdefault(_name, _handler) + branch_executor.macros.update(parent_macros) + results[index] = branch_executor.execute_action( + branch, _validated=True) + except Exception as error: # noqa: BLE001 # reason: see comment above + errors[index] = repr(error) + autocontrol_logger.error( + "AC_parallel branch %d failed: %r", index, error, exc_info=True) threads = [threading.Thread(target=_run, args=(idx, branch), daemon=True) for idx, branch in enumerate(branches)] @@ -618,6 +677,13 @@ def _run(index: int, branch: Any) -> None: thread.start() for thread in threads: thread.join() + if errors: + # Raise so the executor's own machinery handles it like any other + # failed command: recorded when raise_on_error is off, propagated + # when it is on. + failed = ", ".join(f"branch {idx}: {err}" for idx, err in sorted(errors.items())) + raise AutoControlActionException( + f"AC_parallel: {len(errors)} branch(es) failed: {failed}") return {"branches": len(branches), "results": results} diff --git a/je_auto_control/utils/expect_poll/expect_poll.py b/je_auto_control/utils/expect_poll/expect_poll.py index d442b2a0..5124e43c 100644 --- a/je_auto_control/utils/expect_poll/expect_poll.py +++ b/je_auto_control/utils/expect_poll/expect_poll.py @@ -37,12 +37,16 @@ def to_equal(expected: Any) -> Matcher: def to_contain(item: Any) -> Matcher: """Match when ``item`` is contained in the value.""" - return lambda value: item in value + # A getter returning None means "not ready yet" (e.g. a missing result key or + # a transiently-failing polled action). Treat it as not-matched so the poll + # keeps retrying instead of crashing with `item in None` -> TypeError. + return lambda value: value is not None and item in value def to_be_greater_than(threshold: Any) -> Matcher: """Match when the value is greater than ``threshold``.""" - return lambda value: value > threshold + # See to_contain: None is the not-ready sentinel; `None > x` would raise. + return lambda value: value is not None and value > threshold def to_match_regex(pattern: str) -> Matcher: @@ -80,7 +84,18 @@ def expect_poll(getter: Callable[[], Any], matcher: Matcher, *, Returns a :class:`PollResult` with the final value, attempt count and elapsed time. ``clock`` / ``sleep`` are injectable for deterministic tests. + + :raises ValueError: if ``interval_s`` is not positive. """ + # interval_s 必須為正。0 會讓迴圈以最快速度重跑 getter(實測 0.3 秒 + # 內 60 萬次),負值則由 time.sleep 拋出無關的錯誤訊息。此函式經 + # AC_expect_poll 暴露,interval_s 由使用者控制。 + # interval_s must be positive. 0 re-runs getter as fast as possible + # (measured ~600k iterations in 0.3s, pegging a core); a negative surfaces + # as time.sleep's own error. This is reachable via AC_expect_poll, where + # interval_s is user-controlled. + if interval_s <= 0: + raise ValueError("interval_s must be positive") start = clock() deadline = start + float(timeout_s) attempts = 0 @@ -94,7 +109,10 @@ def expect_poll(getter: Callable[[], Any], matcher: Matcher, *, if clock() >= deadline: return PollResult(False, value, attempts, round(clock() - start, 4), describe(value)) - sleep(float(interval_s)) + # Clamp the wait to the time left so a large interval_s cannot overshoot + # timeout_s (and run an extra getter well past the deadline). + remaining = deadline - clock() + sleep(min(float(interval_s), max(remaining, 0.0))) def assert_poll(getter: Callable[[], Any], matcher: Matcher, *, diff --git a/je_auto_control/utils/failure_bundle/__init__.py b/je_auto_control/utils/failure_bundle/__init__.py new file mode 100644 index 00000000..c1e4d3aa --- /dev/null +++ b/je_auto_control/utils/failure_bundle/__init__.py @@ -0,0 +1,11 @@ +"""Portable, redacted failure diagnostics.""" + +from je_auto_control.utils.failure_bundle.bundle import ( + FailureBundleOptions, + create_failure_bundle, + failure_bundle_on_error, +) + +__all__ = [ + "FailureBundleOptions", "create_failure_bundle", "failure_bundle_on_error", +] diff --git a/je_auto_control/utils/failure_bundle/bundle.py b/je_auto_control/utils/failure_bundle/bundle.py new file mode 100644 index 00000000..79035092 --- /dev/null +++ b/je_auto_control/utils/failure_bundle/bundle.py @@ -0,0 +1,176 @@ +"""Create a self-contained ZIP for diagnosing failed automation runs. + +Collection is deliberately best-effort: failure diagnostics must still be +created when screen capture, an optional backend, or the diagnostics runner +itself is broken. Text and structured values are redacted before they enter +the archive and arbitrary attachments are opt-in. +""" +from __future__ import annotations + +import json +import os +import platform +import sys +import tempfile +import time +import zipfile +from contextlib import contextmanager +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Iterable, Mapping + +from je_auto_control.utils.config_redaction import ( + redact_config, + redact_secret_text, +) + + +@dataclass(frozen=True) +class FailureBundleOptions: + """Controls potentially sensitive or expensive bundle collectors.""" + + screenshot: bool = True + diagnostics: bool = True + log_path: str | None = None + log_tail_bytes: int = 256_000 + attachments: tuple[str, ...] = () + + +def _json_bytes(value: Any) -> bytes: + return json.dumps(value, ensure_ascii=False, indent=2, + sort_keys=True, default=repr).encode("utf-8") + + +def _safe_name(path: Path, used: set[str]) -> str: + base = path.name or "attachment" + candidate, index = base, 2 + while candidate in used: + candidate = f"{path.stem}-{index}{path.suffix}" + index += 1 + used.add(candidate) + return candidate + + +def _read_log_tail(path: Path, limit: int) -> str: + with path.open("rb") as handle: + if path.stat().st_size > limit: + handle.seek(-limit, os.SEEK_END) + data = handle.read() + return redact_secret_text(data.decode("utf-8", errors="replace")) + + +def _collect_diagnostics(archive: zipfile.ZipFile, + failures: list[dict[str, str]]) -> None: + try: + from je_auto_control.utils.diagnostics import run_diagnostics + archive.writestr("diagnostics.json", + _json_bytes(run_diagnostics().to_dict())) + except Exception as exc: # diagnostics are best-effort + failures.append({"collector": "diagnostics", "error": repr(exc)}) + + +def _collect_screenshot(archive: zipfile.ZipFile, + failures: list[dict[str, str]]) -> None: + try: + from je_auto_control.utils.cv2_utils.screenshot import pil_screenshot + with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as image_file: + image_path = image_file.name + try: + pil_screenshot().save(image_path, format="PNG") + archive.write(image_path, "screenshot.png") + finally: + Path(image_path).unlink(missing_ok=True) + except Exception as exc: # headless and locked sessions are valid + failures.append({"collector": "screenshot", "error": repr(exc)}) + + +def _collect_log(archive: zipfile.ZipFile, opts: FailureBundleOptions, + failures: list[dict[str, str]]) -> None: + try: + archive.writestr("logs/tail.log", _read_log_tail( + Path(opts.log_path or "").expanduser().resolve(), + max(1, opts.log_tail_bytes))) + except Exception as exc: + failures.append({"collector": "log", "error": repr(exc)}) + + +def _collect_attachments(archive: zipfile.ZipFile, opts: FailureBundleOptions, + failures: list[dict[str, str]]) -> None: + used: set[str] = set() + for raw_path in opts.attachments: + try: + path = Path(raw_path).expanduser().resolve(strict=True) + if not path.is_file(): + raise ValueError("attachment is not a regular file") + archive.write(path, f"attachments/{_safe_name(path, used)}") + except Exception as exc: + failures.append({"collector": "attachment", "error": repr(exc)}) + + +def _collect_all(archive: zipfile.ZipFile, opts: FailureBundleOptions, + failures: list[dict[str, str]]) -> None: + if opts.diagnostics: + _collect_diagnostics(archive, failures) + if opts.screenshot: + _collect_screenshot(archive, failures) + if opts.log_path: + _collect_log(archive, opts, failures) + _collect_attachments(archive, opts, failures) + + +def create_failure_bundle( + output_path: str | os.PathLike[str], + *, + error: BaseException | str | None = None, + context: Mapping[str, Any] | None = None, + events: Iterable[Mapping[str, Any]] = (), + options: FailureBundleOptions | None = None, +) -> str: + """Write an atomic, redacted diagnostic ZIP and return its real path.""" + opts = options or FailureBundleOptions() + target = Path(output_path).expanduser().resolve() + target.parent.mkdir(parents=True, exist_ok=True) + failures: list[dict[str, str]] = [] + manifest = { + "schema": "autocontrol.failure-bundle/v1", + "created_at_unix": time.time(), + "error": None if error is None else redact_secret_text(str(error)), + "runtime": { + "python": sys.version.split()[0], + "platform": platform.platform(), + "executable": Path(sys.executable).name, + }, + "context": redact_config(dict(context or {})), + "events": redact_config(list(events)), + "collector_failures": failures, + } + + fd, temp_name = tempfile.mkstemp(prefix=f".{target.name}.", + suffix=".tmp", dir=str(target.parent)) + os.close(fd) + try: + with zipfile.ZipFile(temp_name, "w", zipfile.ZIP_DEFLATED) as archive: + _collect_all(archive, opts, failures) + archive.writestr("manifest.json", _json_bytes(manifest)) + os.replace(temp_name, target) + except BaseException: + Path(temp_name).unlink(missing_ok=True) + raise + return str(target) + + +@contextmanager +def failure_bundle_on_error( + output_path: str | os.PathLike[str], + *, + context: Mapping[str, Any] | None = None, + events: Iterable[Mapping[str, Any]] = (), + options: FailureBundleOptions | None = None, +): + """Create a bundle when the wrapped block raises, then re-raise it.""" + try: + yield + except BaseException as error: + create_failure_bundle(output_path, error=error, context=context, + events=events, options=options) + raise diff --git a/je_auto_control/utils/file_drop/file_drop.py b/je_auto_control/utils/file_drop/file_drop.py index d8fda491..c6588cb9 100644 --- a/je_auto_control/utils/file_drop/file_drop.py +++ b/je_auto_control/utils/file_drop/file_drop.py @@ -33,20 +33,41 @@ def plan_file_drop(paths: Sequence[str], *, point: Tuple[int, int] = (0, 0), "blob_size": len(blob)} +def _declare_win32_signatures(kernel32: Any, user32: Any) -> None: + """Declare argtypes/restypes so 64-bit handles aren't truncated. + + Without argtypes ctypes marshals every argument as a 32-bit ``int``, so a + 64-bit ``HGLOBAL`` / ``WPARAM`` is silently truncated — ``GlobalLock`` then + receives a bogus handle, returns ``NULL``, and ``memmove(NULL, ...)`` faults. + """ + import ctypes + from ctypes import wintypes + kernel32.GlobalAlloc.argtypes = [wintypes.UINT, ctypes.c_size_t] + kernel32.GlobalAlloc.restype = wintypes.HGLOBAL + kernel32.GlobalLock.argtypes = [wintypes.HGLOBAL] + kernel32.GlobalLock.restype = ctypes.c_void_p + kernel32.GlobalUnlock.argtypes = [wintypes.HGLOBAL] + kernel32.GlobalUnlock.restype = wintypes.BOOL + user32.PostMessageW.argtypes = [ + wintypes.HWND, wintypes.UINT, wintypes.WPARAM, wintypes.LPARAM, + ] + user32.PostMessageW.restype = wintypes.BOOL + + def _default_driver(hwnd: int, blob: bytes, point: Tuple[int, int]) -> bool: """Post a real ``WM_DROPFILES`` to ``hwnd`` (Windows only).""" import sys if not sys.platform.startswith("win"): raise RuntimeError("drop_files is only supported on Windows") import ctypes - from ctypes import wintypes kernel32, user32 = ctypes.windll.kernel32, ctypes.windll.user32 - kernel32.GlobalAlloc.restype = wintypes.HGLOBAL - kernel32.GlobalLock.restype = ctypes.c_void_p + _declare_win32_signatures(kernel32, user32) handle = kernel32.GlobalAlloc(_GMEM_MOVEABLE, len(blob)) if not handle: raise RuntimeError("GlobalAlloc failed") pointer = kernel32.GlobalLock(handle) + if not pointer: + raise RuntimeError("GlobalLock failed") ctypes.memmove(pointer, blob, len(blob)) kernel32.GlobalUnlock(handle) # The receiving window owns the memory and frees it via DragFinish. diff --git a/je_auto_control/utils/generate_report/generate_html_report.py b/je_auto_control/utils/generate_report/generate_html_report.py index 94e532e5..6238d64a 100644 --- a/je_auto_control/utils/generate_report/generate_html_report.py +++ b/je_auto_control/utils/generate_report/generate_html_report.py @@ -1,3 +1,4 @@ +import html from threading import Lock from je_auto_control.utils.exception.exception_tags import html_generate_no_data_tag_error_message @@ -107,14 +108,18 @@ def make_html_table(event_str: str, record_data: dict, table_head: str) -> str: :param table_head: 表頭樣式 (成功/失敗) Table head style :return: 更新後的 HTML 字串 Updated HTML string """ + # Escape recorded values — a param/exception may contain <, >, & (e.g. an + # AC_write of "" + out = make_html_table("", _record(payload), "event_table_head") + assert payload not in out + assert html.escape(payload) in out + assert "<script>" in out + + +def test_ampersand_and_angle_brackets_escaped(): + out = make_html_table("", _record("a & b < c"), "event_table_head") + assert "a & b < c" in out + assert "a & b < c" not in out + + +def test_exception_field_is_escaped(): + record = _record("ok") + record["program_exception"] = "boom" + out = make_html_table("", record, "failure_table_head") + assert "boom" not in out + assert "<b>boom</b>" in out + + +def test_none_values_render_as_literal_none(): + record = { + "function_name": None, "local_param": None, + "time": None, "program_exception": None, + } + out = make_html_table("", record, "event_table_head") + assert "None" in out diff --git a/test/unit_test/headless/test_r3_agent_llm_backend.py b/test/unit_test/headless/test_r3_agent_llm_backend.py new file mode 100644 index 00000000..7da5eaf7 --- /dev/null +++ b/test/unit_test/headless/test_r3_agent_llm_backend.py @@ -0,0 +1,60 @@ +"""Round-3 regression: the Anthropic *planner* LLM backend must catch +``anthropic.AnthropicError`` and honour the empty-string contract instead of +letting a 429/5xx/timeout escape ``complete``. + +The ``anthropic`` SDK is not installed in CI, so a fake module carrying an +``AnthropicError`` base class is injected for the duration of each test. +""" +import sys +import types + +from je_auto_control.utils.llm.backends.anthropic_backend import ( + AnthropicLLMBackend, +) + + +class _FakeAnthropicError(Exception): + """Stand-in for ``anthropic.AnthropicError`` (a bare Exception).""" + + +class _FakeRateLimitError(_FakeAnthropicError): + """Subclass, like the real ``anthropic.RateLimitError``.""" + + +def _install_fake_anthropic(monkeypatch): + module = types.ModuleType("anthropic") + module.AnthropicError = _FakeAnthropicError + monkeypatch.setitem(sys.modules, "anthropic", module) + + +def _backend(client): + backend = AnthropicLLMBackend.__new__(AnthropicLLMBackend) + backend.available = True + backend._client = client + return backend + + +def _client_raising(exc): + class _Messages: + def create(self, **kwargs): + raise exc + + return types.SimpleNamespace(messages=_Messages()) + + +def test_sdk_error_degrades_to_empty_string(monkeypatch): + _install_fake_anthropic(monkeypatch) + backend = _backend(_client_raising(_FakeRateLimitError("429 rate limited"))) + assert backend.complete("plan the next action") == "" + + +def test_successful_response_returns_joined_text(monkeypatch): + _install_fake_anthropic(monkeypatch) + block = types.SimpleNamespace(type="text", text="planned action") + + class _Messages: + def create(self, **kwargs): + return types.SimpleNamespace(content=[block]) + + client = types.SimpleNamespace(messages=_Messages()) + assert _backend(client).complete("plan the next action") == "planned action" diff --git a/test/unit_test/headless/test_r3_agent_loop_dispatch.py b/test/unit_test/headless/test_r3_agent_loop_dispatch.py new file mode 100644 index 00000000..2eb1f18c --- /dev/null +++ b/test/unit_test/headless/test_r3_agent_loop_dispatch.py @@ -0,0 +1,38 @@ +"""Round-3 regression: AgentLoop._dispatch_tool must record AutoControl* +exceptions and TypeErrors per-step (loop continues) instead of crashing the +whole run.""" +from je_auto_control.utils.agent import AgentLoop, FakeAgentBackend +from je_auto_control.utils.exception.exceptions import AutoControlMouseException + + +def _run_with_failing_runner(exc): + def runner(_name, _args): + raise exc + + backend = FakeAgentBackend([ + {"tool": "AC_click_mouse", "input": {"button": "left"}}, + {"stop": True, "message": "done"}, + ]) + return AgentLoop( + backend, tool_runner=runner, screenshot_fn=lambda: None, + ).run("goal") + + +def test_autocontrol_exception_recorded_and_loop_continues(): + result = _run_with_failing_runner(AutoControlMouseException("boom")) + failing = next(s for s in result.steps if s.tool == "AC_click_mouse") + assert failing.error is not None + assert "AutoControlMouseException" in failing.error + # The loop kept going to the stop decision rather than aborting the run. + assert result.succeeded is True + assert result.final_message == "done" + + +def test_type_error_from_hallucinated_kwarg_recorded_and_loop_continues(): + result = _run_with_failing_runner( + TypeError("unexpected keyword argument 'foo'"), + ) + failing = next(s for s in result.steps if s.tool == "AC_click_mouse") + assert failing.error is not None + assert "TypeError" in failing.error + assert result.succeeded is True diff --git a/test/unit_test/headless/test_r3_agent_openai_backend.py b/test/unit_test/headless/test_r3_agent_openai_backend.py new file mode 100644 index 00000000..52910855 --- /dev/null +++ b/test/unit_test/headless/test_r3_agent_openai_backend.py @@ -0,0 +1,73 @@ +"""Round-3 regressions for the OpenAI agent backend. + +Covers: + * ``parallel_tool_calls=False`` must be sent (the loop answers only the first + tool_call, so parallel calls would 400 the next request), + * a ``finish_reason == "length"`` truncation with no tool_call must be + surfaced as an error rather than a successful final answer. +""" +from types import SimpleNamespace + +import pytest + +from je_auto_control.utils.agent.backends.base import AgentBackendError +from je_auto_control.utils.agent.backends.openai import OpenAIAgentBackend + + +class _RecordingOpenAIClient: + """Captures chat.completions.create kwargs, returns a canned response.""" + + def __init__(self, response): + self.captured: dict = {} + outer = self + + class _Completions: + def create(self, **kwargs): + outer.captured = kwargs + return response + + class _Chat: + def __init__(self): + self.completions = _Completions() + + self.chat = _Chat() + + +def _tool_call(call_id, name="AC_click_mouse", arguments="{}"): + return SimpleNamespace( + id=call_id, function=SimpleNamespace(name=name, arguments=arguments), + ) + + +def _response(tool_calls=None, content=None, finish_reason="stop"): + message = SimpleNamespace(content=content, tool_calls=tool_calls) + choice = SimpleNamespace(message=message, finish_reason=finish_reason) + return SimpleNamespace(choices=[choice]) + + +def _backend(client): + return OpenAIAgentBackend(client=client, tools=[{"type": "function"}]) + + +def test_parallel_tool_calls_disabled_on_every_request(): + client = _RecordingOpenAIClient( + _response(tool_calls=[_tool_call("call_1")], finish_reason="tool_calls"), + ) + _backend(client).decide_next_action("goal", None, []) + assert client.captured.get("parallel_tool_calls") is False + + +def test_length_truncation_without_tool_call_raises(): + client = _RecordingOpenAIClient( + _response(content="partial", finish_reason="length"), + ) + with pytest.raises(AgentBackendError): + _backend(client).decide_next_action("goal", None, []) + + +def test_normal_completion_still_stops(): + client = _RecordingOpenAIClient( + _response(content="done", finish_reason="stop"), + ) + decision = _backend(client).decide_next_action("goal", None, []) + assert decision == {"stop": True, "message": "done"} diff --git a/test/unit_test/headless/test_r3_data_source_uri.py b/test/unit_test/headless/test_r3_data_source_uri.py new file mode 100644 index 00000000..63898daa --- /dev/null +++ b/test/unit_test/headless/test_r3_data_source_uri.py @@ -0,0 +1,37 @@ +"""Round-3 regression: data_source SQLite URI must percent-encode the path. + +The sibling bug in ``sql/sql_query.py`` was fixed by its own agent; this pins +the identical raw-f-string URI defect in ``data_source._load_sqlite`` — a path +containing ``%``/``#`` opened the wrong file (or none) despite the pre-check +passing. +""" +import sqlite3 + +import pytest + +from je_auto_control.utils.data_source.data_source import load_rows + + +def _make_db(path): + """Create a one-row SQLite DB at ``path`` (a normal, non-URI filename).""" + conn = sqlite3.connect(str(path)) + try: + conn.execute("CREATE TABLE t (name TEXT)") + conn.execute("INSERT INTO t (name) VALUES ('ok')") + conn.commit() + finally: + conn.close() + + +@pytest.mark.parametrize("filename", ["weird%41name.db", "has#hash.db"]) +def test_sqlite_source_opens_path_with_uri_special_chars(tmp_path, filename): + """A DB whose filename contains ``%``/``#`` loads its rows correctly.""" + db_path = tmp_path / filename + _make_db(db_path) + rows = load_rows( + {"kind": "sqlite", "path": str(db_path), "query": "SELECT name FROM t"}) + assert rows == [{"name": "ok"}] + + +if __name__ == "__main__": + raise SystemExit(pytest.main([__file__, "-v"])) diff --git a/test/unit_test/headless/test_r3_executor_containment.py b/test/unit_test/headless/test_r3_executor_containment.py new file mode 100644 index 00000000..708858c1 --- /dev/null +++ b/test/unit_test/headless/test_r3_executor_containment.py @@ -0,0 +1,145 @@ +"""Round-3 audit regressions for executor/flow-control containment. + +These pin the orchestrator-owned fixes that pair with the exception-hierarchy +reparent (every framework exception now subclasses ``AutoControlException``): + +* framework exceptions are contained (recorded) by the per-action boundary + instead of aborting the whole script; +* ``AC_try`` / ``AC_retry`` catch the whole family, not just + ``AutoControlActionException``; +* an empty flow body is a no-op rather than an ``AutoControlActionNullException``; +* ``AC_shell_to_var`` timeouts are contained; +* ``AC_expect_poll`` treats a failed polled action as "not yet satisfied"; +* ``execute_action_with_vars`` defers nested bodies (runtime ``${item}`` works); +* ``AC_parallel`` branches see custom commands / macros; +* byte-identical repeated actions keep separate record entries. + +No real mouse/keyboard/screen is driven — every command is a stub. +""" +import subprocess + +import pytest + +from je_auto_control.utils.executor.action_executor import ( + Executor, add_command_to_executor, execute_action_with_vars, executor, +) +from je_auto_control.utils.exception.exceptions import ( + AutoControlAssertionException, AutoControlMouseException, + ImageNotFoundException, +) + + +def test_framework_exception_is_contained_not_raised(): + """raise_on_error=False must record a framework error, not let it escape.""" + engine = Executor() + engine.event_dict["AC_r3_boom"] = _raiser(ImageNotFoundException("missing")) + record = engine.execute_action([["AC_r3_boom"]], _validated=True) + assert any("ImageNotFoundException" in str(v) for v in record.values()) + + +def test_ac_try_catches_framework_exception(): + """AC_try's catch branch runs when the body raises a framework error.""" + engine = Executor() + caught = {"ran": False} + engine.event_dict["AC_r3_boom"] = _raiser( + AutoControlAssertionException("assert failed")) + engine.event_dict["AC_r3_recover"] = lambda: caught.__setitem__("ran", True) + engine.execute_action( + [["AC_try", {"body": [["AC_r3_boom"]], + "catch": [["AC_r3_recover"]]}]], _validated=True) + assert caught["ran"] is True + + +def test_empty_loop_body_is_noop(): + """AC_loop with an empty body completes its iterations without error.""" + engine = Executor() + record = engine.execute_action( + [["AC_loop", {"times": 3, "body": []}]], _validated=True) + assert next(iter(record.values())) == 3 + + +def test_shell_to_var_timeout_is_contained(monkeypatch): + """A shell timeout is converted to a contained framework error.""" + def _timeout(*_args, **kwargs): + raise subprocess.TimeoutExpired(cmd="x", timeout=kwargs.get("timeout", 1)) # nosemgrep: python.lang.security.audit.dangerous-subprocess-use-audit.dangerous-subprocess-use-audit # reason: raising the TimeoutExpired exception class, not spawning a subprocess + + monkeypatch.setattr(subprocess, "run", _timeout) + engine = Executor() + record = engine.execute_action( + [["AC_shell_to_var", {"command": "sleep 9", "timeout": 1}]], + _validated=True) + assert any("timed out" in str(v) for v in record.values()) + + +def test_expect_poll_failed_action_is_not_success(): + """A polled action that always fails must not be reported as ok=True.""" + executor.event_dict["AC_r3_pollboom"] = _raiser( + AutoControlMouseException("nope")) + try: + record = executor.execute_action( + [["AC_expect_poll", {"action": ["AC_r3_pollboom"], + "timeout_s": 0.2, "interval_s": 0.05}]], + _validated=True) + result = next(iter(record.values())) + assert result["ok"] is False + finally: + executor.event_dict.pop("AC_r3_pollboom", None) + + +def test_execute_action_with_vars_defers_loop_body(): + """Runtime ${item} inside a deferred body resolves per-iteration. + + The eager pre-pass previously resolved ${item} against the seed mapping — + where it does not exist — and raised before execution. + """ + execute_action_with_vars( + [["AC_for_each", {"items": ["a", "b"], "as": "item", + "body": [["AC_set_var", + {"name": "r3_last", "value": "${item}"}]]}]], + {"seed_only": 1}) + assert executor.variables.get_value("r3_last") == "b" + + +def test_parallel_branch_sees_custom_command(): + """A command added via add_command_to_executor works inside AC_parallel.""" + ran = {"flag": False} + add_command_to_executor( + {"AC_r3_custom": lambda: ran.__setitem__("flag", True)}) + try: + executor.execute_action( + [["AC_parallel", {"branches": [[["AC_r3_custom"]]]}]], + _validated=True) + assert ran["flag"] is True + finally: + executor.event_dict.pop("AC_r3_custom", None) + + +def test_duplicate_actions_recorded_separately(): + """Byte-identical actions keep separate record entries (no overwrite).""" + engine = Executor() + calls = {"n": 0} + + def _sometimes(): + calls["n"] += 1 + if calls["n"] == 1: + raise AutoControlMouseException("first fails") + return "ok" + + engine.event_dict["AC_r3_dup"] = _sometimes + record = engine.execute_action( + [["AC_r3_dup"], ["AC_r3_dup"]], _validated=True) + assert len(record) == 2 + values = [str(v) for v in record.values()] + assert any("AutoControlMouseException" in v for v in values) + assert any(v == "ok" for v in values) + + +def _raiser(error): + """Return a zero-arg callable that raises ``error`` when invoked.""" + def _raise(): + raise error + return _raise + + +if __name__ == "__main__": + raise SystemExit(pytest.main([__file__, "-v"])) diff --git a/test/unit_test/headless/test_r3_gui_main_window.py b/test/unit_test/headless/test_r3_gui_main_window.py new file mode 100644 index 00000000..69202b3c --- /dev/null +++ b/test/unit_test/headless/test_r3_gui_main_window.py @@ -0,0 +1,55 @@ +"""Round-3 GUI audit regression: applying the font must not wipe the theme. + +``_apply_font_pt`` used to call ``setStyleSheet("font-size: ...")`` which +*replaces* the widget stylesheet, discarding the qt_material theme that +``apply_stylesheet`` had just installed (finding 5). The font rule must now be +merged on top of the captured theme stylesheet instead. +""" +import os + +import pytest + +os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") +pytest.importorskip("PySide6.QtWidgets", exc_type=ImportError) +# main_window imports qt_material (the theme); the headless CI job installs +# PySide6 but not the GUI theme extra, so skip cleanly there rather than erroring +# out collection for the whole suite. +pytest.importorskip("qt_material") + +from PySide6.QtWidgets import QApplication, QMainWindow # noqa: E402 + +from je_auto_control.gui.main_window import AutoControlGUIUI # noqa: E402 + + +@pytest.fixture(scope="module") +def qapp(): + return QApplication.instance() or QApplication([]) + + +def test_apply_font_pt_preserves_theme_stylesheet(qapp): + # A bare QMainWindow stands in for the real window so the whole tab set is + # not constructed; _apply_font_pt only touches _theme_stylesheet, + # _detect_auto_font_pt (skipped for pt > 0) and setStyleSheet. + window = QMainWindow() + window._theme_stylesheet = "QWidget { color: rgb(1, 2, 3); }" + try: + AutoControlGUIUI._apply_font_pt(window, 14) + sheet = window.styleSheet() + assert "color: rgb(1, 2, 3)" in sheet # theme kept + assert "font-size: 14pt" in sheet # font applied + finally: + window.deleteLater() + + +def test_apply_font_pt_keeps_theme_across_size_changes(qapp): + window = QMainWindow() + window._theme_stylesheet = "QPushButton { background: rgb(9, 9, 9); }" + try: + AutoControlGUIUI._apply_font_pt(window, 12) + AutoControlGUIUI._apply_font_pt(window, 20) # simulate a text-size change + sheet = window.styleSheet() + assert "background: rgb(9, 9, 9)" in sheet # theme survives the change + assert "font-size: 20pt" in sheet + assert "font-size: 12pt" not in sheet # old rule fully replaced + finally: + window.deleteLater() diff --git a/test/unit_test/headless/test_r3_gui_script_builder.py b/test/unit_test/headless/test_r3_gui_script_builder.py new file mode 100644 index 00000000..749d7c12 --- /dev/null +++ b/test/unit_test/headless/test_r3_gui_script_builder.py @@ -0,0 +1,143 @@ +"""Round-3 GUI audit regressions for the Script Builder model, tree and form. + +Covers: +* drag-drop no longer silently desyncs the Step model (finding 1); +* ``Step`` uses identity equality so duplicate steps are edited correctly + (finding 2); +* ``actions_to_steps`` unwraps the ``auto_control`` mapping and rejects + shapes it cannot represent instead of fabricating garbage (finding 3); +* ``_commit_field`` survives an unparseable value in one field instead of + aborting/freezing every later edit (finding 4). +""" +import os + +import pytest + +os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") +pytest.importorskip("PySide6.QtWidgets", exc_type=ImportError) + +from PySide6.QtWidgets import QAbstractItemView, QApplication # noqa: E402 + +from je_auto_control.gui.script_builder.command_schema import ( # noqa: E402 + COMMAND_SPECS, +) +from je_auto_control.gui.script_builder.step_form_view import ( # noqa: E402 + StepFormView, +) +from je_auto_control.gui.script_builder.step_list_view import ( # noqa: E402 + StepTreeView, +) +from je_auto_control.gui.script_builder.step_model import ( # noqa: E402 + Step, action_to_step, actions_to_steps, steps_to_actions, +) + + +@pytest.fixture(scope="module") +def qapp(): + return QApplication.instance() or QApplication([]) + + +def _command_with_body_key(): + for command, spec in COMMAND_SPECS.items(): + if spec.body_keys: + return command, spec.body_keys[0] + return None, None + + +# --- Finding 2: identity equality ------------------------------------------ + +def test_step_uses_identity_equality(): + a = Step(command="AC_ok") + b = Step(command="AC_ok") # structurally identical, distinct object + assert a == a # NOSONAR python:S1764 # reason: verifies identity-equality reflexivity (a is a) + assert a != b + steps = [a, b] + steps.remove(b) + assert steps == [a] # removed the selected object, not the first equal one + + +def test_duplicate_root_delete_targets_selected(qapp): + tree = StepTreeView() + first = Step(command="AC_ok") + second = Step(command="AC_ok") # duplicate + tree.load_steps([first, second]) + tree.setCurrentItem(tree.topLevelItem(1)) # select the second (duplicate) + tree.remove_selected() + remaining = tree.root_steps() + assert len(remaining) == 1 + assert remaining[0] is first # the first survived, not a value match + + +# --- Finding 1: drag-drop disabled + nested delete stays in sync ----------- + +def test_item_drag_drop_is_disabled(qapp): + tree = StepTreeView() + # InternalMove reorders items without touching the model; it must be off. + assert tree.dragDropMode() == QAbstractItemView.DragDropMode.NoDragDrop + + +def test_nested_duplicate_delete_syncs_model(qapp): + command, body_key = _command_with_body_key() + if command is None: + pytest.skip("no flow-control command with body_keys in the schema") + child_a = Step(command="AC_ok") + child_b = Step(command="AC_ok") # duplicate of child_a + parent = Step(command=command, bodies={body_key: [child_a, child_b]}) + tree = StepTreeView() + tree.load_steps([parent]) + parent_item = tree.topLevelItem(0) + body_item = parent_item.child(0) + tree.setCurrentItem(body_item.child(1)) # the duplicate nested child + tree.remove_selected() # must not raise AttributeError + assert parent.bodies[body_key] == [child_a] + assert parent.bodies[body_key][0] is child_a + + +# --- Finding 3: actions_to_steps input shapes ------------------------------ + +def test_actions_to_steps_unwraps_auto_control_mapping(): + steps = actions_to_steps({"auto_control": [["AC_ok"]]}) + # Before the fix a dict was iterated as keys, so "auto_control" became a + # fabricated command "a". + assert [s.command for s in steps] == ["AC_ok"] + + +def test_actions_to_steps_round_trips_plain_list(): + steps = actions_to_steps([["AC_ok"]]) + assert steps_to_actions(steps) == [["AC_ok"]] + + +def test_actions_to_steps_rejects_dict_entry(): + with pytest.raises(ValueError): + actions_to_steps([{"foo": "bar"}]) + + +def test_actions_to_steps_rejects_string_action(): + with pytest.raises(ValueError): + action_to_step("auto_control") # used to become command "a" + + +def test_actions_to_steps_rejects_non_list(): + with pytest.raises(ValueError): + actions_to_steps(42) + + +def test_actions_to_steps_rejects_mapping_without_key(): + with pytest.raises(ValueError): + actions_to_steps({"not_auto_control": []}) + + +# --- Finding 4: commit survives an unparseable field ----------------------- + +def test_commit_survives_unparseable_int_field(qapp): + # AC_human_type has a STRING field (text) and an optional INT field (seed). + step = action_to_step(["AC_human_type", {"text": "hi", "seed": 5}]) + view = StepFormView() + view.load_step(step) + # A loaded float in an int field (setText bypasses the validator), exactly + # like a JSON "100.0" landing in an int editor. + view._editors["seed"].setText("100.0") + # Editing another field fires _commit_field; it must not abort on the bad + # int, so the string edit is persisted. + view._editors["text"].setText("changed") + assert step.params["text"] == "changed" diff --git a/test/unit_test/headless/test_r3_gui_slot_exceptions.py b/test/unit_test/headless/test_r3_gui_slot_exceptions.py new file mode 100644 index 00000000..65b9feb1 --- /dev/null +++ b/test/unit_test/headless/test_r3_gui_slot_exceptions.py @@ -0,0 +1,160 @@ +"""Round-3 GUI audit regressions: Actions-menu slots must contain framework +exceptions instead of letting them escape into the Qt event loop. + +* builder_tab / main_widget slots caught only builtin exception types while + their callees raise ``AutoControl*Exception`` (finding 6); +* the auto-click ``_do_click`` slot skipped ``timer.stop()`` on a throwing + backend, so the QTimer fired forever (finding 7); +* ``TestSuiteTab._on_load_file`` read a file with no error handling, so a + non-UTF-8 file escaped the slot (finding 10). + +Each slot is exercised on a lightweight stub ``self`` with the callee +monkeypatched to fail, so no full Qt widget tree is constructed. +""" +import os +import types + +import pytest + +os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") +pytest.importorskip("PySide6.QtWidgets", exc_type=ImportError) + +from je_auto_control.utils.exception.exceptions import ( # noqa: E402 + AutoControlExecuteActionException, AutoControlHTMLException, + AutoControlMouseException, +) + + +def _raiser(exc): + def _fn(*_args, **_kwargs): + raise exc + return _fn + + +# --- Finding 6: builder_tab._on_run ---------------------------------------- + +def test_builder_run_slot_surfaces_autocontrol_exception(monkeypatch): + import je_auto_control.gui.script_builder.builder_tab as bt + warned = {} + monkeypatch.setattr(bt.QMessageBox, "warning", + lambda *a, **k: warned.setdefault("hit", True)) + monkeypatch.setattr( + bt, "execute_action", + _raiser(AutoControlExecuteActionException("unregistered command")), + ) + + tree = types.SimpleNamespace(root_steps=lambda: [bt.Step(command="AC_ok")]) + result = types.SimpleNamespace(setPlainText=lambda _s: None) + stub = types.SimpleNamespace(_tree=tree, _result=result) + + bt.ScriptBuilderTab._on_run(stub) # must not raise + assert warned.get("hit") + + +# --- Finding 6: main_widget record / script slots -------------------------- + +def test_playback_record_slot_surfaces_autocontrol_exception(monkeypatch): + import je_auto_control.gui.main_widget as mw + warned = {} + monkeypatch.setattr(mw.QMessageBox, "warning", + lambda *a, **k: warned.setdefault("hit", True)) + monkeypatch.setattr( + mw, "execute_action", + _raiser(AutoControlExecuteActionException("boom")), + ) + stub = types.SimpleNamespace(_record_data=[["AC_ok"]]) + + mw.AutoControlGUIWidget._playback_record(stub) # must not raise + assert warned.get("hit") + + +def test_execute_script_slot_surfaces_autocontrol_exception(monkeypatch): + import je_auto_control.gui.main_widget as mw + captured = {} + monkeypatch.setattr(mw, "read_action_json", lambda _p: [["AC_ok"]]) + monkeypatch.setattr( + mw, "execute_action", + _raiser(AutoControlExecuteActionException("boom")), + ) + editor = types.SimpleNamespace(text=lambda: "some.json") + result = types.SimpleNamespace(setText=lambda s: captured.__setitem__("t", s)) + stub = types.SimpleNamespace(script_path_input=editor, + script_result_text=result) + + mw.AutoControlGUIWidget._execute_script(stub) # must not raise + assert captured.get("t", "").startswith("Error") + + +# --- Finding 6: report tab, zero records ----------------------------------- + +def test_report_gen_html_slot_surfaces_autocontrol_exception(): + from je_auto_control.gui._report_tab import ReportTabMixin + from je_auto_control.utils.test_record.record_test_class import ( + test_record_instance, + ) + test_record_instance.test_record_list.clear() # zero records -> raises + captured = {} + stub = types.SimpleNamespace( + report_name_input=types.SimpleNamespace(text=lambda: "rpt"), + report_result_text=types.SimpleNamespace( + setText=lambda s: captured.__setitem__("t", s), + ), + ) + + ReportTabMixin._gen_html(stub) # must not raise AutoControlHTMLException + assert captured.get("t", "").startswith("Error") + + +def test_report_generate_html_report_really_raises_on_zero_records(): + # Guards the assumption behind the slot test above. + from je_auto_control.utils.generate_report.generate_html_report import ( + generate_html_report, + ) + from je_auto_control.utils.test_record.record_test_class import ( + test_record_instance, + ) + test_record_instance.test_record_list.clear() + with pytest.raises(AutoControlHTMLException): + generate_html_report("unused") + + +# --- Finding 7: auto-click timer stops on a throwing backend --------------- + +def test_do_click_stops_timer_on_backend_exception(monkeypatch): + import je_auto_control.gui._auto_click_tab as ac + monkeypatch.setattr(ac.QMessageBox, "warning", lambda *a, **k: None) + monkeypatch.setattr( + ac, "click_mouse", + _raiser(AutoControlMouseException("no backend")), + ) + stops = {"n": 0} + stub = types.SimpleNamespace( + click_type_combo=types.SimpleNamespace(currentIndex=lambda: 0), + mouse_radio=types.SimpleNamespace(isChecked=lambda: True), + mouse_button_combo=types.SimpleNamespace(currentText=lambda: "mouse_left"), + cursor_x_input=types.SimpleNamespace(text=lambda: "0"), + cursor_y_input=types.SimpleNamespace(text=lambda: "0"), + timer=types.SimpleNamespace(stop=lambda: stops.__setitem__("n", stops["n"] + 1)), + ) + + ac.AutoClickTabMixin._do_click(stub) # must not raise + assert stops["n"] == 1 # the failing auto-click QTimer was stopped + + +# --- Finding 10: suite load-file tolerates a bad file ---------------------- + +def test_suite_load_file_handles_non_utf8(monkeypatch, tmp_path): + import je_auto_control.gui.test_suite_tab as ts + bad = tmp_path / "bad.json" + bad.write_bytes(b"\xff\xfe\x00 not valid utf-8 \xff") + monkeypatch.setattr(ts.QFileDialog, "getOpenFileName", + lambda *a, **k: (str(bad), "")) + captured = {} + stub = types.SimpleNamespace( + _spec=types.SimpleNamespace(setPlainText=lambda _s: captured.__setitem__("spec", True)), + _summary=types.SimpleNamespace(setText=lambda s: captured.__setitem__("summary", s)), + ) + + ts.TestSuiteTab._on_load_file(stub) # must not raise UnicodeDecodeError + assert "summary" in captured # error surfaced on the summary label + assert "spec" not in captured # the unreadable content was not shown diff --git a/test/unit_test/headless/test_r3_gui_thread_marshal.py b/test/unit_test/headless/test_r3_gui_thread_marshal.py new file mode 100644 index 00000000..a6c073fa --- /dev/null +++ b/test/unit_test/headless/test_r3_gui_thread_marshal.py @@ -0,0 +1,159 @@ +"""Round-3 GUI audit regressions for worker-thread -> GUI-thread hand-off. + +* ``QTimer.singleShot`` fired from a non-Qt thread never runs (no event loop + there); the LAN browser, presence roster and WebRTC file-received callbacks + must marshal via a Qt Signal instead (finding 8); +* the admin-console thumbnail poll must ``deleteLater`` its QThread/worker each + tick instead of leaking one per interval (finding 9). + +Each test drives the real method from a background ``threading.Thread`` and +pumps the GUI event loop, so a queued signal is required for the effect to +appear (a thread-affine ``singleShot`` would not fire). +""" +import os +import threading +import time + +import pytest + +os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") +pytest.importorskip("PySide6.QtWidgets", exc_type=ImportError) + +import shiboken6 # noqa: E402 +from PySide6.QtCore import QEvent, QObject, QThread # noqa: E402 +from PySide6.QtWidgets import QApplication # noqa: E402 + + +# Constructing the WebRTC panel / admin-console QThread teardown inside the +# SHARED pytest process natively aborts (SIGABRT/0xC0000409) under the offscreen +# Qt platform on CI — accumulated Qt state across GUI tests corrupts on teardown. +# The product paths these cover are exercised by the full-widget build in +# test_actions_menu_gui, which is deliberately run in an isolated subprocess for +# exactly this reason. These need the same subprocess isolation before they can +# run in-process; skip until then rather than crash the whole suite. +_OFFSCREEN_SHARED_PROC_ABORT = ( + "worker->GUI teardown aborts the shared pytest process under offscreen Qt; " + "needs subprocess isolation (see test_actions_menu_gui)" +) + + +@pytest.fixture(scope="module") +def qapp(): + return QApplication.instance() or QApplication([]) + + +def _pump_until(qapp, predicate, timeout=3.0): + deadline = time.time() + timeout + while not predicate() and time.time() < deadline: + qapp.processEvents() + time.sleep(0.005) + return predicate() + + +def _run_off_thread(target): + thread = threading.Thread(target=target) + thread.start() + thread.join(3.0) + + +# --- Finding 8: LAN browse dialog ------------------------------------------ + +def test_lan_browse_services_marshaled_to_gui(qapp, monkeypatch): + import je_auto_control.utils.remote_desktop.lan_discovery as lan + # Keep the dialog hermetic: no real mDNS browser. + monkeypatch.setattr(lan, "is_discovery_available", lambda: False) + from je_auto_control.gui.remote_desktop.webrtc_dialogs import LanBrowseDialog + + dialog = LanBrowseDialog() + try: + assert dialog._table.rowCount() == 0 + services = {"h1": {"host_id": "h1", "ip": "10.0.0.1", + "signaling_url": "wss://x", "name": "Host One"}} + _run_off_thread(lambda: dialog._update_services(services)) + assert _pump_until(qapp, lambda: dialog._table.rowCount() == 1) + finally: + dialog.close() + dialog.deleteLater() + + +# --- Finding 8: presence roster -------------------------------------------- + +def test_presence_registry_event_marshaled_to_gui(qapp): + from je_auto_control.gui.presence_tab import PresenceTab + + tab = PresenceTab() + try: + tab._timer.stop() # only the marshaled signal may refresh the roster + tab._status.setText("SENTINEL") + _run_off_thread(lambda: tab._on_registry_event("viewer-x", None)) + assert _pump_until(qapp, lambda: tab._status.text() != "SENTINEL") + finally: + tab._timer.stop() + tab._registry.remove_listener(tab._on_registry_event) + tab.deleteLater() + + +# --- Finding 8: WebRTC file-received callback ------------------------------ + +@pytest.mark.skip(reason=_OFFSCREEN_SHARED_PROC_ABORT) +def test_panel_signals_expose_file_received(): + from je_auto_control.gui.remote_desktop.webrtc_panel import _PanelSignals + assert hasattr(_PanelSignals(), "file_received") + + +@pytest.mark.skip(reason=_OFFSCREEN_SHARED_PROC_ABORT) +def test_webrtc_received_file_marshaled_to_gui(qapp): + import types + from je_auto_control.gui.remote_desktop.webrtc_panel import ( + _PanelSignals, _WebRTCViewerPanel, + ) + + signals = _PanelSignals() + + class _Receiver(QObject): + def __init__(self): + super().__init__() + self.got = None + + def on_file(self, path): + self.got = path + + recv = _Receiver() + signals.file_received.connect(recv.on_file) + stub = types.SimpleNamespace(_signals=signals) + + _run_off_thread(lambda: _WebRTCViewerPanel._on_received_file(stub, "file-123")) + assert _pump_until(qapp, lambda: recv.got == "file-123") + + +# --- Finding 9: thumbnail poll thread is reaped on finish ------------------ + +@pytest.mark.skip(reason=_OFFSCREEN_SHARED_PROC_ABORT) +def test_thumbnail_poll_thread_is_reaped(qapp, monkeypatch, tmp_path): + import je_auto_control.gui.admin_console_tab as admin_mod + from je_auto_control.utils.admin.admin_client import AdminConsoleClient + + client = AdminConsoleClient(persist_path=tmp_path / "hosts.json") + monkeypatch.setattr(admin_mod, "default_admin_console", lambda: client) + # Don't run a real background thread: the reaping wiring is what matters, + # and this keeps the test deterministic (no timing, no dangling threads). + monkeypatch.setattr(admin_mod.QThread, "start", lambda self: None) + + tab = admin_mod.AdminConsoleTab() + try: + tab._thumb_timer.stop() + tab._refresh_thumbnails() + thread = tab._thumb_thread + assert thread is not None + assert thread in tab.findChildren(QThread) + + thread.finished.emit() # simulate the QThread finishing + assert tab._thumb_thread is None # _on_thumb_thread_done ran + # Flush the deferred deletions the finished signal scheduled. + qapp.sendPostedEvents(None, QEvent.Type.DeferredDelete.value) + # Without the deleteLater wiring the QThread would linger as a child of + # the tab, accumulating one per poll tick. + assert not shiboken6.Shiboken.isValid(thread) + assert tab.findChildren(QThread) == [] + finally: + tab.deleteLater() diff --git a/test/unit_test/headless/test_r3_mcp_connection_isolation.py b/test/unit_test/headless/test_r3_mcp_connection_isolation.py new file mode 100644 index 00000000..1f18d415 --- /dev/null +++ b/test/unit_test/headless/test_r3_mcp_connection_isolation.py @@ -0,0 +1,109 @@ +"""Round-3 regression: per-connection isolation of calls and capabilities. + +Over HTTP each peer runs on its own thread but shared server-global state +made two clients that both use JSON-RPC id 1 clobber each other's active +call context (cross-cancel), and one client's ``initialize`` overwrote +another's advertised capabilities (breaking the destructive-confirmation +gate). ``connection_scope(connection_id=...)`` now scopes both. These tests +drive the dispatcher via connection_scope directly — no sockets needed. +""" +import json +import threading +from typing import Any, Dict, Optional + +from je_auto_control.utils.mcp_server.server import MCPServer +from je_auto_control.utils.mcp_server.tools import MCPTool + + +def _toolcall(name: str, msg_id: int) -> str: + return json.dumps({ + "jsonrpc": "2.0", "id": msg_id, "method": "tools/call", + "params": {"name": name, "arguments": {}}, + }) + + +def _cancel(request_id: int) -> str: + return json.dumps({ + "jsonrpc": "2.0", "method": "notifications/cancelled", + "params": {"requestId": request_id}, + }) + + +def _initialize(caps: Dict[str, Any]) -> str: + return json.dumps({ + "jsonrpc": "2.0", "id": 1, "method": "initialize", + "params": {"capabilities": caps, "protocolVersion": "2025-06-18"}, + }) + + +class _Gate: + def __init__(self) -> None: + self.started = threading.Event() + self.proceed = threading.Event() + self.saw_cancelled: Optional[bool] = None + + +def _make_tool(name: str, gate: _Gate) -> MCPTool: + def handler(ctx): + gate.started.set() + gate.proceed.wait(timeout=3.0) + gate.saw_cancelled = ctx.cancelled + return "done" + return MCPTool( + name=name, description=name, + input_schema={"type": "object", "properties": {}}, + handler=handler, + ) + + +def _run_in_scope(server: MCPServer, conn_id: str, line: str) -> None: + with server.connection_scope(connection_id=conn_id): + server.handle_line(line) + + +def test_same_msg_id_on_two_connections_do_not_clobber_or_cross_cancel(): + gate_a, gate_b = _Gate(), _Gate() + server = MCPServer(tools=[_make_tool("a", gate_a), _make_tool("b", gate_b)]) + + thread_a = threading.Thread( + target=_run_in_scope, args=(server, "A", _toolcall("a", 1))) + thread_b = threading.Thread( + target=_run_in_scope, args=(server, "B", _toolcall("b", 1))) + thread_a.start() + thread_b.start() + assert gate_a.started.wait(timeout=2.0) + assert gate_b.started.wait(timeout=2.0) + + # Two in-flight calls sharing msg id 1 must occupy two distinct slots. + with server._calls_lock: + assert len(server._active_calls) == 2 + + # Cancelling connection A's call must not touch connection B's. + with server.connection_scope(connection_id="A"): + server.handle_line(_cancel(1)) + gate_a.proceed.set() + gate_b.proceed.set() + thread_a.join(timeout=3.0) + thread_b.join(timeout=3.0) + + assert gate_a.saw_cancelled is True + assert gate_b.saw_cancelled is False + + +def test_client_capabilities_are_scoped_per_connection(): + server = MCPServer(tools=[]) + _run_in_scope(server, "A", _initialize({"elicitation": {}})) + _run_in_scope(server, "B", _initialize({})) + + with server.connection_scope(connection_id="A"): + assert "elicitation" in server._client_capabilities + with server.connection_scope(connection_id="B"): + assert "elicitation" not in server._client_capabilities + + +def test_forget_connection_drops_per_connection_capabilities(): + server = MCPServer(tools=[]) + _run_in_scope(server, "A", _initialize({"elicitation": {}})) + server.forget_connection("A") + with server.connection_scope(connection_id="A"): + assert server._client_capabilities == {} diff --git a/test/unit_test/headless/test_r3_mcp_http_timeout.py b/test/unit_test/headless/test_r3_mcp_http_timeout.py new file mode 100644 index 00000000..d88f5cd5 --- /dev/null +++ b/test/unit_test/headless/test_r3_mcp_http_timeout.py @@ -0,0 +1,103 @@ +"""Round-3 regression: the MCP HTTP transport must not be wedged by a peer. + +The TLS handshake ran inside ``accept()`` on the single accept thread with no +bound, so one silent client blocked every other peer; and the request handler +had no read timeout, so a Content-Length underrun pinned a worker forever. +The handshake is now bounded in ``get_request`` and the handler declares a +finite ``timeout``. +""" +import datetime +import http.client +import ipaddress +import socket +import ssl +import time +from pathlib import Path + +import pytest + +from je_auto_control.utils.mcp_server import http_transport +from je_auto_control.utils.mcp_server.http_transport import ( + _MCPHttpHandler, start_mcp_http_server, +) + +cryptography = pytest.importorskip("cryptography") + +from cryptography import x509 # noqa: E402 +from cryptography.hazmat.primitives import hashes, serialization # noqa: E402 +from cryptography.hazmat.primitives.asymmetric import rsa # noqa: E402 +from cryptography.x509.oid import NameOID # noqa: E402 + + +def test_handler_declares_a_finite_request_timeout(): + # Before the fix the handler inherited BaseHTTPRequestHandler.timeout=None + # (no bound); a positive finite value is what closes a stalled body read. + assert isinstance(_MCPHttpHandler.timeout, (int, float)) + assert _MCPHttpHandler.timeout > 0 + + +def _server_ssl_context(tmp_path: Path) -> ssl.SSLContext: + key = rsa.generate_private_key(public_exponent=65537, key_size=2048) + name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "mcp-test")]) + now = datetime.datetime.now(datetime.timezone.utc) + cert = ( + x509.CertificateBuilder() + .subject_name(name).issuer_name(name) + .public_key(key.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(now - datetime.timedelta(minutes=1)) + .not_valid_after(now + datetime.timedelta(days=1)) + .add_extension( + x509.SubjectAlternativeName([ + x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")), + ]), critical=False) + .sign(private_key=key, algorithm=hashes.SHA256()) + ) + cert_path = tmp_path / "cert.pem" + key_path = tmp_path / "key.pem" + cert_path.write_bytes(cert.public_bytes(serialization.Encoding.PEM)) + key_path.write_bytes(key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption(), + )) + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) # NOSONAR python:S4423 # reason: loopback test server; PROTOCOL_TLS_SERVER negotiates modern TLS + ctx.load_cert_chain(certfile=str(cert_path), keyfile=str(key_path)) + return ctx + + +def _insecure_client_context() -> ssl.SSLContext: + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) # NOSONAR S5527 # loopback test + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE # NOSONAR S4830 # loopback self-signed test + return ctx + + +def _post_initialize(host: str, port: int, ctx: ssl.SSLContext) -> int: + body = ('{"jsonrpc":"2.0","id":1,"method":"initialize",' + '"params":{"protocolVersion":"2025-06-18","capabilities":{}}}') + conn = http.client.HTTPSConnection(host, port, context=ctx, timeout=6.0) + try: + conn.request("POST", "/mcp", body=body, + headers={"Content-Type": "application/json"}) + return conn.getresponse().status + finally: + conn.close() + + +def test_silent_tls_client_does_not_wedge_the_accept_thread(tmp_path, + monkeypatch): + monkeypatch.setattr(http_transport, "_HANDSHAKE_TIMEOUT", 0.5) + server = start_mcp_http_server( + host="127.0.0.1", port=0, ssl_context=_server_ssl_context(tmp_path)) + host, port = server.address + silent = socket.create_connection((host, port), timeout=2.0) + try: + # Let the accept thread pick up the silent socket and enter the + # (now time-boxed) handshake. Without the bound this wedges forever. + time.sleep(0.3) + status = _post_initialize(host, port, _insecure_client_context()) + assert status == 200 + finally: + silent.close() + server.stop(timeout=2.0) diff --git a/test/unit_test/headless/test_r3_mcp_live_screen.py b/test/unit_test/headless/test_r3_mcp_live_screen.py new file mode 100644 index 00000000..7e627247 --- /dev/null +++ b/test/unit_test/headless/test_r3_mcp_live_screen.py @@ -0,0 +1,70 @@ +"""Round-3 regression (lower): live-screen and subscribe hardening. + +``LiveScreenProvider`` reused a single stop event and ``.clear()``-ed it, so a +quick unsubscribe -> subscribe could restart a second broadcast thread while +the first was still looping (double notifications). ``resources/subscribe`` +checked membership and subscribed under separate lock windows, a TOCTOU that +could create and leak a duplicate provider handle. The first test is a real +regression on the fresh-event fix; the second guards the subscribe contract. +""" +import json +from typing import Any, Callable, Dict, List, Optional + +from je_auto_control.utils.mcp_server.resources import ( + LiveScreenProvider, MCPResource, ResourceProvider, +) +from je_auto_control.utils.mcp_server.server import MCPServer + + +def test_resubscribe_uses_a_fresh_stop_event(): + provider = LiveScreenProvider(poll_seconds=1.0) + handle = provider.subscribe(provider.URI, lambda: None) + old_stop = provider._stop + provider.unsubscribe(provider.URI, handle) + # Emptying the subscriber set must tell the old thread to exit. + assert old_stop.is_set() + + second = provider.subscribe(provider.URI, lambda: None) + try: + # A reused-and-cleared event would leave the old thread running; the + # fix binds each thread to its own event instead. + assert provider._stop is not old_stop + assert not provider._stop.is_set() + finally: + provider.unsubscribe(provider.URI, second) + + +class _CountingProvider(ResourceProvider): + URI = "test://sub" + + def __init__(self) -> None: + self.subscribe_calls = 0 + + def list(self) -> List[MCPResource]: + return [MCPResource(uri=self.URI, name="sub")] + + def read(self, uri: str) -> Optional[Dict[str, Any]]: + return None + + def subscribe(self, uri: str, + on_update: Callable[[], None]) -> Optional[Any]: + if uri != self.URI: + return None + self.subscribe_calls += 1 + return object() + + +def _subscribe(server: MCPServer, uri: str) -> Dict[str, Any]: + return json.loads(server.handle_line(json.dumps({ + "jsonrpc": "2.0", "id": 1, "method": "resources/subscribe", + "params": {"uri": uri}, + }))) + + +def test_duplicate_subscribe_creates_a_single_provider_handle(): + provider = _CountingProvider() + server = MCPServer(tools=[], resource_provider=provider) + assert "error" not in _subscribe(server, provider.URI) + assert "error" not in _subscribe(server, provider.URI) + # Second subscribe for a live uri must be a no-op, not a new handle. + assert provider.subscribe_calls == 1 diff --git a/test/unit_test/headless/test_r3_mcp_malformed_dispatch.py b/test/unit_test/headless/test_r3_mcp_malformed_dispatch.py new file mode 100644 index 00000000..157e2744 --- /dev/null +++ b/test/unit_test/headless/test_r3_mcp_malformed_dispatch.py @@ -0,0 +1,56 @@ +"""Round-3 regression: one malformed message must not kill the server. + +``params`` was never validated to be an object (a list ``params`` reached +handlers that call ``params.get`` and raised ``AttributeError``), and an +inbound response with a non-hashable ``id`` raised ``TypeError`` when used +as a dict key. Neither was guarded, and the stdio read loop had no net, so a +single bad line terminated the whole server. These tests drive the +dispatcher and the stdio loop directly. +""" +import io +import json + +from je_auto_control.utils.mcp_server.server import MCPServer + + +def test_request_with_non_object_params_returns_invalid_params(): + server = MCPServer(tools=[]) + decoded = json.loads(server.handle_line(json.dumps({ + "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": [1], + }))) + assert decoded["error"]["code"] == -32602 + + +def test_notification_with_non_object_params_does_not_raise(): + server = MCPServer(tools=[]) + # A list-typed params on a notification path previously reached + # _cancel_active_call([...]).get -> AttributeError, uncaught in handle_line. + result = server.handle_line(json.dumps({ + "jsonrpc": "2.0", "method": "notifications/cancelled", "params": [1], + })) + assert result is None + + +def test_inbound_response_with_non_hashable_id_is_dropped(): + server = MCPServer(tools=[]) + # {"id": [1], "result": {}} is treated as a reply to a server request; + # the id is a dict key, so a list id raised TypeError before the guard. + result = server.handle_line(json.dumps({ + "jsonrpc": "2.0", "id": [1], "result": {}, + })) + assert result is None + + +def test_stdio_loop_survives_a_malformed_line(): + server = MCPServer(tools=[]) + lines = "\n".join([ + json.dumps({"jsonrpc": "2.0", "id": [1], "result": {}}), # bad id + json.dumps({"jsonrpc": "2.0", "id": 2, "method": "ping"}), + ]) + "\n" + out = io.StringIO() + server.serve_stdio(stdin=io.StringIO(lines), stdout=out) + responses = [json.loads(line) for line in out.getvalue().splitlines() + if line.strip()] + # The ping after the malformed line proves the loop kept running. + ids = [msg.get("id") for msg in responses] + assert 2 in ids diff --git a/test/unit_test/headless/test_r3_mcp_plugin_reload.py b/test/unit_test/headless/test_r3_mcp_plugin_reload.py new file mode 100644 index 00000000..3cbd4ea7 --- /dev/null +++ b/test/unit_test/headless/test_r3_mcp_plugin_reload.py @@ -0,0 +1,66 @@ +"""Round-3 regression: plugin hot-reload must survive broken plugins. + +``_reload_file`` unregistered the file's existing tools *before* loading and +caught only ``(OSError, ImportError, SyntaxError)`` — a plugin whose module +body raised a runtime error (e.g. ``NameError``) or whose tool construction +failed killed the watcher thread and left the tools vanished. Separately, a +whitespace-only docstring made ``make_plugin_tool`` raise ``IndexError``. +""" +import time + +from je_auto_control.utils.mcp_server.plugin_watcher import PluginWatcher +from je_auto_control.utils.mcp_server.server import MCPServer +from je_auto_control.utils.mcp_server.tools.plugin_tools import make_plugin_tool + + +def _write(path, body): + import os + previous = path.stat().st_mtime if path.exists() else 0.0 + path.write_text(body, encoding="utf-8") + now = max(time.time(), previous + 1.0) + os.utime(path, (now, now)) + + +def test_broken_reload_keeps_previous_tools_and_watcher_alive(tmp_path): + plugin = tmp_path / "evolving.py" + _write(plugin, "def AC_ok():\n return 'v1'\n") + server = MCPServer(tools=[]) + watcher = PluginWatcher(server, str(tmp_path), poll_seconds=0.1) + watcher.poll_once() + assert "plugin_ac_ok" in server._tools + + # Overwrite with a module that raises at import time (NameError is not in + # the old narrow catch tuple). poll_once must not raise and must retain + # the previously-registered tool rather than dropping it. + _write(plugin, "undefined_symbol_at_module_level\n") + watcher.poll_once() + assert "plugin_ac_ok" in server._tools + + +def test_valid_reload_after_recovery_swaps_tools(tmp_path): + plugin = tmp_path / "recover.py" + _write(plugin, "def AC_thing():\n return 1\n") + server = MCPServer(tools=[]) + watcher = PluginWatcher(server, str(tmp_path), poll_seconds=0.1) + watcher.poll_once() + _write(plugin, "boom_undefined\n") # broken edit + watcher.poll_once() + _write(plugin, "def AC_thing():\n return 2\n") # fixed edit + watcher.poll_once() + assert server._tools["plugin_ac_thing"].handler() == 2 + + +def test_make_plugin_tool_handles_whitespace_only_docstring(): + def handler(): + return None + handler.__doc__ = " \n \t " + tool = make_plugin_tool("AC_ws", handler) + assert tool.description == "Plugin command 'AC_ws'." + + +def test_make_plugin_tool_uses_first_docstring_line(): + def handler(): + return None + handler.__doc__ = "First line summary.\nMore detail." + tool = make_plugin_tool("AC_doc", handler) + assert tool.description == "First line summary." diff --git a/test/unit_test/headless/test_r3_mcp_rate_limit.py b/test/unit_test/headless/test_r3_mcp_rate_limit.py new file mode 100644 index 00000000..220af20c --- /dev/null +++ b/test/unit_test/headless/test_r3_mcp_rate_limit.py @@ -0,0 +1,34 @@ +"""Round-3 regression: ac_rate_limit must honour changed rate/capacity. + +The handler used ``_RATE_LIMITERS.setdefault(name, TokenBucket(rate, +capacity))`` so the *second* call with the same name but a new rate or +capacity silently kept the original bucket. The fix rebuilds the bucket when +either parameter changes while reusing it when they do not. +""" +import uuid + +from je_auto_control.utils.mcp_server.tools._handlers import rate_limit + + +def _name() -> str: + return f"r3-{uuid.uuid4().hex}" + + +def test_changed_capacity_rebuilds_the_bucket(): + name = _name() + first = rate_limit(name, rate=1.0, capacity=1.0) + assert first["acquired"] is True # drains the single token + + # Same name, larger capacity: previously ignored (bucket still cap 1, so + # this second acquire would fail with ~0 tokens). Now it rebuilds. + second = rate_limit(name, rate=1.0, capacity=5.0) + assert second["acquired"] is True + assert second["tokens"] > 3.5 + + +def test_unchanged_params_reuse_the_same_bucket(): + name = _name() + first = rate_limit(name, rate=100.0, capacity=100.0) + second = rate_limit(name, rate=100.0, capacity=100.0) + # A reused bucket keeps draining; a rebuilt one would reset to full. + assert second["tokens"] < first["tokens"] diff --git a/test/unit_test/headless/test_r3_mcp_tool_error_containment.py b/test/unit_test/headless/test_r3_mcp_tool_error_containment.py new file mode 100644 index 00000000..b27c5c89 --- /dev/null +++ b/test/unit_test/headless/test_r3_mcp_tool_error_containment.py @@ -0,0 +1,60 @@ +"""Round-3 regression: failing tools must return an isError result. + +The tool-invocation catch tuple omitted the framework exception family +(``AutoControlException`` and its subclasses such as +``ImageNotFoundException``), ``subprocess.TimeoutExpired`` and +``sqlite3.Error``. A tool raising any of those escaped both containment +layers — over stdio the worker thread died and the client hung; over HTTP +the connection aborted. These tests drive the dispatcher directly. +""" +import json +import sqlite3 +import subprocess +from typing import Any, Dict + +import pytest + +from je_auto_control.utils.exception.exceptions import ImageNotFoundException +from je_auto_control.utils.mcp_server.server import MCPServer +from je_auto_control.utils.mcp_server.tools import MCPTool + + +def _tool(name: str, exc: Exception) -> MCPTool: + def handler(): + raise exc + return MCPTool( + name=name, description=name, + input_schema={"type": "object", "properties": {}}, + handler=handler, + ) + + +def _call(server: MCPServer, name: str) -> Dict[str, Any]: + raw = server.handle_line(json.dumps({ + "jsonrpc": "2.0", "id": 1, "method": "tools/call", + "params": {"name": name, "arguments": {}}, + })) + assert raw is not None + return json.loads(raw) + + +@pytest.mark.parametrize("exc", [ + ImageNotFoundException("not on screen"), + subprocess.TimeoutExpired(cmd="sleep", timeout=1.0), # nosemgrep: python.lang.security.audit.dangerous-subprocess-use-audit.dangerous-subprocess-use-audit # reason: exception instance for parametrize, not a subprocess call + sqlite3.OperationalError("no such table"), +]) +def test_framework_and_external_errors_become_iserror_result(exc): + name = "boom" + server = MCPServer(tools=[_tool(name, exc)]) + decoded = _call(server, name) + # A contained failure is a *result* with isError, never a JSON-RPC error + # and never an escaped exception (which would have hung the transport). + assert "result" in decoded, decoded + assert decoded["result"]["isError"] is True + assert decoded["result"]["content"][0]["type"] == "text" + + +def test_error_response_does_not_leak_as_minus_32603(): + server = MCPServer(tools=[_tool("img", ImageNotFoundException("x"))]) + decoded = _call(server, "img") + assert "error" not in decoded diff --git a/test/unit_test/headless/test_r3_mcp_validation_union.py b/test/unit_test/headless/test_r3_mcp_validation_union.py new file mode 100644 index 00000000..525650a4 --- /dev/null +++ b/test/unit_test/headless/test_r3_mcp_validation_union.py @@ -0,0 +1,51 @@ +"""Round-3 regression: schema validator must accept union ``type`` lists. + +Several tools declare a JSON-Schema union such as ``{"type": ["string", +"array"]}``. The validator used ``_TYPE_CHECKS.get(expected)`` which raised +``TypeError: unhashable type: 'list'`` for a union, so every call to those +tools failed. These tests exercise the validator directly (no Qt, no +platform backends). +""" +import pytest + +from je_auto_control.utils.mcp_server.tools._validation import ( + validate_arguments, +) + + +def _union_schema(): + return { + "type": "object", + "properties": {"key": {"type": ["string", "array"]}}, + "required": ["key"], + } + + +@pytest.mark.parametrize("value", ["a string", ["a", "list"]]) +def test_union_type_accepts_any_listed_type(value): + assert validate_arguments(_union_schema(), {"key": value}) is None + + +def test_union_type_rejects_value_matching_no_listed_type(): + message = validate_arguments(_union_schema(), {"key": 5}) + assert message is not None + assert "expected one of" in message + assert "key" in message + + +def test_optional_union_property_when_absent_is_valid(): + schema = { + "type": "object", + "properties": {"params": {"type": ["array", "object"]}}, + } + assert validate_arguments(schema, {}) is None + + +def test_union_including_null_accepts_none(): + schema = { + "type": "object", + "properties": {"pages": {"type": ["integer", "array", "null"]}}, + } + assert validate_arguments(schema, {"pages": None}) is None + assert validate_arguments(schema, {"pages": 3}) is None + assert validate_arguments(schema, {"pages": "nope"}) is not None diff --git a/test/unit_test/headless/test_r3_net_acme_client.py b/test/unit_test/headless/test_r3_net_acme_client.py new file mode 100644 index 00000000..21a21891 --- /dev/null +++ b/test/unit_test/headless/test_r3_net_acme_client.py @@ -0,0 +1,45 @@ +"""Round-3 net audit: the ACME client retries a badNonce rejection. + +RFC 8555 §6.5 requires retrying a badNonce rejection once with the fresh +nonce the server just supplied. +""" +from je_auto_control.utils.acme_v2 import client as acme + + +def _bare_client(): + """An AcmeClient with only the attributes the flow under test touches.""" + client = acme.AcmeClient.__new__(acme.AcmeClient) + client._account_key = object() + client._kid = "kid" + client._nonce = None + client._directory = {"newNonce": "https://example/nonce"} + return client + + +# --- badNonce retry ------------------------------------------------------- + +def test_signed_post_retries_once_on_bad_nonce(monkeypatch): + client = _bare_client() + client._nonce = "nonce-1" + monkeypatch.setattr( + acme, "sign_compact", + lambda **kwargs: {"protected": "p", "payload": "l", "signature": "s"}) + + responses = [ + (400, {"type": "urn:ietf:params:acme:error:badNonce"}, + {"Replay-Nonce": "nonce-2"}), + (200, {"status": "valid"}, {"Replay-Nonce": "nonce-3"}), + ] + calls: list = [] + + def fake_http(method, url, *, body=None, content_type=None, accept=None): + calls.append((method, url)) + return responses[len(calls) - 1] + + monkeypatch.setattr(client, "_http", fake_http) + + status, parsed, _headers = client._signed_post("https://acct", {"x": 1}) + + assert status == 200 + assert parsed == {"status": "valid"} + assert len(calls) == 2 # retried once with the fresh nonce diff --git a/test/unit_test/headless/test_r3_net_admin_client.py b/test/unit_test/headless/test_r3_net_admin_client.py new file mode 100644 index 00000000..cf40c112 --- /dev/null +++ b/test/unit_test/headless/test_r3_net_admin_client.py @@ -0,0 +1,72 @@ +"""Round-3 net audit: the admin address book must persist safely. + +``_save`` snapshotted the host map under the lock but wrote the file outside +it, so a stale snapshot could clobber a concurrent add_host (a lost host). The +write itself was truncate-then-write, so a crash mid-write corrupted every +stored token. The fix writes under the lock and atomically (temp + os.replace). +""" +import json +import os +import threading + +from je_auto_control.utils.admin.admin_client import AdminConsoleClient + + +def test_save_writes_under_lock(tmp_path): + """The file write must happen while ``self._lock`` is held.""" + client = AdminConsoleClient(persist_path=tmp_path / "hosts.json") + observed: dict = {} + original = client._write_atomic + + def spy(payload): + # Non-reentrant Lock: a non-blocking acquire fails iff already held. + acquired = client._lock.acquire(blocking=False) + observed["held"] = not acquired + if acquired: + client._lock.release() + return original(payload) + + client._write_atomic = spy + client.add_host("x", "https://x", "tok") + + assert observed["held"] is True + + +def test_write_is_atomic_and_cleans_up_on_failure(tmp_path, monkeypatch): + """A failed replace must leave the prior file intact and drop the temp.""" + path = tmp_path / "hosts.json" + client = AdminConsoleClient(persist_path=path) + client.add_host("keep", "https://k", "tok-keep") + good = path.read_text(encoding="utf-8") + + def boom_replace(_src, _dst): + raise OSError("disk full") + + monkeypatch.setattr(os, "replace", boom_replace) + client.add_host("second", "https://s", "tok-2") # save fails atomically + + assert path.read_text(encoding="utf-8") == good # not truncated + data = json.loads(path.read_text(encoding="utf-8")) + assert {h["label"] for h in data["hosts"]} == {"keep"} + leftovers = [name for name in os.listdir(tmp_path) + if name.startswith(".admin_hosts_")] + assert leftovers == [] # temp file cleaned up + + +def test_concurrent_add_host_does_not_lose_hosts(tmp_path): + """The last save (always a save, under the lock) persists every host.""" + path = tmp_path / "hosts.json" + client = AdminConsoleClient(persist_path=path) + + def add(index: int) -> None: + client.add_host(f"host{index}", f"https://h{index}", f"tok{index}") + + threads = [threading.Thread(target=add, args=(i,)) for i in range(20)] + for thread in threads: + thread.start() + for thread in threads: + thread.join() + + data = json.loads(path.read_text(encoding="utf-8")) + labels = {h["label"] for h in data["hosts"]} + assert labels == {f"host{i}" for i in range(20)} diff --git a/test/unit_test/headless/test_r3_net_background_loops.py b/test/unit_test/headless/test_r3_net_background_loops.py new file mode 100644 index 00000000..43e2cfee --- /dev/null +++ b/test/unit_test/headless/test_r3_net_background_loops.py @@ -0,0 +1,167 @@ +"""Round-3 net audit: background poll loops must survive per-tick failures. + +The scheduler, ACME renewal, screen observer, and popup watchdog each own a +single daemon thread. Any escaping exception is a poison pill that stops *all* +work the thread drives. These tests pin the containment at each loop's edge. +""" +import sqlite3 +import time + +import je_auto_control.wrapper.auto_control_image as image_mod +from je_auto_control.utils.exception.exceptions import ( + AutoControlActionException, AutoControlCantFindKeyException, + AutoControlKeyboardException, AutoControlScreenException, + ImageNotFoundException, +) +from je_auto_control.utils.observer.observer import ( + ScreenObserver, image_predicate, +) +from je_auto_control.utils.scheduler import scheduler as sch +from je_auto_control.utils.tls_acme import renewal +from je_auto_control.utils.watchdog.popup_watchdog import ( + PopupWatchdog, WatchdogRule, +) + + +# --- finding 4: scheduler -------------------------------------------------- + +def test_tick_survives_history_db_error(monkeypatch): + """A sqlite3.Error from start_run (outside _fire's own except) is contained.""" + class BoomHistory: + def start_run(self, *args, **kwargs): + raise sqlite3.OperationalError("database is locked") + + def finish_run(self, *args, **kwargs): + return True + + monkeypatch.setattr(sch, "default_history_store", BoomHistory()) + monkeypatch.setattr(sch, "capture_error_snapshot", lambda run_id: None) + monkeypatch.setattr(sch, "read_action_json", lambda path: []) + + scheduler = sch.Scheduler(executor=lambda actions: None, tick_seconds=0.05) + job = scheduler.add_job("s.json", interval_seconds=0.1) + job.next_run_ts = 0.0 # force it due + + scheduler._tick_once() # must not raise despite start_run exploding + + +def test_tick_survives_snapshot_error(monkeypatch): + """An AutoControlScreenException from capture_error_snapshot (in _fire's + finally) must not escape the tick.""" + class OkHistory: + def start_run(self, *args, **kwargs): + return 1 + + def finish_run(self, *args, **kwargs): + return True + + monkeypatch.setattr(sch, "default_history_store", OkHistory()) + + def boom_snapshot(run_id): + raise AutoControlScreenException("no display") + + monkeypatch.setattr(sch, "capture_error_snapshot", boom_snapshot) + monkeypatch.setattr(sch, "read_action_json", lambda path: []) + + def failing_exec(actions): + raise AutoControlActionException("bad action") + + scheduler = sch.Scheduler(executor=failing_exec, tick_seconds=0.05) + job = scheduler.add_job("s.json", interval_seconds=0.1) + job.next_run_ts = 0.0 + + scheduler._tick_once() # must not raise + + +def test_scheduler_thread_stays_alive_on_db_error(monkeypatch): + class BoomHistory: + def start_run(self, *args, **kwargs): + raise sqlite3.OperationalError("locked") + + def finish_run(self, *args, **kwargs): + return True + + monkeypatch.setattr(sch, "default_history_store", BoomHistory()) + monkeypatch.setattr(sch, "capture_error_snapshot", lambda run_id: None) + monkeypatch.setattr(sch, "read_action_json", lambda path: []) + + scheduler = sch.Scheduler(executor=lambda actions: None, tick_seconds=0.05) + scheduler.add_job("s.json", interval_seconds=0.05) + scheduler.start() + time.sleep(0.3) + try: + assert scheduler._thread.is_alive() + finally: + scheduler.stop() + + +# --- finding 5: ACME renewal ---------------------------------------------- + +def test_raising_on_failure_hook_does_not_escape_tick(tmp_path): + cert = tmp_path / "missing.pem" # missing -> renewal_due True + + def bad_renew(): + raise RuntimeError("certbot failed") + + def bad_hook(_error): + raise ValueError("alerting is down") + + scheduler = renewal.RenewalScheduler( + cert, bad_renew, on_failure=bad_hook, check_interval_s=999) + + assert scheduler.tick() is True # hook raises but must not propagate + + +def test_renewal_loop_survives_raising_tick(monkeypatch, tmp_path): + cert = tmp_path / "missing.pem" + scheduler = renewal.RenewalScheduler( + cert, lambda: None, check_interval_s=0.05) + + def boom_tick(): + raise RuntimeError("kaboom") + + monkeypatch.setattr(scheduler, "tick", boom_tick) + scheduler.start() + time.sleep(0.2) + try: + assert scheduler.is_running # thread survived the raising tick + finally: + scheduler.stop() + + +# --- finding 6: screen observer ------------------------------------------- + +def test_image_predicate_absent_does_not_kill_poll_once(monkeypatch): + def raise_not_found(*_args, **_kwargs): + raise ImageNotFoundException("absent") + + monkeypatch.setattr(image_mod, "locate_image_center", raise_not_found) + observer = ScreenObserver() + observer.add("img", image_predicate("x.png"), lambda ev, val: None) + + assert observer.poll_once() == [] # contained, no event, no raise + + +def test_handler_raising_framework_exc_is_contained(): + observer = ScreenObserver() + + def handler(_event, _value): + raise AutoControlKeyboardException("bad key") + + observer.add("appear", lambda: True, handler) + events = observer.poll_once() # handler raises but is contained + assert len(events) == 1 # transition still recorded + + +# --- finding 7: popup watchdog -------------------------------------------- + +def test_action_raising_keyboard_exc_does_not_kill_check(): + watchdog = PopupWatchdog() + + def action(): + raise AutoControlCantFindKeyException("no such key") + + watchdog.add_rule( + WatchdogRule(name="r", matcher=lambda: True, action=action)) + + assert watchdog.check_once() == 0 # rule error contained, no raise diff --git a/test/unit_test/headless/test_r3_net_chatops.py b/test/unit_test/headless/test_r3_net_chatops.py new file mode 100644 index 00000000..40c90089 --- /dev/null +++ b/test/unit_test/headless/test_r3_net_chatops.py @@ -0,0 +1,102 @@ +"""Round-3 net audit: chat-ops must reply on failure, never kill the poller. + +An in-flight run's ``None`` duration made ``/status`` raise TypeError; the +router did not catch framework/DB errors; and a transient reply failure could +re-execute an already-run command because ``last_seen_ts`` was only committed +after the whole batch. All three would silently stop ``run_forever``. +""" +import sqlite3 + +import pytest + +from je_auto_control.utils.chatops import handlers +from je_auto_control.utils.chatops.router import CommandResult, CommandRouter +from je_auto_control.utils.chatops.slack_bot import SlackBot, SlackError +from je_auto_control.utils.exception.exceptions import ( + AutoControlActionException, +) +from je_auto_control.utils.run_history.history_store import RunRecord + + +# --- finding 10a: None duration in /status -------------------------------- + +def test_cmd_status_handles_in_flight_none_duration(monkeypatch): + row = RunRecord( + id=1, source_type="scheduler", source_id="j1", script_path="s.json", + started_at=100.0, finished_at=None, status="running", error_text=None) + + class FakeStore: + def list_runs(self, limit=5): + return [row] + + monkeypatch.setattr( + "je_auto_control.utils.run_history.history_store." + "default_history_store", FakeStore()) + + result = handlers.cmd_status([], {}) # must not raise TypeError + assert "in progress" in result.text + + +# --- finding 10b: router contains framework / DB errors ------------------- + +def test_router_returns_reply_on_framework_exc(): + router = CommandRouter() + + def boom(_argv, _ctx): + raise AutoControlActionException("script blew up") + + router.register("run", boom) + result = router.dispatch("/run x") + + assert result is not None + assert result.succeeded is False + assert "run failed" in result.text + + +def test_router_returns_reply_on_db_error(): + router = CommandRouter() + + def boom(_argv, _ctx): + raise sqlite3.OperationalError("database is locked") + + router.register("status", boom) + result = router.dispatch("/status") + + assert result is not None + assert result.succeeded is False + + +# --- finding 10c: slack_bot commits last_seen_ts per message -------------- + +def test_last_seen_ts_committed_before_reply(monkeypatch): + """A reply-post failure must not cause the command to run twice.""" + executed: list = [] + router = CommandRouter() + + def do_run(_argv, _ctx): + executed.append(1) + return CommandResult(text="ran") + + router.register("run", do_run) + bot = SlackBot(token="xoxb-test", channel_id="C1", router=router) + + # Slack returns newest-first; only the older message ("1") is reached + # before the reply fails. + monkeypatch.setattr(bot, "_fetch_messages", lambda: [ + {"ts": "2", "text": "/run a", "user": "U1"}, + {"ts": "1", "text": "/run b", "user": "U1"}, + ]) + monkeypatch.setattr(bot, "_is_self", lambda msg: False) + + def failing_post(_text, *, thread_ts=""): + raise SlackError("reply failed") + + monkeypatch.setattr(bot, "post_message", failing_post) + + with pytest.raises(SlackError): + bot.poll_once() + + # The command ran once and its ts was committed *before* the reply, so the + # next poll (Slack's `oldest` is exclusive) will not re-run it. + assert executed == [1] + assert bot.last_seen_ts == "1" diff --git a/test/unit_test/headless/test_r3_net_socket_rest.py b/test/unit_test/headless/test_r3_net_socket_rest.py new file mode 100644 index 00000000..06021c18 --- /dev/null +++ b/test/unit_test/headless/test_r3_net_socket_rest.py @@ -0,0 +1,150 @@ +"""Round-3 net audit: socket + REST request handlers must answer, not drop. + +The TCP command server truncated any command that spanned more than one TCP +read and killed its handler thread (leaving the client with a bare EOF) on a +framework error. The REST dispatcher dropped the connection on a sqlite3.Error +from a DB-backed handler. Both must instead reassemble the request and always +send a well-formed response. +""" +import sqlite3 + +from je_auto_control.utils.exception.exceptions import ( + AutoControlActionException, AutoControlScreenException, +) +from je_auto_control.utils.socket_server import ( + auto_control_socket_server as ss, +) +from je_auto_control.utils.rest_api import rest_server as rs + + +class _FakeRequest: + """Minimal socket stand-in: canned recv chunks + captured sendall bytes.""" + + def __init__(self, chunks) -> None: + self._chunks = list(chunks) + self.sent: list = [] + + def settimeout(self, _timeout) -> None: + """No-op: real sockets expose this; the handler sets a read timeout.""" + + def recv(self, _bufsize) -> bytes: + if self._chunks: + return self._chunks.pop(0) + return b"" # EOF + + def sendall(self, data) -> None: + self.sent.append(data) + + +# --- finding 8: socket server --------------------------------------------- + +def test_read_command_reassembles_large_message(): + """A >8 KiB command spread across TCP reads must not be truncated.""" + payload = "x" * 20000 + chunks = [payload[i:i + 8192].encode("utf-8") + for i in range(0, len(payload), 8192)] + chunks.append(b"\n") # terminator arrives in a later read + request = _FakeRequest(chunks) + + result = ss._read_command(request) + + assert result == payload # full 20 KiB, not the first 8192 bytes + + +def test_handle_sends_error_and_terminator_on_framework_exc(monkeypatch): + """An unknown-command framework error must reach the client, not kill the + handler thread with no reply.""" + def boom(_payload): + raise AutoControlActionException("unknown command") + + monkeypatch.setattr(ss, "execute_action", boom) + + handler = ss.TCPServerHandler.__new__(ss.TCPServerHandler) + request = _FakeRequest([b'{"AC_bogus": {}}\n']) + handler.request = request + handler.server = object() + + handler.handle() # must not raise + + joined = b"".join(request.sent) + assert b"Return_Data_Over_JE" in joined # terminator always sent + assert b"unknown command" in joined # the error text reached the client + + +# --- finding 9: REST dispatcher ------------------------------------------- + +class _Gate: + def check(self, **_kwargs) -> str: + return "ok" + + +class _Metrics: + def __init__(self) -> None: + self.requests: list = [] + + def record_request(self, method, path, status) -> None: + self.requests.append((method, path, status)) + + def record_failed_auth(self) -> None: + pass + + +class _Audit: + def __init__(self) -> None: + self.logs: list = [] + + def log(self, *args, **kwargs) -> None: + self.logs.append((args, kwargs)) + + +def _make_rest_handler(): + handler = rs._RestRequestHandler.__new__(rs._RestRequestHandler) + + class _Server: + pass + + server = _Server() + server.auth_gate = _Gate() + server.metrics = _Metrics() + server.audit_log = _Audit() + handler.server = server + handler.client_address = ("127.0.0.1", 5555) + handler.headers = {} + handler.path = "/history" + + sent: dict = {} + + def fake_send_json(payload, status=200, default=None): + sent["payload"] = payload + sent["status"] = status + + handler._send_json = fake_send_json + return handler, server, sent + + +def test_dispatch_contains_framework_family(): + """The reparent makes AutoControlException contain the whole family.""" + handler, server, sent = _make_rest_handler() + + def boom(_ctx): + raise AutoControlScreenException("no display") + + handler._dispatch("GET", {"/history": boom}, body=None) + + assert sent["status"] == 500 + assert sent["payload"] == {"error": "handler crashed"} + assert any(status == 500 for _, _, status in server.metrics.requests) + assert server.audit_log.logs # failure was audited + + +def test_dispatch_contains_sqlite_error(): + """A DB error from a handler must return 500, not drop the connection.""" + handler, server, sent = _make_rest_handler() + + def boom(_ctx): + raise sqlite3.OperationalError("database is locked") + + handler._dispatch("GET", {"/history": boom}, body=None) + + assert sent["status"] == 500 + assert any(status == 500 for _, _, status in server.metrics.requests) diff --git a/test/unit_test/headless/test_r3_net_triggers.py b/test/unit_test/headless/test_r3_net_triggers.py new file mode 100644 index 00000000..221e0058 --- /dev/null +++ b/test/unit_test/headless/test_r3_net_triggers.py @@ -0,0 +1,159 @@ +"""Round-3 net audit: trigger poll loops must contain framework exceptions. + +Every framework error now subclasses ``AutoControlException``. These triggers +reach out to the screen / a script file — all of which raise members of that +family in the *normal* course of polling (image not on screen yet, script +renamed). Before the fix their catch tuples missed the base, so the offender +was permanently disabled (image/pixel/window triggers), a bad email re-fired +forever, or an HTTP webhook client got a dropped connection. +""" +import time + +import pytest + +import je_auto_control.wrapper.auto_control_image as image_mod +import je_auto_control.wrapper.auto_control_screen as screen_mod +import je_auto_control.wrapper.auto_control_window as window_mod +from je_auto_control.utils.exception.exceptions import ( + AutoControlActionException, AutoControlJsonActionException, + AutoControlScreenException, ImageNotFoundException, +) +from je_auto_control.utils.triggers import email_trigger as et +from je_auto_control.utils.triggers import webhook_server as ws +from je_auto_control.utils.triggers.trigger_engine import ( + ImageAppearsTrigger, PixelColorTrigger, TriggerEngine, WindowAppearsTrigger, +) + + +class _FakeHistory: + """Records finish_run status so a test can assert OK vs ERROR.""" + + def __init__(self) -> None: + self.finished: list = [] + + def start_run(self, *args, **kwargs) -> int: + return 1 + + def finish_run(self, run_id, status, error_text, artifact_path=None) -> bool: + self.finished.append((run_id, status, error_text)) + return True + + +# --- finding 1: image / pixel / window triggers ---------------------------- + +def test_image_trigger_returns_false_when_image_absent(monkeypatch): + """ImageNotFoundException on an absent template is the normal poll case.""" + def raise_not_found(*_args, **_kwargs): + raise ImageNotFoundException("absent") + + monkeypatch.setattr(image_mod, "locate_image_center", raise_not_found) + trigger = ImageAppearsTrigger( + trigger_id="img", script_path="s.json", image_path="x.png") + assert trigger.is_fired() is False # must not raise + + +def test_pixel_trigger_returns_false_on_screen_exception(monkeypatch): + def raise_screen(*_args, **_kwargs): + raise AutoControlScreenException("no display") + + monkeypatch.setattr(screen_mod, "get_pixel", raise_screen) + trigger = PixelColorTrigger( + trigger_id="px", script_path="s.json", x=1, y=1) + assert trigger.is_fired() is False + + +def test_window_trigger_returns_false_on_framework_exception(monkeypatch): + def raise_action(*_args, **_kwargs): + raise AutoControlActionException("backend blew up") + + monkeypatch.setattr(window_mod, "find_window", raise_action) + trigger = WindowAppearsTrigger( + trigger_id="win", script_path="s.json", title_substring="Save") + assert trigger.is_fired() is False + + +def test_engine_keeps_image_trigger_enabled_when_image_absent(monkeypatch): + """End-to-end: a normally-absent image must not disable the trigger.""" + def raise_not_found(*_args, **_kwargs): + raise ImageNotFoundException("absent") + + monkeypatch.setattr(image_mod, "locate_image_center", raise_not_found) + engine = TriggerEngine(executor=lambda actions: None, tick_seconds=0.05) + engine.add(ImageAppearsTrigger( + trigger_id="img", script_path="s.json", image_path="x.png", + repeat=True)) + engine.start() + time.sleep(0.3) + try: + assert engine._thread.is_alive() + assert engine._triggers["img"].enabled is True + finally: + engine.stop() + + +# --- finding 2: email trigger --------------------------------------------- + +def test_decode_header_unknown_charset_does_not_raise(): + """An unknown charset raises LookupError from the codec lookup.""" + from email.header import decode_header, make_header + poisoned = "=?x-unknown-charset?B?SGVsbG8=?=" + # Prove the input genuinely triggers the LookupError path the old + # `except ValueError` could not catch. + with pytest.raises(LookupError): + str(make_header(decode_header(poisoned))) # NOSONAR python:S2201 # reason: called for its raising side effect (proves the LookupError path) + # The helper must swallow it and fall back to the raw value. + assert et._decode_header_value(poisoned) == poisoned + + +def test_email_fire_marks_uid_seen_and_records_error_on_missing_script( + monkeypatch): + """A missing script must record STATUS_ERROR and stop the uid re-firing.""" + fake_hist = _FakeHistory() + monkeypatch.setattr(et, "default_history_store", fake_hist) + monkeypatch.setattr(et, "capture_error_snapshot", lambda run_id: None) + + def missing_script(_path): + raise AutoControlJsonActionException("script gone") + + monkeypatch.setattr(et, "read_action_json", missing_script) + monkeypatch.setattr(et, "_fetch_message", lambda client, uid: object()) + monkeypatch.setattr(et, "_build_payload", + lambda uid, msg: {"email.uid": uid}) + monkeypatch.setattr(et, "_mark_seen", lambda client, uid: None) + + watcher = et.EmailTriggerWatcher( + executor=lambda actions, variables: None) + trigger = et.EmailTrigger( + trigger_id="t1", host="h", username="u", password="p", + script_path="missing.json") + + fired = watcher._fire_for_uid(client=object(), trigger=trigger, uid=b"42") + + assert fired == 1 + assert "42" in trigger._seen_uids # processed -> no infinite re-fire + assert fake_hist.finished, "a run must have been finished" + assert fake_hist.finished[-1][1] == "error" # not a bogus STATUS_OK + assert trigger.last_error is not None + + +# --- finding 3: webhook server -------------------------------------------- + +def test_webhook_fire_records_failure_and_returns_when_script_raises( + monkeypatch): + """A framework error must set last_status=500 and let fire() return.""" + fake_hist = _FakeHistory() + monkeypatch.setattr(ws, "default_history_store", fake_hist) + monkeypatch.setattr(ws, "capture_error_snapshot", lambda run_id: None) + monkeypatch.setattr(ws, "read_action_json", lambda path: [{"AC_x": {}}]) + + def boom(actions, variables): + raise AutoControlActionException("unknown command") + + server = ws.WebhookTriggerServer(executor=boom) + trigger = server.add("/hook", "script.json") + + run_id = server.fire(trigger, {"webhook.method": "POST"}) # must not raise + + assert run_id is not None + assert trigger.last_status == 500 + assert fake_hist.finished[-1][1] == "error" diff --git a/test/unit_test/headless/test_r3_platform_linux.py b/test/unit_test/headless/test_r3_platform_linux.py new file mode 100644 index 00000000..34577638 --- /dev/null +++ b/test/unit_test/headless/test_r3_platform_linux.py @@ -0,0 +1,106 @@ +"""Round-3 platform regression tests: Linux (Wayland + X11) backends. + +Headless — no real input is dispatched and no X server is contacted. The +Wayland helpers are pure bit math; the X11 ``get_pixel`` decode is driven +with a synthetic pixel buffer via ``sys.modules`` stubs so the Linux-only +module imports on any host. + +Covers audit findings: +* #3 Wayland ``press_mouse`` must emit a button-down edge only (hold), + ``release_mouse`` an up edge only. +* #4 X11 ``get_pixel`` must return true ``(R, G, B)``, not raw BGR bytes. +* #6 Wayland listener must expose ``check_key_is_press`` so the + critical-exit watcher does not ``AttributeError``. +""" +import importlib +import sys +import types + +import je_auto_control # noqa: F401 # load the facade under the real platform first + +_DOWN_BIT = 0x40 +_UP_BIT = 0x80 + + +def test_finding3_wayland_press_is_down_edge_only(): + """press -> down-bit set / up-bit clear; release -> up-bit set / down clear.""" + from je_auto_control.linux_wayland import mouse as wayland_mouse + + buttons = ( + wayland_mouse.wayland_mouse_left, + wayland_mouse.wayland_mouse_middle, + wayland_mouse.wayland_mouse_right, + ) + for button in buttons: + press = wayland_mouse._press_code(button) + release = wayland_mouse._release_code(button) + # press holds the button: down edge only. + assert press & _DOWN_BIT + assert not press & _UP_BIT + # release lifts the button: up edge only. + assert release & _UP_BIT + assert not release & _DOWN_BIT + # the button selector (low nibble) is preserved in both. + assert press & 0x0F == button & 0x0F + assert release & 0x0F == button & 0x0F + + # Concretely for left (0xC0 = down|up of button 0). + assert wayland_mouse._press_code(0xC0) == 0x40 + assert wayland_mouse._release_code(0xC0) == 0x80 + + +def test_finding6_wayland_listener_has_check_key_is_press(): + """The name must exist (best-effort False) so callers degrade, not crash.""" + from je_auto_control.linux_wayland import listener + + assert hasattr(listener, "check_key_is_press") + assert "check_key_is_press" in listener.__all__ + # positional (critical-exit) and keyword (wrapper) call styles both work. + assert listener.check_key_is_press(65) is False + assert listener.check_key_is_press(keycode=65) is False + + +def test_finding4_x11_get_pixel_returns_rgb(monkeypatch): + """A little-endian BGRX buffer must decode to (R, G, B).""" + monkeypatch.setattr(sys, "platform", "linux") + + fake_xlib = types.ModuleType("Xlib") + fake_xlib.X = types.SimpleNamespace(ZPixmap=2) + monkeypatch.setitem(sys.modules, "Xlib", fake_xlib) + + class _FakeImage: + # bytes on the wire: Blue, Green, Red, unused. + data = bytes([0x10, 0x20, 0x30, 0xFF]) + + class _FakeRoot: + def get_image(self, *_args, **_kwargs): + return _FakeImage() + + class _FakeScreen: + root = _FakeRoot() + width_in_pixels = 100 + height_in_pixels = 100 + + class _FakeDisplay: + def screen(self): + return _FakeScreen() + + display_mod = types.ModuleType("x11_linux_display") + display_mod.display = _FakeDisplay() + monkeypatch.setitem( + sys.modules, + "je_auto_control.linux_with_x11.core.utils.x11_linux_display", + display_mod, + ) + monkeypatch.delitem( + sys.modules, + "je_auto_control.linux_with_x11.screen.x11_linux_screen", + raising=False, + ) + + screen_mod = importlib.import_module( + "je_auto_control.linux_with_x11.screen.x11_linux_screen" + ) + # Blue=0x10, Green=0x20, Red=0x30 -> (R, G, B) = (0x30, 0x20, 0x10). + assert screen_mod.get_pixel(0, 0) == (0x30, 0x20, 0x10) + assert screen_mod._decode_pixel(bytes([1, 2, 3, 4])) == (3, 2, 1) diff --git a/test/unit_test/headless/test_r3_platform_scroll_guard.py b/test/unit_test/headless/test_r3_platform_scroll_guard.py new file mode 100644 index 00000000..4b1f85c5 --- /dev/null +++ b/test/unit_test/headless/test_r3_platform_scroll_guard.py @@ -0,0 +1,75 @@ +"""Round-3 platform regression test: scroll cursor-query guard. + +Headless — the backend ``mouse`` object and the cursor helpers are all +monkeypatched, so nothing touches real input or the screen. + +Covers audit finding #5: ``mouse_scroll(value, x, y)`` with BOTH +coordinates supplied must NOT query the cursor position (which raises +``NotImplementedError`` on Wayland); it should only be queried to fill in +a coordinate that was actually omitted. +""" +import je_auto_control # noqa: F401 # load the facade under the real platform first +from je_auto_control.wrapper import auto_control_mouse as acm + + +class _FakeMouse: + """Records scroll calls; never touches real input.""" + + def __init__(self): + self.scroll_calls = 0 + + def scroll(self, *_args, **_kwargs): + self.scroll_calls += 1 + + +def _install_common(monkeypatch, get_position): + """Wire the module globals to fakes and return a call counter dict.""" + calls = {"set_pos": 0} + fake_mouse = _FakeMouse() + monkeypatch.setattr(acm, "get_mouse_position", get_position) + monkeypatch.setattr(acm, "screen_size", lambda: (1920, 1080)) + monkeypatch.setattr(acm, "mouse", fake_mouse) + monkeypatch.setattr(acm, "special_mouse_keys_table", {}, raising=False) + + def _set_pos(x, y): + calls["set_pos"] += 1 + return x, y + + monkeypatch.setattr(acm, "set_mouse_position", _set_pos) + calls["mouse"] = fake_mouse + return calls + + +def test_finding5_both_coords_do_not_query_cursor(monkeypatch): + """With x and y both given, the cursor must never be queried.""" + queried = {"count": 0} + + def _must_not_query(): + queried["count"] += 1 + raise RuntimeError("cursor query must not happen when both coords given") + + calls = _install_common(monkeypatch, _must_not_query) + + # Before the fix this raised RuntimeError (cursor queried unconditionally). + acm.mouse_scroll(5, x=100, y=200) + + assert queried["count"] == 0 + assert calls["set_pos"] == 1 + assert calls["mouse"].scroll_calls == 1 + + +def test_finding5_missing_coord_still_queries_cursor(monkeypatch): + """When a coordinate is omitted the cursor IS queried to fill it in.""" + queried = {"count": 0} + + def _query(): + queried["count"] += 1 + return (7, 8) + + calls = _install_common(monkeypatch, _query) + + acm.mouse_scroll(5, x=None, y=200) + + assert queried["count"] == 1 + assert calls["set_pos"] == 1 + assert calls["mouse"].scroll_calls == 1 diff --git a/test/unit_test/headless/test_r3_platform_windows_mouse.py b/test/unit_test/headless/test_r3_platform_windows_mouse.py new file mode 100644 index 00000000..4e38ffe3 --- /dev/null +++ b/test/unit_test/headless/test_r3_platform_windows_mouse.py @@ -0,0 +1,84 @@ +"""Round-3 platform regression tests: Windows mouse backend. + +Headless — no real mouse/keyboard input is dispatched. The backend layer +(``_send_stroke`` / ``PostMessageW``) is monkeypatched or exercised only +through pure helpers, so nothing touches the OS input APIs. + +Covers audit findings: +* #1 ``mouse_keys_table`` must be built from the *selected* backend. +* #2 Interception ``scroll`` must scale a notch count by ``WHEEL_DELTA``. +* #7 ``send_mouse_event_to_window`` must post WM_* messages, not the + SendInput button tuple. +* #8 ``SendInput.argtypes`` typo (``arg_types``) must be corrected. +""" +import ctypes +import sys +from ctypes import wintypes + +import pytest + +pytestmark = pytest.mark.skipif( + sys.platform not in ("win32", "cygwin", "msys"), + reason="Windows input backend modules only import on Windows.", +) + + +def test_finding1_mouse_keys_table_built_from_selected_backend(): + """The table reflects the selected backend's flag tuples, not the + import-time SendInput globals.""" + from je_auto_control.wrapper import _platform_windows as pw + from je_auto_control.windows.interception import mouse as interception_mouse + from je_auto_control.windows.mouse import win32_ctype_mouse_control as sendinput_mouse + + table = pw._build_mouse_keys_table(interception_mouse) + # Built from the module handed in — Interception constants, not SendInput. + assert table["mouse_left"] == interception_mouse.win32_mouse_left + assert table["mouse_right"] == interception_mouse.win32_mouse_right + # Interception bitmasks differ from SendInput dwFlags: proves the + # distinction the bug erased. + assert interception_mouse.win32_mouse_left != sendinput_mouse.win32_mouse_left + assert table["mouse_left"] != sendinput_mouse.win32_mouse_left + # The live module table matches whatever backend it actually selected. + assert pw.mouse_keys_table["mouse_left"] == pw.mouse.win32_mouse_left + + +def test_finding2_interception_scroll_scales_by_wheel_delta(monkeypatch): + """One notch must move one notch: rolling == value * 120.""" + from je_auto_control.windows.interception import mouse as interception_mouse + + captured = {} + + def _fake_send(state, *, flags=0, x=0, y=0, rolling=0): + captured["state"] = state + captured["rolling"] = rolling + + monkeypatch.setattr(interception_mouse, "_send_stroke", _fake_send) + interception_mouse.scroll(3) + assert captured["state"] == interception_mouse.MOUSE_WHEEL + assert captured["rolling"] == 3 * 120 + + +def test_finding7_window_message_translation_not_raw_tuple(): + """The window path resolves a button tuple into integer WM_* messages.""" + from je_auto_control.utils.exception.exceptions import AutoControlException + from je_auto_control.windows.mouse import win32_ctype_mouse_control as sendinput_mouse + + down, up = sendinput_mouse._resolve_window_messages(sendinput_mouse.win32_mouse_left) + assert down == (sendinput_mouse.WM_LBUTTONDOWN, sendinput_mouse.MK_LBUTTON) + assert up == (sendinput_mouse.WM_LBUTTONUP, 0) + # A window message is an int — never the SendInput dwFlags tuple. + assert isinstance(down[0], int) + assert down[0] != sendinput_mouse.win32_mouse_left + + right_down, _ = sendinput_mouse._resolve_window_messages(sendinput_mouse.win32_mouse_right) + assert right_down[0] == sendinput_mouse.WM_RBUTTONDOWN + + with pytest.raises(AutoControlException): + sendinput_mouse._resolve_window_messages((9, 9, 9)) + + +def test_finding8_sendinput_argtypes_applied(): + """The ``argtypes`` (not ``arg_types``) attribute must be set.""" + from je_auto_control.windows.core.utils import win32_ctype_input as mod + + assert mod.user32.SendInput.argtypes == (wintypes.UINT, ctypes.c_void_p, ctypes.c_int) diff --git a/test/unit_test/headless/test_r3_rdusb_acl.py b/test/unit_test/headless/test_r3_rdusb_acl.py new file mode 100644 index 00000000..a3da0260 --- /dev/null +++ b/test/unit_test/headless/test_r3_rdusb_acl.py @@ -0,0 +1,45 @@ +"""Audit round 3 regression for the USB passthrough ACL (finding 11). + +vid/pid are hex strings and must compare case-insensitively; otherwise a rule +written in one case silently fails to match a device reporting the other case, +which bypasses a DENY rule when the default policy is "allow". +""" +from je_auto_control.utils.usb.passthrough.acl import AclRule, UsbAcl + + +def test_uppercase_deny_rule_matches_lowercase_device(tmp_path): + acl = UsbAcl(path=tmp_path / "acl.json", default_policy="allow") + acl.add_rule( + AclRule(vendor_id="1D6B", product_id="0002", allow=False), + persist=False, + ) + # Device reports lowercase hex; the uppercase DENY rule must still apply. + assert acl.decide(vendor_id="1d6b", product_id="0002", + serial=None) == "deny" + + +def test_lowercase_deny_rule_matches_uppercase_device(tmp_path): + acl = UsbAcl(path=tmp_path / "acl.json", default_policy="allow") + acl.add_rule( + AclRule(vendor_id="1d6b", product_id="0002", allow=False), + persist=False, + ) + assert acl.decide(vendor_id="1D6B", product_id="0002", + serial=None) == "deny" + + +def test_non_matching_vid_still_falls_through_to_default(tmp_path): + acl = UsbAcl(path=tmp_path / "acl.json", default_policy="allow") + acl.add_rule( + AclRule(vendor_id="1D6B", product_id="0002", allow=False), + persist=False, + ) + # A genuinely different device is unaffected by the rule. + assert acl.decide(vendor_id="dead", product_id="beef", + serial=None) == "allow" + + +def test_rule_matches_predicate_is_case_insensitive(): + rule = AclRule(vendor_id="ABCD", product_id="00FF") + assert rule.matches(vendor_id="abcd", product_id="00ff", serial=None) + assert rule.matches(vendor_id="ABCD", product_id="00FF", serial=None) diff --git a/test/unit_test/headless/test_r3_rdusb_host.py b/test/unit_test/headless/test_r3_rdusb_host.py new file mode 100644 index 00000000..8dace307 --- /dev/null +++ b/test/unit_test/headless/test_r3_rdusb_host.py @@ -0,0 +1,102 @@ +"""Audit round 3 regressions for RemoteDesktopHost (loopback sockets only). + +Covers: +* Finding 1 — dead handlers must be reaped *before* the max_clients check, + otherwise a full table of disconnected handlers rejects every new viewer + forever. +* Finding 3 — the initial cursor + frame are sent from the per-client sender + thread, not the shared accept thread, so a slow-reading viewer cannot block + new connections. +""" +import threading +import time + +from je_auto_control.utils.remote_desktop import ( + RemoteDesktopHost, RemoteDesktopViewer, +) +from je_auto_control.utils.remote_desktop.host import _ClientHandler + + +def _wait_until(predicate, timeout: float = 5.0, + interval: float = 0.02) -> bool: + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + if predicate(): + return True + time.sleep(interval) + return predicate() + + +class _DeadHandler: + """Stand-in for a client whose viewer already disconnected.""" + + def __init__(self) -> None: + self._shutdown = threading.Event() + self._shutdown.set() + self.authenticated = False + + def stop(self) -> None: + pass + + def _close(self) -> None: + pass + + +def _make_host(**kwargs) -> RemoteDesktopHost: + params = dict( + token="tok", bind="127.0.0.1", port=0, + frame_provider=lambda: b"x", + input_dispatcher=lambda message: None, + enable_cursor_broadcast=False, + ) + params.update(kwargs) + return RemoteDesktopHost(**params) + + +def test_reap_runs_before_capacity_check_admits_new_viewer(): + """A table full of dead handlers must not permanently reject new viewers.""" + host = _make_host(max_clients=1) + host.start() + try: + # Seed a dead handler so the (max_clients==1) table looks "full". + with host._clients_lock: + host._clients.append(_DeadHandler()) + viewer = RemoteDesktopViewer("127.0.0.1", host.port, "tok") + viewer.connect(timeout=5.0) + try: + assert _wait_until(lambda: host.connected_clients == 1) + finally: + viewer.disconnect(timeout=2.0) + finally: + host.stop(timeout=2.0) + + +def test_slow_viewer_initial_frame_does_not_block_accept(monkeypatch): + """Blocking the initial frame send must stall only that viewer's thread.""" + block = threading.Event() + + def _blocking_initial_frame(self) -> None: + # Emulate a viewer that authenticated then stopped reading: the + # initial full-screen frame send parks here until released. + block.wait(timeout=5.0) + + monkeypatch.setattr( + _ClientHandler, "_send_initial_frame", _blocking_initial_frame, + ) + host = _make_host(max_clients=4) + host.start() + viewers = [] + try: + for _ in range(2): + viewer = RemoteDesktopViewer("127.0.0.1", host.port, "tok") + # Pre-fix the accept thread blocks inside viewer #1's initial + # frame send, so viewer #2 never receives AUTH_CHALLENGE and this + # connect() raises on timeout. + viewer.connect(timeout=3.0) + viewers.append(viewer) + assert _wait_until(lambda: host.connected_clients == 2) + finally: + block.set() + for viewer in viewers: + viewer.disconnect(timeout=2.0) + host.stop(timeout=2.0) diff --git a/test/unit_test/headless/test_r3_rdusb_hotkey.py b/test/unit_test/headless/test_r3_rdusb_hotkey.py new file mode 100644 index 00000000..da09f163 --- /dev/null +++ b/test/unit_test/headless/test_r3_rdusb_hotkey.py @@ -0,0 +1,68 @@ +"""Audit round 3 regression for the Linux X11 hotkey grab (finding 12). + +_grab_masked registers a hotkey under several lock-mask variants. If a later +variant fails it must roll back the ones that already succeeded, otherwise the +grab leaks (and _registered is never updated, so every poll re-grabs and spams +BadAccess). + +Xlib is not installed on the test host, so a minimal fake module is injected. +""" +import sys +import types + +from je_auto_control.utils.hotkey.backends.linux_backend import ( + LinuxHotkeyBackend, +) + + +def _install_fake_xlib(monkeypatch) -> None: + fake_x = types.SimpleNamespace( + GrabModeAsync=1, Mod2Mask=0x10, LockMask=0x02, + ) + fake_xlib = types.ModuleType("Xlib") + fake_xlib.X = fake_x + monkeypatch.setitem(sys.modules, "Xlib", fake_xlib) + + +def test_grab_masked_rolls_back_on_partial_failure(monkeypatch): + _install_fake_xlib(monkeypatch) + binding = types.SimpleNamespace(combo="ctrl+a", binding_id="b1") + grab_masks = [] + ungrab_masks = [] + + class _Root: + def grab_key(self, keycode, mask, *_args): + grab_masks.append(mask) + if len(grab_masks) == 3: # variants: 0, Mod2, Lock, Mod2|Lock + raise RuntimeError("BadAccess") + + def ungrab_key(self, keycode, mask): + ungrab_masks.append(mask) + + ok = LinuxHotkeyBackend._grab_masked( + _Root(), binding, mask=0x04, keycode=38, + ) + + assert ok is False + assert grab_masks == [0x04, 0x14, 0x06] # base | {0, Mod2, Lock} + # The two grabs that succeeded (extra masks 0 and Mod2) are rolled back. + assert ungrab_masks == [0x04, 0x14] + + +def test_grab_masked_returns_true_when_all_variants_succeed(monkeypatch): + _install_fake_xlib(monkeypatch) + binding = types.SimpleNamespace(combo="ctrl+a", binding_id="b1") + ungrab_masks = [] + + class _Root: + def grab_key(self, keycode, mask, *_args): + pass + + def ungrab_key(self, keycode, mask): + ungrab_masks.append(mask) + + ok = LinuxHotkeyBackend._grab_masked( + _Root(), binding, mask=0x04, keycode=38, + ) + assert ok is True + assert ungrab_masks == [] # nothing rolled back on full success diff --git a/test/unit_test/headless/test_r3_rdusb_input.py b/test/unit_test/headless/test_r3_rdusb_input.py new file mode 100644 index 00000000..3fdd364c --- /dev/null +++ b/test/unit_test/headless/test_r3_rdusb_input.py @@ -0,0 +1,109 @@ +"""Audit round 3 regressions for modifier hold + key hold (findings 9, 10). + +Both primitives must guarantee that keys they press are released even when a +press part-way through fails, a release fails, or the hold is interrupted. +""" +import pytest + +from je_auto_control.utils.key_hold.key_hold import hold_key +from je_auto_control.utils.modifier_state.modifier_state import hold_modifiers + + +# --- Finding 9: hold_modifiers -------------------------------------- + +def test_hold_modifiers_releases_pressed_when_a_later_press_fails(): + events = [] + + def sink(event): + events.append((event["op"], event["key"])) + if event["op"] == "press" and event["key"] == "shift": + raise RuntimeError("second press failed") + + with pytest.raises(RuntimeError): + with hold_modifiers(["ctrl", "shift"], sink=sink): + pass + + # ctrl went down before shift's press failed, so ctrl must be released; + # shift was never pressed, so it must never be released. + assert ("press", "ctrl") in events + assert ("release", "ctrl") in events + assert ("release", "shift") not in events + + +def test_hold_modifiers_release_failure_does_not_skip_others(): + released = [] + + def sink(event): + if event["op"] == "release": + released.append(event["key"]) + if event["key"] == "shift": + raise RuntimeError("release failed") + + # Must not raise: a failing release is logged, not propagated, and the + # remaining modifier is still released. + with hold_modifiers(["ctrl", "shift"], sink=sink): + pass + + assert "shift" in released # reversed order releases shift first + assert "ctrl" in released + + +def test_hold_modifiers_happy_path_release_reversed(): + events = [] + with hold_modifiers(["ctrl", "alt"], + sink=lambda e: events.append((e["op"], e["key"]))): + pass + assert events == [ + ("press", "ctrl"), ("press", "alt"), + ("release", "alt"), ("release", "ctrl"), + ] + + +# --- Finding 10: hold_key ------------------------------------------- + +def test_hold_key_releases_on_interrupt_during_wait(): + dispatched = [] + + def _interrupt(_seconds): + raise KeyboardInterrupt() + + with pytest.raises(KeyboardInterrupt): + hold_key("a", 1.0, + sink=lambda e: dispatched.append((e["op"], e["key"])), + sleep=_interrupt) + + assert ("press", "a") in dispatched + assert ("release", "a") in dispatched + + +def test_hold_key_retries_release_when_first_release_fails(): + calls = [] + + def sink(event): + calls.append((event["op"], event["key"])) + if event["op"] == "release" and calls.count(("release", "a")) == 1: + raise RuntimeError("first release failed") + + with pytest.raises(RuntimeError): + hold_key("a", 0.01, sink=sink, sleep=lambda _s: None) + + # The finally block re-attempts the release the plan step failed to run. + assert calls.count(("release", "a")) == 2 + + +def test_hold_key_happy_path_single_release(): + calls = [] + hold_key("a", 0.01, + sink=lambda e: calls.append((e["op"], e["key"])), + sleep=lambda _s: None) + assert calls == [("press", "a"), ("release", "a")] + + +def test_hold_key_autorepeat_has_no_dangling_release(): + ops = [] + hold_key("a", 1.0, rate_hz=2.0, + sink=lambda e: ops.append(e["op"]), + sleep=lambda _s: None) + assert "press" not in ops + assert "release" not in ops + assert ops.count("key") >= 1 diff --git a/test/unit_test/headless/test_r3_rdusb_relay.py b/test/unit_test/headless/test_r3_rdusb_relay.py new file mode 100644 index 00000000..a1915d51 --- /dev/null +++ b/test/unit_test/headless/test_r3_rdusb_relay.py @@ -0,0 +1,38 @@ +"""Audit round 3 regression for the relay pipe (finding 4). + +When one side of a paired session EOFs, the opposite pipe thread is parked in a +blocking recv(); the ``stop`` flag alone cannot wake it. The fix shuts the +opposite socket down so both threads exit and both sockets are closed instead +of hanging join() forever. +""" +import socket +import threading + +from je_auto_control.utils.remote_desktop.relay import _pair_and_pump + + +def test_pair_and_pump_exits_when_one_side_closes(): + host_a, host_b = socket.socketpair() + viewer_a, viewer_b = socket.socketpair() + done = threading.Event() + + def _run() -> None: + _pair_and_pump(host_b, viewer_b) + done.set() + + threading.Thread(target=_run, daemon=True).start() + + # The host peer disconnects while the viewer side sits idle (parked in + # recv). Pre-fix, the viewer->host pipe thread never wakes and + # _pair_and_pump's join() blocks forever, so done is never set. + host_a.close() + + assert done.wait(timeout=3.0), "_pair_and_pump hung after one side EOF" + assert host_b.fileno() == -1 + assert viewer_b.fileno() == -1 + + for extra in (host_a, viewer_a): + try: + extra.close() + except OSError: + pass diff --git a/test/unit_test/headless/test_r3_rdusb_usbip.py b/test/unit_test/headless/test_r3_rdusb_usbip.py new file mode 100644 index 00000000..11920500 --- /dev/null +++ b/test/unit_test/headless/test_r3_rdusb_usbip.py @@ -0,0 +1,189 @@ +"""Audit round 3 regressions for the USB/IP server + protocol. + +Covers: +* Finding 6 — an OUT CMD_SUBMIT carrying a payload must be decoded in two + phases (peek length, read buffer, decode) so it is forwarded instead of + dropping the connection; oversized transfers are rejected before allocating. +* Finding 7 — RET_SUBMIT status is a signed __s32: a negative errno must encode + without raising struct.error (which used to kill the worker thread). +* Finding 8 — CMD_UNLINK must be answered with RET_UNLINK (0x4), not + RET_SUBMIT (0x3). +""" +import socket +import struct + +import pytest + +from je_auto_control.utils.usbip import ( + FakeUrbBackend, PROTOCOL_VERSION, OP_REQ_IMPORT, USBIP_CMD_SUBMIT, + USBIP_CMD_UNLINK, USBIP_RET_SUBMIT, USBIP_RET_UNLINK, UrbResponse, + UsbIpError, UsbIpServer, encode_ret_submit, +) +from je_auto_control.utils.usbip.protocol import ( + UsbIpDevice, UsbIpInterface, peek_transfer_length, +) +from je_auto_control.utils.usbip.server import _MAX_TRANSFER_BUFFER_BYTES + + +def _device(busid: str = "1-1") -> UsbIpDevice: + return UsbIpDevice( + path=f"/sys/devices/pci0000:00/{busid}", + busid=busid, busnum=1, devnum=2, speed=3, + vendor_id=0x046D, product_id=0xC52B, bcd_device=0x0200, + device_class=0, device_subclass=0, device_protocol=0, + configuration_value=1, num_configurations=1, num_interfaces=1, + interfaces=[UsbIpInterface(3, 0, 0)], + ) + + +def _free_port() -> int: + with socket.socket() as sock: + sock.bind(("127.0.0.1", 0)) + return sock.getsockname()[1] + + +def _recv(sock: socket.socket, n: int) -> bytes: + buf = bytearray() + while len(buf) < n: + chunk = sock.recv(n - len(buf)) + if not chunk: + break + buf.extend(chunk) + return bytes(buf) + + +def _import_device(sock: socket.socket, busid: str = "1-1") -> None: + header = struct.pack("!HHI", PROTOCOL_VERSION, OP_REQ_IMPORT, 0) + sock.sendall(header + busid.encode("ascii").ljust(32, b"\x00")) + _recv(sock, 8) # OP_REP_IMPORT header + _recv(sock, 312) # device descriptor body + + +def _cmd_submit(*, seqnum: int, devid: int, direction: int, ep: int, + transfer_length: int, buffer: bytes = b"") -> bytes: + header = struct.pack("!IIIII", USBIP_CMD_SUBMIT, seqnum, devid, + direction, ep) + body = struct.pack("!IIiII8s", 0, transfer_length, 0, 0, 0, b"\x00" * 8) + return header + body + buffer + + +# --- Finding 6 ------------------------------------------------------- + +def test_peek_transfer_length_returns_direction_and_length(): + header = struct.pack("!IIIII", USBIP_CMD_SUBMIT, 1, 2, 0, 3) + body = struct.pack("!IIiII8s", 0, 128, 0, 0, 0, b"\x00" * 8) + direction, tlen = peek_transfer_length(header, body) + assert direction == 0 + assert tlen == 128 + + +def test_out_cmd_submit_with_payload_is_forwarded(): + backend = FakeUrbBackend(devices=[_device("1-1")]) + backend.script_urb(devid=2, direction=0, ep=2, + response=UrbResponse(status=0, actual_length=0)) + server = UsbIpServer(backend, host="127.0.0.1", port=_free_port()) + server.start() + try: + sock = socket.create_connection(("127.0.0.1", server.port), + timeout=5.0) + _import_device(sock) + payload = b"hello-out-transfer" + sock.sendall(_cmd_submit(seqnum=42, devid=2, direction=0, ep=2, + transfer_length=len(payload), + buffer=payload)) + # Pre-fix the first decode raised UsbIpError and the connection was + # dropped before the URB reached the backend. + header = _recv(sock, 20) + assert len(header) == 20 + assert int.from_bytes(header[:4], "big") == USBIP_RET_SUBMIT + _recv(sock, 28) # RET_SUBMIT body + assert len(backend.received) == 1 + assert backend.received[0].transfer_buffer == payload + sock.close() + finally: + server.stop() + + +def test_oversized_transfer_buffer_is_rejected(): + backend = FakeUrbBackend(devices=[_device("1-1")]) + server = UsbIpServer(backend, host="127.0.0.1", port=_free_port()) + server.start() + try: + sock = socket.create_connection(("127.0.0.1", server.port), + timeout=5.0) + _import_device(sock) + huge = _MAX_TRANSFER_BUFFER_BYTES + 1 + # Advertise a huge OUT transfer but send no buffer: the server must + # reject on the length check (dropping the connection) rather than + # try to allocate/read it. + sock.sendall(_cmd_submit(seqnum=7, devid=2, direction=0, ep=1, + transfer_length=huge)) + assert _recv(sock, 1) == b"" # connection closed, no huge alloc + assert backend.received == [] + sock.close() + finally: + server.stop() + + +# --- Finding 7 ------------------------------------------------------- + +def test_ret_submit_encodes_negative_errno_status(): + # Pre-fix this raised struct.error (status packed as unsigned 'I'). + body = encode_ret_submit(seqnum=1, devid=1, direction=1, ep=1, + status=-19, actual_length=0) + status = struct.unpack("!i", body[20:24])[0] + assert status == -19 + + +def test_server_replies_negative_status_without_killing_worker(): + # FakeUrbBackend returns status=-19 (-ENODEV) for any unscripted URB. + backend = FakeUrbBackend(devices=[_device("1-1")]) + server = UsbIpServer(backend, host="127.0.0.1", port=_free_port()) + server.start() + try: + sock = socket.create_connection(("127.0.0.1", server.port), + timeout=5.0) + _import_device(sock) + sock.sendall(_cmd_submit(seqnum=11, devid=2, direction=1, ep=5, + transfer_length=0)) + header = _recv(sock, 20) + body = _recv(sock, 28) + # Pre-fix the worker died on struct.error and these reads got EOF. + assert len(header) == 20 and len(body) == 28 + assert int.from_bytes(header[:4], "big") == USBIP_RET_SUBMIT + assert struct.unpack("!i", body[:4])[0] == -19 + sock.close() + finally: + server.stop() + + +# --- Finding 8 ------------------------------------------------------- + +def test_cmd_unlink_replies_ret_unlink(): + backend = FakeUrbBackend(devices=[_device("1-1")]) + server = UsbIpServer(backend, host="127.0.0.1", port=_free_port()) + server.start() + try: + sock = socket.create_connection(("127.0.0.1", server.port), + timeout=5.0) + _import_device(sock) + unlink = struct.pack("!IIIII", USBIP_CMD_UNLINK, 55, 2, 0, 0) + sock.sendall(unlink + b"\x00" * 28) + header = _recv(sock, 20) + assert len(header) == 20 + command = int.from_bytes(header[:4], "big") + # Pre-fix this was USBIP_RET_SUBMIT (0x3); the client's URB-cancel + # never completed. + assert command == USBIP_RET_UNLINK + assert int.from_bytes(header[4:8], "big") == 55 # echoed seqnum + body = _recv(sock, 28) + assert len(body) == 28 + assert struct.unpack("!i", body[:4])[0] == 0 # status + sock.close() + finally: + server.stop() + + +def test_peek_transfer_length_rejects_short_input(): + with pytest.raises(UsbIpError): + peek_transfer_length(b"\x00" * 4, b"\x00" * 28) diff --git a/test/unit_test/headless/test_r3_rdusb_viewer.py b/test/unit_test/headless/test_r3_rdusb_viewer.py new file mode 100644 index 00000000..b2f7b635 --- /dev/null +++ b/test/unit_test/headless/test_r3_rdusb_viewer.py @@ -0,0 +1,91 @@ +"""Audit round 3 regressions for RemoteDesktopViewer. + +Covers: +* Finding 2 — connect() must close the raw socket when the WebSocket + handshake raises WsProtocolError (a RuntimeError subclass), not leak the fd. +* Finding 5 — disconnect() called from inside a frame/error callback (i.e. on + the receiver thread) must skip the self-join and still finish teardown. +""" +import socket +import threading + +import pytest + +from je_auto_control.utils.remote_desktop import viewer as viewer_module +from je_auto_control.utils.remote_desktop.viewer import RemoteDesktopViewer +from je_auto_control.utils.remote_desktop.ws_protocol import WsProtocolError + + +def test_connect_closes_socket_on_ws_protocol_error(monkeypatch): + """A WsProtocolError during handshake must not leak the raw socket.""" + listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + listener.bind(("127.0.0.1", 0)) + listener.listen(1) + port = listener.getsockname()[1] + held = [] + + def _accept_once() -> None: + try: + conn, _addr = listener.accept() + held.append(conn) # keep the connection open past handshake fail + except OSError: + pass + + threading.Thread(target=_accept_once, daemon=True).start() + + captured = {} + real_create = socket.create_connection + + def _spy_create(address, timeout=None): + raw = real_create(address, timeout=timeout) + captured["sock"] = raw + return raw + + monkeypatch.setattr(viewer_module.socket, "create_connection", _spy_create) + + class _WsFailViewer(RemoteDesktopViewer): + def _handshake(self, channel): + raise WsProtocolError("bad ws upgrade") + + viewer = _WsFailViewer("127.0.0.1", port, "tok") + try: + with pytest.raises(WsProtocolError): + viewer.connect(timeout=2.0) + # Pre-fix the except tuple omitted WsProtocolError, so the raw socket + # stayed open (fileno() != -1). The fix closes it. + raw = captured["sock"] + assert raw.fileno() == -1 + finally: + listener.close() + for conn in held: + try: + conn.close() + except OSError: + pass + + +def test_disconnect_from_receiver_thread_completes_teardown(): + """disconnect() on the receiver thread must not raise and must tear down.""" + viewer = RemoteDesktopViewer("127.0.0.1", 1, "tok") + viewer._channel = None # skip channel.close() + result = {} + + def _worker() -> None: + viewer._connected = True + try: + viewer.disconnect(timeout=1.0) + result["error"] = None + except RuntimeError as exc: # pre-fix: "cannot join current thread" + result["error"] = exc + + worker = threading.Thread(target=_worker) + # Make the running worker *be* the viewer's receiver thread. + viewer._receiver = worker + worker.start() + worker.join(timeout=3.0) + + assert not worker.is_alive() + assert result.get("error") is None + assert viewer._receiver is None + assert viewer.connected is False diff --git a/test/unit_test/headless/test_r3_util_flow_containment.py b/test/unit_test/headless/test_r3_util_flow_containment.py new file mode 100644 index 00000000..f56e1de8 --- /dev/null +++ b/test/unit_test/headless/test_r3_util_flow_containment.py @@ -0,0 +1,156 @@ +"""Round-3 audit regressions: execution-flow containment fixes. + +Covers findings 1 (DAG runner), 2 (callback executor), 5 (test-suite runner), +and 6 (self-healing replay). Each asserts that a framework exception raised by +an injected callable is *contained* into a result rather than crashing the +surrounding driver. Fully headless — no real input, no Qt. +""" +import types + +import pytest + +from je_auto_control.utils.exception.exceptions import ( + AutoControlActionException, AutoControlMouseException, +) + + +# --- Finding 1: DAG runner ------------------------------------------------ + +def test_run_dag_contains_framework_exception(): + from je_auto_control.utils.dag.runner import ( + STATUS_FAILED, STATUS_SKIPPED, run_dag, + ) + definition = {"nodes": [ + {"id": "a", "actions": [["AC_noop", {}]]}, + {"id": "b", "actions": [["AC_noop", {}]], "depends_on": ["a"]}, + ]} + + def boom_runner(node, _definition): + raise AutoControlActionException(f"bad command in {node.id}") + + result = run_dag(definition, local_runner=boom_runner) + assert result.succeeded is False + assert result.nodes["a"].status == STATUS_FAILED + assert "AutoControlActionException" in (result.nodes["a"].error or "") + # Downstream node is skipped (cascade), never left "running". + assert result.nodes["b"].status == STATUS_SKIPPED + + +# --- Finding 2: callback executor ---------------------------------------- + +def test_callback_failure_preserves_trigger_result(): + from je_auto_control.utils.callback.callback_function_executor import ( + CallbackFunctionExecutor, + ) + executor = CallbackFunctionExecutor() + executor.event_dict["_r3_trigger_ok"] = lambda **_kw: "TRIGGER_OK" + + def bad_callback(): + raise ValueError("callback boom") + + result = executor.callback_function("_r3_trigger_ok", bad_callback) + # A callback failure must NOT discard the already-computed trigger result. + assert result == "TRIGGER_OK" + + +def test_callback_trigger_framework_exception_returns_none(): + from je_auto_control.utils.callback.callback_function_executor import ( + CallbackFunctionExecutor, + ) + executor = CallbackFunctionExecutor() + + def boom(**_kw): + raise AutoControlMouseException("mouse fail") + + executor.event_dict["_r3_trigger_bad"] = boom + ran = [] + result = executor.callback_function( + "_r3_trigger_bad", lambda: ran.append(1)) + # Framework trigger failure is handled (not propagated) and returns None; + # the callback does not run when the trigger failed. + assert result is None + assert ran == [] + + +# --- Finding 5: test-suite runner ---------------------------------------- + +class _FakeExecutor: + """Minimal executor stand-in for run_suite.""" + + def __init__(self, on_execute=None): + self.variables = types.SimpleNamespace(set=lambda *_a, **_k: None) + self.executed = [] + self._on_execute = on_execute + + def execute_action(self, actions, raise_on_error=False): + self.executed.append(actions) + if self._on_execute is not None: + return self._on_execute(actions, raise_on_error) + return {} + + +def test_run_actions_scores_lookup_error_as_error(): + from je_auto_control.utils.test_suite.runner import run_suite + from je_auto_control.utils.test_suite.result import STATUS_ERROR + + def raise_key_error(_actions, _raise): + raise KeyError("missing arg") # LookupError, outside the old tuple + + spec = {"name": "s", "cases": [{"name": "c1", "actions": [["x"]]}]} + result = run_suite(spec, executor=_FakeExecutor(raise_key_error)) + assert len(result.cases) == 1 + assert result.cases[0].status == STATUS_ERROR + + +def test_bad_case_does_not_abort_suite_and_teardown_runs(): + from je_auto_control.utils.test_suite.runner import run_suite + from je_auto_control.utils.test_suite.result import ( + STATUS_ERROR, STATUS_PASSED, + ) + spec = { + "name": "s", + "cases": [ + {"name": "bad", + "data": {"kind": "csv", "path": "/no/such/dir/missing.csv"}, + "actions": [["x"]]}, + {"name": "good", "actions": []}, + ], + "teardown": [["td"]], + } + executor = _FakeExecutor() + result = run_suite(spec, executor=executor) + by_name = {case.name: case.status for case in result.cases} + # The malformed (unexpandable) case scores error but does not kill the run. + assert by_name["bad"] == STATUS_ERROR + assert by_name["good"] == STATUS_PASSED + # Teardown always runs, even though a case blew up during expansion. + assert [["td"]] in executor.executed + + +# --- Finding 6: self-healing replay -------------------------------------- + +def test_self_healing_replay_contains_framework_exception(): + from je_auto_control.utils.semantic_recording.self_healing import ( + SelfHealingReplayer, + ) + + def execute(_action): + raise AutoControlActionException("boom") + + replayer = SelfHealingReplayer(execute, max_retries=1) + result = replayer.replay([{"action": "mouse_click", "x": 1, "y": 2}]) + assert result.succeeded is False + assert result.steps[0].success is False + assert "AutoControlActionException" in (result.steps[0].last_error or "") + + +def test_enrich_recording_passes_through_non_mapping(): + from je_auto_control.utils.semantic_recording.enrich import enrich_recording + actions = [{"action": "key_press", "key": "a"}, "not-a-mapping", 42] + out = enrich_recording(actions) + assert out[1] == "not-a-mapping" + assert out[2] == 42 + + +if __name__ == "__main__": + pytest.main([__file__, "-q"]) diff --git a/test/unit_test/headless/test_r3_util_json_and_sql.py b/test/unit_test/headless/test_r3_util_json_and_sql.py new file mode 100644 index 00000000..4f10a9c4 --- /dev/null +++ b/test/unit_test/headless/test_r3_util_json_and_sql.py @@ -0,0 +1,102 @@ +"""Round-3 audit regressions: JSON persistence + SQL URI fixes. + +Covers findings 3 (atomic JSON writes), 4 (cyclic ``$ref`` detection), and +11 (SQLite URI with special characters in the path). Headless: pure temp +files and an in-process SQLite DB, no real input. +""" +import os +import sqlite3 + +import pytest + + +# --- Finding 3: atomic writes -------------------------------------------- + +def _raise_oserror(_src, _dst): + raise OSError("simulated crash mid-replace") + + +def test_write_action_json_is_atomic_on_crash(tmp_path, monkeypatch): + from je_auto_control.utils.json.json_file import ( + read_action_json, write_action_json, + ) + from je_auto_control.utils.exception.exceptions import ( + AutoControlJsonActionException, + ) + target = tmp_path / "actions.json" + write_action_json(str(target), [["old"]]) + assert read_action_json(str(target)) == [["old"]] + + monkeypatch.setattr(os, "replace", _raise_oserror) + with pytest.raises(AutoControlJsonActionException): + write_action_json(str(target), [["new"]]) + monkeypatch.undo() + + # Old content survives the interrupted write; no temp leftovers remain. + assert read_action_json(str(target)) == [["old"]] + leftovers = [p.name for p in tmp_path.iterdir() if p.name != "actions.json"] + assert leftovers == [] + + +def test_write_json_dict_is_atomic_on_crash(tmp_path, monkeypatch): + from je_auto_control.utils.json_store.json_store import ( + read_json_dict, write_json_dict, + ) + target = tmp_path / "state.json" + write_json_dict(target, {"v": 1}) + assert read_json_dict(target) == {"v": 1} + + monkeypatch.setattr(os, "replace", _raise_oserror) + with pytest.raises(OSError): + write_json_dict(target, {"v": 2}) + monkeypatch.undo() + + assert read_json_dict(target) == {"v": 1} + leftovers = [p.name for p in tmp_path.iterdir() if p.name != "state.json"] + assert leftovers == [] + + +# --- Finding 4: cyclic $ref ------------------------------------------------ + +def test_validate_json_cyclic_ref_reports_clean_error(): + from je_auto_control.utils.json_schema.json_schema import validate_json + result = validate_json(1, {"$ref": "#"}) + assert result.ok is False + assert any(err["keyword"] == "$ref" for err in result.errors) + + +def test_validate_json_indirect_ref_cycle(): + from je_auto_control.utils.json_schema.json_schema import validate_json + schema = { + "$defs": { + "a": {"$ref": "#/$defs/b"}, + "b": {"$ref": "#/$defs/a"}, + }, + "$ref": "#/$defs/a", + } + result = validate_json(1, schema) + assert result.ok is False + assert any(err["keyword"] == "$ref" for err in result.errors) + + +# --- Finding 11: SQLite URI with special path characters ----------------- + +def test_query_sqlite_special_char_path(tmp_path): + from je_auto_control.utils.sql.sql_query import query_sqlite + # '%41' would percent-decode to 'A' in a raw file: URI and '#' is unsafe too. + db_path = tmp_path / "weird%41name#.db" + connection = sqlite3.connect(str(db_path)) + try: + connection.execute("CREATE TABLE t (id INTEGER, name TEXT)") + connection.execute("INSERT INTO t VALUES (1, 'alice')") + connection.commit() + finally: + connection.close() + + rows = query_sqlite(str(db_path), "SELECT name FROM t WHERE id = ?", + params=[1]) + assert rows == [{"name": "alice"}] + + +if __name__ == "__main__": + pytest.main([__file__, "-q"]) diff --git a/test/unit_test/headless/test_r3_util_misc.py b/test/unit_test/headless/test_r3_util_misc.py new file mode 100644 index 00000000..e4f54d0e --- /dev/null +++ b/test/unit_test/headless/test_r3_util_misc.py @@ -0,0 +1,129 @@ +"""Round-3 audit regressions: plugin isolation, dedup, time-travel, BDD. + +Covers findings 7 (plugin_sdk), 10 (dedup_window), 13 (time-travel action +index), and 14 (behave step registration). Fully headless. +""" +import sys +import types + +import pytest + + +# --- Finding 7: plugin discovery skips a broken plugin ------------------- + +class _FakeEntryPoint: + def __init__(self, name, factory): + self.name = name + self._factory = factory + + def load(self): + return self._factory + + +def test_discover_plugins_skips_broken_plugin(): + from je_auto_control.utils.plugin_sdk.plugin_sdk import discover_plugins + + def good_factory(): + return {"AC_good": lambda: "ok"} + + def key_error_factory(): + raise KeyError("boom") # outside the old catch tuple + + class _SyntaxErrorEP: + name = "syntax" + + def load(self): + raise SyntaxError("broken plugin module") + + points = [ + _FakeEntryPoint("bad", key_error_factory), + _SyntaxErrorEP(), + _FakeEntryPoint("good", good_factory), + ] + commands = discover_plugins(entry_points=points) + assert "AC_good" in commands + assert "bad" not in commands + + +# --- Finding 10: dedup window coerces int ids consistently --------------- + +def test_dedup_window_dedupes_integer_ids(): + from je_auto_control.utils.dedup_window.dedup_window import DedupWindow + clock = [1000.0] + window = DedupWindow(60.0, clock=lambda: clock[0]) + window.mark(123) + assert window.seen(123) is True + assert window.check_and_mark(123) is False + + +def test_dedup_window_check_and_mark_int_first_seen(): + from je_auto_control.utils.dedup_window.dedup_window import DedupWindow + clock = [1000.0] + window = DedupWindow(60.0, clock=lambda: clock[0]) + assert window.check_and_mark(7) is True + assert window.seen(7) is True + assert window.check_and_mark(7) is False + + +# --- Finding 13: actions-only recording still yields an action index ----- + +def test_actions_only_recording_yields_action_index(tmp_path): + from je_auto_control.utils.time_travel.controller import ( + TraceReplayController, + ) + from je_auto_control.utils.time_travel.player import ( + ActionEvent, TimelinePlayer, save_action_log, + ) + events = [ + ActionEvent(timestamp=1000.0, action_name="mouse_click", args={"x": 1}), + ActionEvent(timestamp=1001.5, action_name="type_keyboard", + args={"text": "a"}), + ] + save_action_log(events, tmp_path / "actions.jsonl") + + player = TimelinePlayer(tmp_path) # no manifest.json → total_steps == 0 + assert player.frame_count == 0 + assert player.action_count == 2 + + controller = TraceReplayController(player) + index = controller.action_index() + assert len(index) == 2 + + +# --- Finding 14: behave steps accept a leading context param ------------- + +def test_behave_wrap_forwards_params_and_accepts_context(): + from je_auto_control.utils.pytest_plugin.bdd_steps import _behave_wrap + seen = [] + wrapped = _behave_wrap(lambda text: seen.append(text) or "ok") + result = wrapped(object(), text="hello") + assert seen == ["hello"] + assert result == "ok" + + +def test_register_behave_steps_wrappers_accept_context(monkeypatch): + from je_auto_control.utils.pytest_plugin.bdd_steps import ( + register_behave_steps, + ) + registered = {} + + def factory(pattern): + def decorator(func): + registered[pattern] = func + return func + return decorator + + fake_behave = types.ModuleType("behave") + fake_behave.given = factory + fake_behave.when = factory + fake_behave.then = factory + monkeypatch.setitem(sys.modules, "behave", fake_behave) + + register_behave_steps() + ready_step = registered["AutoControl is ready"] + # behave calls func(context); the raw 0-arg step would raise TypeError. + ready_step(object()) + + +if __name__ == "__main__": + pytest.main([__file__, "-q"]) diff --git a/test/unit_test/headless/test_r3_util_win32_and_shell.py b/test/unit_test/headless/test_r3_util_win32_and_shell.py new file mode 100644 index 00000000..a6fc55ca --- /dev/null +++ b/test/unit_test/headless/test_r3_util_win32_and_shell.py @@ -0,0 +1,128 @@ +"""Round-3 audit regressions: process launch, WM_DROPFILES, shell reaping. + +Covers findings 8 (start_exe argv), 9 (file_drop ctypes signatures), and +12 (ShellManager.exit_program reaping). Headless: no real process is +launched and no real window message is posted. +""" +import subprocess + +import pytest + + +# --- Finding 8: start_exe must not shlex-split a single spaced path ------- + +def test_start_exe_passes_argv_list_not_split_string(tmp_path, monkeypatch): + from je_auto_control.utils.shell_process.shell_exec import ShellManager + from je_auto_control.utils.start_exe.start_another_process import start_exe + + exe = tmp_path / "my program.exe" + exe.write_text("stub") + captured = {} + + def fake_exec(self, command): + captured["command"] = command + self.process = object() # non-None → launch "succeeded" + + monkeypatch.setattr(ShellManager, "exec_shell", fake_exec) + start_exe(str(exe)) + # The spaced path is handed over verbatim as a single argv element. + assert captured["command"] == [str(exe)] + + +def test_start_exe_raises_when_launch_fails(tmp_path, monkeypatch): + from je_auto_control.utils.shell_process.shell_exec import ShellManager + from je_auto_control.utils.start_exe.start_another_process import start_exe + from je_auto_control.utils.exception.exceptions import AutoControlException + + exe = tmp_path / "app.exe" + exe.write_text("stub") + + def fake_exec(self, _command): + self.process = None # exec_shell swallowed the launch failure + + monkeypatch.setattr(ShellManager, "exec_shell", fake_exec) + with pytest.raises(AutoControlException): + start_exe(str(exe)) + + +# --- Finding 9: file_drop ctypes signatures ------------------------------ + +class _FakeFn: + """Stands in for a ctypes function pointer (accepts argtypes/restype).""" + + +class _FakeLib: + def __init__(self, names): + for name in names: + setattr(self, name, _FakeFn()) + + +def test_declare_win32_signatures_sets_all_argtypes(): + import ctypes + from ctypes import wintypes + from je_auto_control.utils.file_drop.file_drop import ( + _declare_win32_signatures, + ) + kernel32 = _FakeLib(["GlobalAlloc", "GlobalLock", "GlobalUnlock"]) + user32 = _FakeLib(["PostMessageW"]) + + _declare_win32_signatures(kernel32, user32) + + assert kernel32.GlobalAlloc.argtypes == [wintypes.UINT, ctypes.c_size_t] + assert kernel32.GlobalAlloc.restype is wintypes.HGLOBAL + # These three previously had NO argtypes → 64-bit handle truncation. + assert kernel32.GlobalLock.argtypes == [wintypes.HGLOBAL] + assert kernel32.GlobalLock.restype is ctypes.c_void_p + assert kernel32.GlobalUnlock.argtypes == [wintypes.HGLOBAL] + assert kernel32.GlobalUnlock.restype is wintypes.BOOL + assert user32.PostMessageW.argtypes == [ + wintypes.HWND, wintypes.UINT, wintypes.WPARAM, wintypes.LPARAM, + ] + assert user32.PostMessageW.restype is wintypes.BOOL + + +# --- Finding 12: exit_program reaps and logs the real exit code ---------- + +class _FakeProc: + def __init__(self): + self.returncode = None + self.terminated = False + self.waited = False + + def terminate(self): + self.terminated = True + + def wait(self, timeout=None): # noqa: ARG002 # reason: mirrors Popen.wait + self.waited = True + self.returncode = 0 + return 0 + + def poll(self): + return self.returncode + + +def test_exit_program_waits_and_logs_real_returncode(monkeypatch): + from je_auto_control.utils.shell_process import shell_exec as shell_mod + from je_auto_control.utils.shell_process.shell_exec import ShellManager + + manager = ShellManager() + proc = _FakeProc() + manager.process = proc + messages = [] + monkeypatch.setattr( + shell_mod.autocontrol_logger, "info", + lambda msg, *a, **k: messages.append(str(msg))) + monkeypatch.setattr( + shell_mod.autocontrol_logger, "error", lambda *a, **k: None) + + manager.exit_program() + + assert proc.terminated is True + assert proc.waited is True # exit code was actually reaped, not read as None + assert any("code 0" in message for message in messages) + assert manager.process is None + assert isinstance(subprocess.TimeoutExpired, type) # import sanity + + +if __name__ == "__main__": + pytest.main([__file__, "-q"]) diff --git a/test/unit_test/headless/test_r3_vision_capture.py b/test/unit_test/headless/test_r3_vision_capture.py new file mode 100644 index 00000000..8f157f4a --- /dev/null +++ b/test/unit_test/headless/test_r3_vision_capture.py @@ -0,0 +1,132 @@ +"""R3 regression: recorder stop, frame-diff pixel counting, screenshot save. + +Covers: +* ScreenRecordThread ignoring a stop() that lands before run() (run() set the + flag True unconditionally), leaving it unstoppable and the writer un-released. +* smart_waits._frame_diff counting differing BYTES instead of PIXELS, so a + one-pixel blink read as three and wait_until_screen_stable(max_pixel_diff=2) + never settled. +* pil_screenshot swallowing a save failure and returning the image anyway, so a + requested screenshot file could silently not exist. +""" +import pytest + +np = pytest.importorskip("numpy") +pytest.importorskip("cv2") + +from je_auto_control.utils.cv2_utils import screen_record as sr # noqa: E402 +from je_auto_control.utils.cv2_utils import screenshot as ss # noqa: E402 +from je_auto_control.utils.smart_waits import waits # noqa: E402 +from je_auto_control.utils.exception.exceptions import ( # noqa: E402 + AutoControlScreenException, +) + + +class _FakeVideoWriter: + """Stand-in for cv2.VideoWriter that never touches a real file.""" + + @staticmethod + def fourcc(*_codec): + return 0 + + def __init__(self, *_args, **_kwargs): + self.writes = 0 + self.released = False + + def write(self, _frame): + self.writes += 1 + + def release(self): + self.released = True + + +# --- finding 2: recorder stop honoured + writer released ----------------- + +def test_stop_before_run_is_honored(monkeypatch): + monkeypatch.setattr(sr.cv2, "VideoWriter", _FakeVideoWriter) + thread = sr.ScreenRecordThread("out.avi", "XVID", 30, (4, 4)) + frame = np.zeros((4, 4, 3), dtype=np.uint8) + + def screenshot_bounds_the_buggy_path(): + # If the pre-run stop() is overwritten (old bug), stopping here bounds + # the loop to a single frame so the test cannot hang; the assertions + # below still detect that the loop body ran. + thread.stop() + return frame + + monkeypatch.setattr(sr, "screenshot", screenshot_bounds_the_buggy_path) + + thread.stop() # stop BEFORE the thread body runs + thread.run() # run synchronously + + assert thread.video_writer.writes == 0 # loop body never executed + assert thread.video_writer.released is True # writer always released + + +def test_normal_run_writes_frames_and_releases(monkeypatch): + monkeypatch.setattr(sr.cv2, "VideoWriter", _FakeVideoWriter) + thread = sr.ScreenRecordThread("out.avi", "XVID", 30, (4, 4)) + frame = np.zeros((4, 4, 3), dtype=np.uint8) + calls = {"n": 0} + + def screenshot_stop_after_two(): + calls["n"] += 1 + if calls["n"] >= 2: + thread.stop() + return frame + + monkeypatch.setattr(sr, "screenshot", screenshot_stop_after_two) + thread.run() + + assert thread.video_writer.writes == 2 + assert thread.video_writer.released is True + + +# --- finding 4: frame diff counts pixels, not bytes ---------------------- + +def test_frame_diff_counts_pixels_not_bytes(): + base = bytes([0, 0, 0] * 4) # 2x2 RGB, all black + changed = bytearray(base) + changed[0:3] = bytes([255, 255, 255]) # one pixel, three differing bytes + a = waits.Frame(2, 2, base) + b = waits.Frame(2, 2, bytes(changed)) + assert waits._frame_diff(a, b) == 1 # one pixel, not three bytes + + +def test_screen_stable_settles_on_one_pixel_blink(): + base = bytes([10, 20, 30] * 4) + blink = bytearray(base) + blink[0:3] = bytes([200, 200, 200]) # a one-pixel change + frames = [base, bytes(blink)] + state = {"i": 0} + + def sampler(_region): + # Alternate base <-> blink forever: consecutive frames always differ by + # exactly one pixel, so a pixel-aware diff (<=2) settles immediately + # while the old byte-aware diff (=3) never would. + frame = frames[state["i"] % 2] + state["i"] += 1 + return waits.Frame(2, 2, frame) + + outcome = waits.wait_until_screen_stable( + timeout_s=0.2, poll_interval_s=0.001, stable_for_s=0.0, + max_pixel_diff=2, sampler=sampler, + ) + assert outcome.succeeded is True + + +# --- finding 7: screenshot save failure raises --------------------------- + +class _FakeGrab: + @staticmethod + def grab(*_args, **_kwargs): + from PIL import Image + return Image.new("RGB", (4, 4)) + + +def test_screenshot_save_failure_raises(monkeypatch, tmp_path): + pytest.importorskip("PIL") + monkeypatch.setattr(ss, "ImageGrab", _FakeGrab) + bad_path = str(tmp_path / "no_such_dir" / "shot.png") # parent missing + with pytest.raises(AutoControlScreenException): + ss.pil_screenshot(file_path=bad_path) diff --git a/test/unit_test/headless/test_r3_vision_gray.py b/test/unit_test/headless/test_r3_vision_gray.py new file mode 100644 index 00000000..3bb8bed2 --- /dev/null +++ b/test/unit_test/headless/test_r3_vision_gray.py @@ -0,0 +1,92 @@ +"""R3 regression: RGB vs BGR luminance weights + bounded find-all. No Qt. + +Covers: +* visual_match._to_gray / ssim._to_gray_f converting ndarray / PIL (RGB) sources + with BGR weights, which swapped the R/B luminance weights so a red template's + gray disagreed with the same red on screen by up to ~47/255. +* best_matches materialising a Match per score-map position (min_score=-1.0), + effectively hanging on a real screen. +""" +import pytest + +np = pytest.importorskip("numpy") +cv2 = pytest.importorskip("cv2") + +from je_auto_control.utils.visual_match import visual_match as vm # noqa: E402 +from je_auto_control.utils.ssim import ssim as ssim_mod # noqa: E402 +from je_auto_control.utils.ssim.ssim import ssim_compare # noqa: E402 + + +# Correct luminance of saturated red: 0.299 * 255. The old BGR-on-RGB path gave +# 0.114 * 255 ~= 29 (the blue weight). +_RED_GRAY = 0.299 * 255 + + +def _rgb_red(height: int = 8, width: int = 8): + """A saturated-red image in RGB channel order (R in channel 0).""" + img = np.zeros((height, width, 3), dtype=np.uint8) + img[..., 0] = 255 + return img + + +def test_visual_match_gray_uses_rgb_weights_for_ndarray(): + gray = vm._to_gray(_rgb_red()) + assert gray.mean() == pytest.approx(_RED_GRAY, abs=1.0) + + +def test_visual_match_gray_uses_rgb_weights_for_pil(): + pytest.importorskip("PIL") + from PIL import Image + gray = vm._to_gray(Image.fromarray(_rgb_red())) # PIL is RGB + assert gray.mean() == pytest.approx(_RED_GRAY, abs=1.0) + + +def test_disk_bgr_template_and_rgb_haystack_agree(tmp_path): + # A red image written to disk is read back as BGR by cv2.imread; the same + # red as an RGB ndarray (the live screen grab) must map to the same gray. + path = tmp_path / "red.png" + cv2.imwrite(str(path), np.full((8, 8, 3), (0, 0, 255), np.uint8)) # BGR red + from_disk = vm._to_gray(str(path)) + from_rgb = vm._to_gray(_rgb_red()) + assert from_disk.mean() == pytest.approx(from_rgb.mean(), abs=1.0) + assert from_disk.mean() == pytest.approx(_RED_GRAY, abs=1.0) + + +def test_ssim_gray_uses_rgb_weights_for_ndarray(): + gray = ssim_mod._to_gray_f(_rgb_red()) + assert float(gray.mean()) == pytest.approx(_RED_GRAY, abs=1.0) + + +def test_ssim_compare_disk_red_vs_rgb_red_is_identical(tmp_path): + path = tmp_path / "red.png" + cv2.imwrite(str(path), np.full((20, 20, 3), (0, 0, 255), np.uint8)) + score = ssim_compare(str(path), _rgb_red(20, 20)) + # Structurally identical red -> ~1.0. The old code read them ~47 apart, + # scoring the identical colour as a large structural change (~0.67). + assert score > 0.99 + + +def _textured(height: int, width: int, seed: int = 0): + yy, xx = np.mgrid[0:height, 0:width] + return ((yy * 53 + xx * 97 + yy * xx * 17 + seed) % 256).astype(np.uint8) + + +def test_best_matches_is_bounded(monkeypatch): + """best_matches must cap candidates before the O(n·kept) Python NMS.""" + hay = _textured(200, 300, seed=5) + tmpl = hay[40:52, 60:72].copy() # an exact 12x12 sub-patch + + built = {"n": 0} + real_match = vm.Match + + def counting_match(*args, **kwargs): + built["n"] += 1 + return real_match(*args, **kwargs) + + monkeypatch.setattr(vm, "Match", counting_match) + result = vm.best_matches(tmpl, haystack=hay, top_n=5) + + assert len(result) <= 5 + assert (result[0].x, result[0].y) == (60, 40) # correctness preserved + # ~54k positions clear min_score=-1.0; the cap keeps construction bounded. + assert built["n"] <= 1000 diff --git a/test/unit_test/headless/test_r3_vision_locator.py b/test/unit_test/headless/test_r3_vision_locator.py new file mode 100644 index 00000000..a12af5bd --- /dev/null +++ b/test/unit_test/headless/test_r3_vision_locator.py @@ -0,0 +1,77 @@ +"""R3 regression: anchor not-found handling, directional distance, wma zero-sum. + +Covers: +* anchor adapters catching only (OSError, RuntimeError, ValueError), so a plain + ImageNotFoundException / AutoControlActionException ("not on screen") crashed + the whole locate instead of returning found=False / []. +* max_distance_px applied only for relation==near, ignored for directional + relations. +* smoothing.wma dividing by a zero-sum weight window (warm-up tail of [1, 0]). +""" +import pytest + +from je_auto_control.utils.anchor_locator import ( + anchor_locate, image_locator, ocr_locator, REL_NEAR, +) +from je_auto_control.utils.anchor_locator import locator as locator_mod +from je_auto_control.utils.exception.exceptions import ( + AutoControlActionException, ImageNotFoundException, +) +from je_auto_control.utils.smoothing.smoothing import wma + + +# --- finding 8: adapters swallow framework "not found" exceptions -------- + +def test_image_candidates_swallow_not_found(monkeypatch): + def raise_not_found(*_args, **_kwargs): + raise ImageNotFoundException("not on screen") + + monkeypatch.setattr( + "je_auto_control.wrapper.auto_control_image.locate_all_image", + raise_not_found, + ) + assert locator_mod._image_candidates(image_locator("b.png")) == [] + + +def test_ocr_center_swallows_action_exception(monkeypatch): + def raise_action(*_args, **_kwargs): + raise AutoControlActionException("text not found") + + monkeypatch.setattr( + "je_auto_control.utils.ocr.ocr_engine.locate_text_center", + raise_action, + ) + assert locator_mod._ocr_center(ocr_locator("Submit")) is None + + +def test_anchor_locate_target_missing_returns_outcome(monkeypatch): + monkeypatch.setattr( + "je_auto_control.wrapper.auto_control_image.locate_image_center", + lambda *_a, **_k: (10, 10), # anchor resolves fine + ) + + def raise_not_found(*_args, **_kwargs): + raise ImageNotFoundException("no target") + + monkeypatch.setattr( + "je_auto_control.wrapper.auto_control_image.locate_all_image", + raise_not_found, + ) + outcome = anchor_locate(anchor=image_locator("a.png"), + target=image_locator("b.png"), relation=REL_NEAR) + assert outcome.found is False + assert outcome.error == "target not found" + + +# --- finding 9: wma zero-sum weight window ------------------------------- + +def test_wma_zero_weight_tail_no_zero_division(): + # weights=[1, 0]: the warm-up window is [value0] with applied weight [0]. + assert wma([5.0, 6.0], weights=[1, 0]) == [5.0, 5.0] + + +def test_wma_normal_case_unaffected(): + out = wma([1.0, 2.0, 3.0], weights=[1, 2]) + assert out[0] == pytest.approx(1.0) + assert out[1] == pytest.approx((1 * 1 + 2 * 2) / 3) + assert out[2] == pytest.approx((2 * 1 + 3 * 2) / 3) diff --git a/test/unit_test/headless/test_r3_vision_poll.py b/test/unit_test/headless/test_r3_vision_poll.py new file mode 100644 index 00000000..2e827ea6 --- /dev/null +++ b/test/unit_test/headless/test_r3_vision_poll.py @@ -0,0 +1,43 @@ +"""R3 regression: expect_poll clamps its sleep to the remaining time. No Qt. + +A large interval_s could otherwise overshoot timeout_s by nearly a full +interval (and run an extra getter well past the deadline). +""" +from je_auto_control.utils.expect_poll import expect_poll, to_equal + + +def test_sleep_is_clamped_to_remaining_time(): + times = [0.0] + sleeps = [] + + def clock(): + return times[0] + + def sleep(seconds): + sleeps.append(seconds) + times[0] += seconds + + result = expect_poll(lambda: 1, to_equal(2), timeout_s=0.05, + interval_s=100.0, clock=clock, sleep=sleep) + + assert result.ok is False + # No single sleep may exceed the time left, and the poll must not overshoot + # the deadline. The old code slept the full 100s on the first miss. + assert sleeps and max(sleeps) <= 0.05 + 1e-9 + assert times[0] <= 0.05 + 1e-9 + + +def test_clamp_does_not_change_evenly_divided_timeline(): + # interval_s divides timeout_s evenly: clamping must not change attempts. + times = [0.0] + + def clock(): + return times[0] + + def sleep(seconds): + times[0] += seconds + + result = expect_poll(lambda: 0, to_equal(9), timeout_s=5.0, + interval_s=0.25, clock=clock, sleep=sleep) + assert result.ok is False + assert result.attempts == 21 # 20 sleeps + the first attempt diff --git a/test/unit_test/headless/test_recording_editor_undo.py b/test/unit_test/headless/test_recording_editor_undo.py index 2fa73cc3..ce9f42bd 100644 --- a/test/unit_test/headless/test_recording_editor_undo.py +++ b/test/unit_test/headless/test_recording_editor_undo.py @@ -4,7 +4,7 @@ import pytest os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") -pytest.importorskip("PySide6.QtWidgets") +pytest.importorskip("PySide6.QtWidgets", exc_type=ImportError) from PySide6.QtWidgets import QApplication # noqa: E402 diff --git a/test/unit_test/headless/test_remote_desktop_cursor.py b/test/unit_test/headless/test_remote_desktop_cursor.py index d5412a0d..fa402e90 100644 --- a/test/unit_test/headless/test_remote_desktop_cursor.py +++ b/test/unit_test/headless/test_remote_desktop_cursor.py @@ -135,7 +135,7 @@ def test_frame_display_paints_cursor_overlay(): """Setting the remote cursor must mutate state + trigger update.""" import os os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") - pytest.importorskip("PySide6.QtWidgets") + pytest.importorskip("PySide6.QtWidgets", exc_type=ImportError) from PySide6.QtWidgets import QApplication if QApplication.instance() is None: QApplication([]) diff --git a/test/unit_test/headless/test_remote_desktop_gui.py b/test/unit_test/headless/test_remote_desktop_gui.py index dfd504f5..8ced3282 100644 --- a/test/unit_test/headless/test_remote_desktop_gui.py +++ b/test/unit_test/headless/test_remote_desktop_gui.py @@ -15,7 +15,7 @@ os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") PIL = pytest.importorskip("PIL.Image") -pyside = pytest.importorskip("PySide6.QtWidgets") +pyside = pytest.importorskip("PySide6.QtWidgets", exc_type=ImportError) # These tests round-trip JPEG frames through the WebRTC stack — skip # entirely on environments that lack the optional 'webrtc' extra (aiortc # + PyAV), since the registry singleton imports webrtc_transport on use. diff --git a/test/unit_test/headless/test_remote_desktop_host_lifecycle.py b/test/unit_test/headless/test_remote_desktop_host_lifecycle.py new file mode 100644 index 00000000..6c4c80fc --- /dev/null +++ b/test/unit_test/headless/test_remote_desktop_host_lifecycle.py @@ -0,0 +1,135 @@ +"""Lifecycle races on RemoteDesktopHost. + +The class docstring promises "start() is idempotent and stop() can be called +from any thread". Nothing enforced that, and for a tool that hands a remote +peer control of the local mouse and keyboard, a stop() that does not actually +stop is a safety problem rather than a tidiness one. +""" +import socket +import threading +import time + +import pytest + +from je_auto_control.utils.remote_desktop import host as host_mod +from je_auto_control.utils.remote_desktop.host import RemoteDesktopHost + + +def _make_host() -> RemoteDesktopHost: + return RemoteDesktopHost(token="t", bind="127.0.0.1", port=0, + frame_provider=lambda: None) + + +class _FakeChannel: + """Stands in for a completed handshake.""" + + def close(self): + pass + + def settimeout(self, *_a): + pass + + def send(self, *_a, **_k): + pass + + def recv(self, *_a, **_k): + time.sleep(9) + raise OSError("closed") + + +def test_a_client_finishing_its_handshake_after_stop_is_not_attached( + monkeypatch): + """A viewer must never attach to a host the operator already stopped. + + Regression: _open_channel performs the auth/TLS handshake (bounded by + _AUTH_TIMEOUT_S = 60s). stop() sets _shutdown, then snapshots and clears + _clients under _clients_lock. _accept_loop appended the finished handler + without re-checking _shutdown under that same lock, so a client landing in + that window was never in stop()'s snapshot and was never stopped — its + receiver thread kept dispatching remote INPUT to the local machine after + stop() returned and is_running was already False. + """ + parked, release = threading.Event(), threading.Event() + + def slow_open_channel(_self, _sock, _address): + parked.set() + release.wait(5.0) + return _FakeChannel() + + monkeypatch.setattr(RemoteDesktopHost, "_open_channel", slow_open_channel) + monkeypatch.setattr(host_mod._ClientHandler, "start", lambda self: None) + + host = _make_host() + host.start() + conn = socket.create_connection(("127.0.0.1", host.port), timeout=3) + try: + assert parked.wait(3.0), "accept thread never reached the handshake" + host.stop(timeout=1.0) + assert host.is_running is False + + release.set() + time.sleep(0.8) # let the accept thread finish its handshake + + assert host._clients == [], ( + "a client attached to a stopped host: it would keep applying " + "remote input locally" + ) + finally: + release.set() + try: + conn.close() + except OSError: + pass + host.stop(timeout=1.0) + + +def test_stop_racing_start_never_raises_or_leaks_a_thread_exception(): + """start() and stop() must be mutually exclusive. + + Three distinct regressions lived here, all reproducible only under a + concurrent stop(): + * AttributeError — stop() nulled _capture_thread between start()'s + assignment and its .start() call; + * RuntimeError('cannot join thread before it is started') — stop()'s + guard (_listen_sock) was already set, so it joined unstarted threads; + * OSError WinError 10038 — the accept thread called settimeout() on the + listening socket outside any try, racing stop() closing it. + + This is a stress test, so it is probabilistic by nature: against the + unfixed code these surfaced at roughly 3/300 and 1/300, and 120 rounds was + not enough to catch them. 400 keeps it reliable while still finishing in a + couple of seconds. The two tests around it are deterministic. + """ + caught: list = [] + original = threading.excepthook + threading.excepthook = lambda args: caught.append( + (args.thread.name, type(args.exc_value).__name__)) + raised: list = [] + try: + for _ in range(400): + host = _make_host() + starter = threading.Thread(target=host.start) + starter.start() + for _ in range(2): + try: + host.stop(timeout=0.2) + except (RuntimeError, AttributeError) as error: + raised.append(type(error).__name__) + starter.join(2.0) + host.stop(timeout=0.2) + finally: + threading.excepthook = original + + assert raised == [], f"stop() raised: {raised[:3]}" + assert caught == [], f"thread died: {caught[:3]}" + + +def test_listening_socket_timeout_is_set_before_the_thread_can_see_it(): + """The accept thread must not configure a socket stop() may close.""" + host = _make_host() + host.start() + try: + assert host._listen_sock.gettimeout() == pytest.approx( + host_mod._ACCEPT_POLL_TIMEOUT_S) + finally: + host.stop(timeout=1.0) diff --git a/test/unit_test/headless/test_remote_desktop_quick_connect.py b/test/unit_test/headless/test_remote_desktop_quick_connect.py index e7ead50c..ba2ad8b6 100644 --- a/test/unit_test/headless/test_remote_desktop_quick_connect.py +++ b/test/unit_test/headless/test_remote_desktop_quick_connect.py @@ -8,7 +8,7 @@ os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") PIL = pytest.importorskip("PIL.Image") -pytest.importorskip("PySide6.QtWidgets") +pytest.importorskip("PySide6.QtWidgets", exc_type=ImportError) from PySide6.QtWidgets import QApplication # noqa: E402 diff --git a/test/unit_test/headless/test_remote_desktop_relay.py b/test/unit_test/headless/test_remote_desktop_relay.py index 22bd4f9d..102c0b4d 100644 --- a/test/unit_test/headless/test_remote_desktop_relay.py +++ b/test/unit_test/headless/test_remote_desktop_relay.py @@ -1,5 +1,6 @@ """Phase 3.3: tests for the in-process TCP relay.""" import socket +import threading import time import pytest @@ -118,3 +119,27 @@ def test_relay_stops_cleanly(): assert server.is_running server.stop(timeout=1.0) assert not server.is_running + + +def test_rapid_start_stop_never_leaks_a_thread_exception(monkeypatch): + """Stopping must not blow up the accept thread. + + Regression: start() published the listening socket and left the *new* + thread to call settimeout() on it. A stop() landing in that window closed + the socket first, so settimeout raised WSAENOTSOCK (WinError 10038) outside + any handler and the accept thread died with an unhandled exception — + reproducibly, on ~45% of tight start/stop cycles. + + test_relay_stops_cleanly above cannot catch this: it only asserts the + is_running flags, which are correct whether or not the thread survived. + """ + caught: list = [] + monkeypatch.setattr(threading, "excepthook", + lambda args: caught.append(args.exc_value)) + + for _ in range(50): + server = RelayServer(bind="127.0.0.1", port=0) + server.start() + server.stop(timeout=1.0) # no sleep: keep the race window wide + + assert caught == [], f"accept thread raised: {caught[:3]}" diff --git a/test/unit_test/headless/test_script_builder_param_preservation.py b/test/unit_test/headless/test_script_builder_param_preservation.py new file mode 100644 index 00000000..0e582290 --- /dev/null +++ b/test/unit_test/headless/test_script_builder_param_preservation.py @@ -0,0 +1,86 @@ +"""Loading a step into the form must never lose the user's data. + +Script Builder round-trips real files: builder_tab loads an action JSON, and on +save writes the steps back out. So anything the form drops on load is written +straight back over the user's file. +""" +import pytest + +pytest.importorskip("PySide6.QtWidgets", exc_type=ImportError) # skips if Qt libs (e.g. libEGL) absent + +from PySide6.QtWidgets import QApplication # noqa: E402 + +from je_auto_control.gui.script_builder.step_form_view import ( # noqa: E402 + StepFormView, +) +from je_auto_control.gui.script_builder.step_model import ( # noqa: E402 + action_to_step, step_to_action, +) + + +@pytest.fixture(scope="module") +def qapp(): + return QApplication.instance() or QApplication([]) + + +def _round_trip(action): + """Load an action into the form exactly as selecting a step does.""" + step = action_to_step(action) + view = StepFormView() + view.load_step(step) + return step_to_action(step) + + +@pytest.mark.parametrize("action", [ + # Params the schema has no field for — legitimate in hand-written JSON. + ["AC_debug_trace", {"actions": [["AC_click_mouse", + {"mouse_keycode": "mouse_left"}]], + "dry_run": True}], + ["AC_skill_save", {"actions": [["AC_ok"]], "path": "s.json", + "name": "old"}], + # A plain, fully-schema'd command must be untouched too. + ["AC_execute_process", {"exe_path": "notepad.exe"}], +]) +def test_loading_a_step_preserves_every_param(qapp, action): + """Regression: two independent faults conspired to lose data. + + 1. _commit_field rebuilt params from scratch out of spec.fields only, so + any param without a schema field was dropped. + 2. Editors are wired to textChanged at build time while + _populate_from_step fills them one at a time, so each setText committed + a half-filled form — clobbering fields not yet populated (this is what + turned AC_skill_save's "name" into None even though it *is* a field). + + Merely selecting the step was enough; builder_tab then saved the result. + """ + assert _round_trip(action) == action + + +def test_editing_a_field_keeps_unknown_params(qapp): + """A real edit must update its own field and leave the rest alone.""" + action = ["AC_debug_trace", {"actions": [["AC_ok"]], "dry_run": False}] + step = action_to_step(action) + view = StepFormView() + view.load_step(step) + + editor = view._editors["dry_run"] + editor.setChecked(True) # a genuine user edit, post-population + + out = step_to_action(step) + assert out[1]["dry_run"] is True + assert out[1]["actions"] == [["AC_ok"]], "unknown param lost on edit" + + +def test_clearing_an_optional_field_drops_the_key(qapp): + """The pre-existing semantic must survive: empty optional => no key.""" + action = ["AC_click_mouse", {"mouse_keycode": "mouse_left", + "x": 10, "y": 20}] + step = action_to_step(action) + view = StepFormView() + view.load_step(step) + + view._editors["x"].clear() # user empties an optional field + + out = step_to_action(step) + assert "x" not in out[1] + assert out[1]["y"] == 20 diff --git a/test/unit_test/headless/test_script_builder_schema_parity.py b/test/unit_test/headless/test_script_builder_schema_parity.py new file mode 100644 index 00000000..8c1f1f4d --- /dev/null +++ b/test/unit_test/headless/test_script_builder_schema_parity.py @@ -0,0 +1,80 @@ +"""Parity between the Script Builder schema and the executor's real signatures. + +The builder emits ``[command, params]`` and the executor dispatches +``event(**params)``. So every field the schema declares must be a parameter the +dispatch target actually accepts — a renamed or invented field is not a +cosmetic mismatch, it is a guaranteed TypeError the moment the step runs. + +Existing tests only assert command *names* exist in the schema, which is why +two such fields shipped: ``AC_click_mouse`` declared ``times`` (click_mouse has +no such parameter, and its default=1 was committed on load, so the most basic +command failed with no user input at all), and ``AC_execute_process`` declared +``program_path`` against ``start_exe(exe_path)``. +""" +import inspect + +import pytest + +from je_auto_control.gui.script_builder.command_schema import _build_specs +from je_auto_control.utils.executor.action_executor import executor + + +def _dispatch_targets(): + """Yield (command, spec, signature) for specs with an introspectable target. + + Block commands (AC_try, AC_loop, …) are dispatched as ``handler(executor, + args)`` rather than ``event(**params)``, so they are not in event_dict and + are out of scope here. + """ + for spec in _build_specs(): + target = executor.event_dict.get(spec.command) + if target is None: + continue + try: + sig = inspect.signature(target) + except (TypeError, ValueError): # builtins without signatures + continue + yield spec, sig + + +def _accepts_arbitrary_kwargs(sig) -> bool: + return any(p.kind == p.VAR_KEYWORD for p in sig.parameters.values()) + + +def test_every_schema_field_is_accepted_by_its_dispatch_target(): + offenders = [] + for spec, sig in _dispatch_targets(): + if _accepts_arbitrary_kwargs(sig): + continue + accepted = set(sig.parameters) | set(spec.body_keys or ()) + for field in spec.fields: + if field.name not in accepted: + offenders.append( + f"{spec.command}: schema field {field.name!r} is not a " + f"parameter of {sig}" + ) + assert offenders == [], ( + "Script Builder would emit params the executor cannot accept:\n " + + "\n ".join(offenders) + ) + + +@pytest.mark.parametrize("command, field_name", [ + ("AC_click_mouse", "times"), + ("AC_execute_process", "program_path"), +]) +def test_previously_broken_fields_stay_gone(command, field_name): + """Pin the two specific regressions above by name.""" + spec = next(s for s in _build_specs() if s.command == command) + assert field_name not in {f.name for f in spec.fields} + + +def test_the_two_repaired_commands_bind_cleanly(): + """The end-to-end property that actually matters: params bind to the call.""" + click = executor.event_dict["AC_click_mouse"] + inspect.signature(click).bind(mouse_keycode="mouse_left", x=1, y=2) + + start = executor.event_dict["AC_execute_process"] + spec = next(s for s in _build_specs() if s.command == "AC_execute_process") + field = spec.fields[0].name + inspect.signature(start).bind(**{field: "notepad.exe"}) diff --git a/test/unit_test/headless/test_tls_acme.py b/test/unit_test/headless/test_tls_acme.py index 224f58f3..9de9220a 100644 --- a/test/unit_test/headless/test_tls_acme.py +++ b/test/unit_test/headless/test_tls_acme.py @@ -215,6 +215,32 @@ def boom(): assert isinstance(failures[0], RuntimeError) +def test_renewal_survives_a_certbot_subprocess_failure(tmp_path): + """certbot failures must route to on_failure, not kill the renewal thread. + + Regression: tick() caught only (RuntimeError, OSError, ValueError), but + run_certbot raises subprocess.CalledProcessError (exit != 0) and + TimeoutExpired — both subprocess.SubprocessError, none of them in the + tuple. The first certbot failure — the exact moment renewal matters — let + the exception escape tick(), killed the acme-renewal thread, never fired + on_failure, and the certificate silently expired. + """ + import subprocess + + failures = [] + + def certbot_fails(): + raise subprocess.CalledProcessError(1, ["certbot", "renew"]) + + scheduler = RenewalScheduler( + tmp_path / "cert.pem", renew=certbot_fails, + threshold=timedelta(days=30), on_failure=failures.append, + ) + assert scheduler.tick() is True # attempted, did not raise + assert len(failures) == 1 + assert isinstance(failures[0], subprocess.CalledProcessError) + + def test_scheduler_start_and_stop_are_idempotent(tmp_path): scheduler = RenewalScheduler( tmp_path / "cert.pem", diff --git a/test/unit_test/headless/test_trigger_engine.py b/test/unit_test/headless/test_trigger_engine.py index f4952833..89d9a0f1 100644 --- a/test/unit_test/headless/test_trigger_engine.py +++ b/test/unit_test/headless/test_trigger_engine.py @@ -149,6 +149,8 @@ def test_cron_trigger_skips_when_minute_mismatches(): def test_cron_trigger_rejects_invalid_expression(): - trig = CronTrigger(trigger_id="c", script_path="s.json", cron="not-cron") + # Rejected at construction, not deferred to is_fired(). Validating on the + # polling thread was the bug: the ValueError escaped _poll_once and killed + # the engine, taking every other trigger with it. with pytest.raises(ValueError): - trig.is_fired() + CronTrigger(trigger_id="c", script_path="s.json", cron="not-cron") diff --git a/test/unit_test/headless/test_trigger_engine_resilience.py b/test/unit_test/headless/test_trigger_engine_resilience.py new file mode 100644 index 00000000..dd445616 --- /dev/null +++ b/test/unit_test/headless/test_trigger_engine_resilience.py @@ -0,0 +1,107 @@ +"""One bad trigger must never stop the trigger engine. + +The engine polls every registered trigger on a single background thread. That +makes any escaping exception a poison pill: the thread dies, *every* trigger +silently stops firing, and the offender stays registered so a restart dies the +same way. Nothing logs a failure and the UI still lists the triggers as active. +""" +import threading +import time + +import pytest + +from je_auto_control.utils.triggers.trigger_engine import ( + CronTrigger, TriggerEngine, _TriggerBase, +) + + +class _RogueTrigger(_TriggerBase): + """is_fired() reaches out to the screen/clock/filesystem — it can raise.""" + + def is_fired(self) -> bool: + raise RuntimeError("boom") + + +@pytest.fixture() +def engine(): + made = TriggerEngine(executor=lambda actions: None, tick_seconds=0.05) + yield made + made.stop() + + +def test_a_bad_cron_expression_is_rejected_at_construction(): + """Regression: parse_cron ran on the polling thread, not the caller. + + The Triggers tab passes its cron QLineEdit text straight to add(), so a + typo used to register fine and then kill the engine on the next tick. + """ + with pytest.raises(ValueError, match="cron"): + CronTrigger(trigger_id="bad", script_path="b.json", cron="not a cron") + + +def test_a_valid_cron_still_constructs(): + trigger = CronTrigger(trigger_id="ok", script_path="s.json", + cron="*/5 * * * *") + assert trigger.cron == "*/5 * * * *" + + +def test_a_trigger_whose_is_fired_raises_is_disabled_not_fatal(engine): + """Regression: an exception from is_fired() killed the polling thread.""" + caught: list = [] + original = threading.excepthook + threading.excepthook = lambda args: caught.append( + type(args.exc_value).__name__) + try: + engine.add(_RogueTrigger(trigger_id="rogue", script_path="r.json", + repeat=True)) + engine.start() + time.sleep(0.4) + + assert engine._thread.is_alive(), "polling thread died" + assert caught == [] + assert engine._triggers["rogue"].enabled is False, ( + "the offender should be disabled so it stops re-raising each tick" + ) + finally: + threading.excepthook = original + + +def test_a_trigger_pointing_at_a_missing_script_is_not_fatal(engine): + """Regression: _fire caught only (OSError, ValueError, RuntimeError). + + read_action_json raises AutoControlJsonActionException, which is none of + those — so merely renaming a trigger's script file killed the engine. + """ + caught: list = [] + original = threading.excepthook + threading.excepthook = lambda args: caught.append( + type(args.exc_value).__name__) + try: + engine.add(CronTrigger(trigger_id="missing", + script_path="does-not-exist.json", + cron="* * * * *", repeat=True)) + engine.start() + time.sleep(0.4) + + assert engine._thread.is_alive(), "polling thread died" + assert caught == [] + finally: + threading.excepthook = original + + +def test_a_healthy_trigger_keeps_firing_alongside_a_broken_one(engine): + """The whole point: one poison pill must not take the others down.""" + fired: list = [] + engine._execute = lambda actions: fired.append(actions) + engine.add(_RogueTrigger(trigger_id="rogue", script_path="r.json", + repeat=True)) + healthy = CronTrigger(trigger_id="healthy", script_path="h.json", + cron="* * * * *", repeat=True) + engine.add(healthy) + + engine.start() + time.sleep(0.4) + + assert engine._thread.is_alive() + assert engine._triggers["rogue"].enabled is False + assert engine._triggers["healthy"].enabled is True diff --git a/test/unit_test/headless/test_usb_acl_prompt.py b/test/unit_test/headless/test_usb_acl_prompt.py index 61a61091..bf25b8c0 100644 --- a/test/unit_test/headless/test_usb_acl_prompt.py +++ b/test/unit_test/headless/test_usb_acl_prompt.py @@ -8,7 +8,7 @@ # Force offscreen so the dialog never tries to draw on a real display. os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") -pyside = pytest.importorskip("PySide6.QtWidgets") +pyside = pytest.importorskip("PySide6.QtWidgets", exc_type=ImportError) # gui/__init__.py eagerly loads main_window → webrtc_panel → aiortc. # The dialog itself only needs Qt, but we have to satisfy the chain # to import anything from je_auto_control.gui. @@ -69,10 +69,23 @@ def test_dialog_remember_reflects_checkbox(qapp): # --------------------------------------------------------------------------- +# A retry chain must be bounded. An unbounded self-rescheduling singleShot +# outlives the test that started it: the qapp fixture below reuses +# QApplication.instance(), which in a full-suite run was created by an earlier +# module and lives for the whole session. An orphaned chain therefore keeps +# scanning topLevelWidgets() indefinitely and can press accept()/reject() on a +# later test's prompt. 2s of retries comfortably covers the 3s decide() +# timeout these tests use. +_DIALOG_RETRY_INTERVAL_MS = 20 +_DIALOG_MAX_ATTEMPTS = 100 + + def _drive_dialog_when_visible(action: str) -> None: - """Schedule a one-shot Qt timer that finds the modal dialog and + """Schedule a bounded Qt timer chain that finds the modal dialog and presses Allow / Deny / cancel on it. """ + remaining = {"attempts": _DIALOG_MAX_ATTEMPTS} + def attempt(): for widget in QApplication.topLevelWidgets(): if isinstance(widget, UsbPassthroughPromptDialog) and widget.isVisible(): @@ -86,8 +99,11 @@ def attempt(): else: widget.reject() return - # Try again shortly if the dialog hasn't appeared yet. - QTimer.singleShot(20, attempt) + # Try again shortly if the dialog hasn't appeared yet — but give up + # rather than outlive this test. + remaining["attempts"] -= 1 + if remaining["attempts"] > 0: + QTimer.singleShot(_DIALOG_RETRY_INTERVAL_MS, attempt) QTimer.singleShot(50, attempt) @@ -95,13 +111,17 @@ def _close_dialog_after(ms: int) -> None: """Reject the prompt once it is up — used by the timeout case so the GUI thread's modal loop unwinds after ``decide`` has already given up. """ + remaining = {"attempts": _DIALOG_MAX_ATTEMPTS} + def shut(): for widget in QApplication.topLevelWidgets(): if (isinstance(widget, UsbPassthroughPromptDialog) and widget.isVisible()): widget.reject() return - QTimer.singleShot(20, shut) + remaining["attempts"] -= 1 + if remaining["attempts"] > 0: + QTimer.singleShot(_DIALOG_RETRY_INTERVAL_MS, shut) QTimer.singleShot(ms, shut) diff --git a/test/unit_test/headless/test_usb_browser_tab.py b/test/unit_test/headless/test_usb_browser_tab.py index fb68c4e0..78d0f3a5 100644 --- a/test/unit_test/headless/test_usb_browser_tab.py +++ b/test/unit_test/headless/test_usb_browser_tab.py @@ -6,7 +6,7 @@ # fetch_remote_devices is pure, but it lives next to a Qt widget that # transitively pulls aiortc via gui/__init__.py. Skip the whole file # unless the webrtc extra is installed. -pytest.importorskip("PySide6.QtWidgets") +pytest.importorskip("PySide6.QtWidgets", exc_type=ImportError) pytest.importorskip("av") pytest.importorskip("aiortc") diff --git a/test/unit_test/headless/test_usb_passthrough_panel.py b/test/unit_test/headless/test_usb_passthrough_panel.py index 4be0215e..ab7acad8 100644 --- a/test/unit_test/headless/test_usb_passthrough_panel.py +++ b/test/unit_test/headless/test_usb_passthrough_panel.py @@ -8,7 +8,7 @@ import pytest -pytest.importorskip("PySide6.QtWidgets") +pytest.importorskip("PySide6.QtWidgets", exc_type=ImportError) # Force a headless Qt platform before any QApplication is created. os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") diff --git a/test/unit_test/headless/test_usbip.py b/test/unit_test/headless/test_usbip.py index 897ec129..b46cd771 100644 --- a/test/unit_test/headless/test_usbip.py +++ b/test/unit_test/headless/test_usbip.py @@ -241,6 +241,34 @@ def test_server_forwards_urb_to_backend(): server.stop() +# --- lifecycle race -------------------------------------------------- + +def test_rapid_start_stop_never_leaks_a_thread_exception(monkeypatch): + """A concurrent stop() must not blow up the accept thread. + + Regression: start() published the listening socket and left the accept + thread to call settimeout() on it, outside any try. A stop() closing the + socket in that window made settimeout raise WSAENOTSOCK (WinError 10038) + with nothing to catch it — the accept thread died with an unhandled + traceback on ~18% of tight start/stop cycles. The fix sets the timeout on + the owning thread in start(), before publishing the socket. + """ + caught: list = [] + original = threading.excepthook + threading.excepthook = lambda args: caught.append( + (args.thread.name, type(args.exc_value).__name__)) + try: + for _ in range(80): + server = UsbIpServer(FakeUrbBackend(devices=[_device()]), + host="127.0.0.1", port=0) + server.start() + server.stop(timeout=0.2) + finally: + threading.excepthook = original + + assert caught == [], f"accept thread raised: {caught[:3]}" + + # --- helpers --------------------------------------------------------- def _recv(sock: socket.socket, n: int) -> bytes: diff --git a/test/unit_test/headless/test_wayland_backend.py b/test/unit_test/headless/test_wayland_backend.py index 0bf6e329..c6a5ad93 100644 --- a/test/unit_test/headless/test_wayland_backend.py +++ b/test/unit_test/headless/test_wayland_backend.py @@ -252,6 +252,69 @@ def test_screenshot_passes_screen_region(): ]] +@pytest.mark.parametrize("direction_name, expected_axis, expected_amount", [ + ("wayland_scroll_direction_down", "-y", "-5"), + ("wayland_scroll_direction_up", "-y", "5"), + ("wayland_scroll_direction_left", "-x", "-5"), + ("wayland_scroll_direction_right", "-x", "5"), +]) +def test_scroll_honours_direction(direction_name, expected_axis, + expected_amount): + """Regression: scroll's signature was ``(direction, x, y)`` while the + wrapper calls ``scroll(scroll_value, scroll_direction)``. The direction + bound to ``x`` and was then dropped, so every direction scrolled the same + way — up and down emitted byte-identical argv. + """ + captured: list = [] + direction = getattr(wayland_mouse, direction_name) + with patch.object(wayland_mouse, "binary_path", + return_value="/usr/bin/ydotool"), \ + patch.object(wayland_mouse.subprocess, "run", + side_effect=_fake_run(captured)): + wayland_mouse.scroll(5, direction) + assert captured[0][1:] == [ + "mousemove", "--wheel", expected_axis, expected_amount, + ] + + +def test_scroll_opposite_directions_are_not_identical(): + """The sharpest form of the regression: up and down must differ.""" + captured: list = [] + with patch.object(wayland_mouse, "binary_path", + return_value="/usr/bin/ydotool"), \ + patch.object(wayland_mouse.subprocess, "run", + side_effect=_fake_run(captured)): + wayland_mouse.scroll(5, wayland_mouse.wayland_scroll_direction_down) + wayland_mouse.scroll(5, wayland_mouse.wayland_scroll_direction_up) + assert captured[0] != captured[1] + + +def test_get_pixel_grabs_a_one_by_one_region_at_the_point(): + """The wrapper calls ``screen.get_pixel``; Wayland had no such function + at all, so every get_pixel raised AttributeError on Wayland.""" + png = _one_pixel_png((10, 20, 30)) + captured: list = [] + + def _run(argv, **_kwargs): + captured.append(argv) + return subprocess.CompletedProcess(argv, 0, png, b"") # nosemgrep + + with patch.object(wayland_screen, "binary_path", + return_value="/usr/bin/grim"), \ + patch.object(wayland_screen.subprocess, "run", side_effect=_run): + assert wayland_screen.get_pixel(7, 9) == (10, 20, 30) + assert "-g" in captured[0] and "7,9 1x1" in captured[0] + + +def _one_pixel_png(rgb) -> bytes: + from io import BytesIO + + from PIL import Image + buffer = BytesIO() + Image.new("RGB", (1, 1), rgb).save(buffer, format="PNG") + return buffer.getvalue() + + def test_screen_size_uses_wlr_randr_when_available(): with patch.object(wayland_screen, "binary_path", side_effect=lambda name: "/usr/bin/" + name), \ @@ -261,7 +324,7 @@ def test_screen_size_uses_wlr_randr_when_available(): ["wlr-randr"], 0, b" HDMI-A-1 1920x1080@60.000Hz\n", b"", ), ): - assert wayland_screen.screen_size() == (1920, 1080) + assert wayland_screen.size() == (1920, 1080) def test_screenshot_raises_when_grim_missing(): diff --git a/uv.lock b/uv.lock index 6ce859d4..1123480b 100644 --- a/uv.lock +++ b/uv.lock @@ -266,6 +266,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "comtypes" +version = "1.4.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/2a/65274c13327f637ec13af8d39f2cf579d9ebe7a0e683696b5f05236d2805/comtypes-1.4.16.tar.gz", hash = "sha256:cd66d1add01265cface4df51ba1e31cd1657e04463c281c802e737e79e1ba93c", size = 260252, upload-time = "2026-03-02T23:11:42.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/7c/0eb685107290b6221c03c46d39214a4e42a124189691cb83ae3228257f46/comtypes-1.4.16-py3-none-any.whl", hash = "sha256:e18d85179ff12955524c5a8c3bc09cb3c0d890f1da4d7123d14244c7b78f84c8", size = 296230, upload-time = "2026-03-02T23:11:41.049Z" }, +] + [[package]] name = "cryptography" version = "49.0.0" @@ -449,6 +458,7 @@ dependencies = [ { name = "defusedxml" }, { name = "je-open-cv" }, { name = "mss" }, + { name = "opencv-python" }, { name = "pillow" }, { name = "pyobjc", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, @@ -456,6 +466,9 @@ dependencies = [ ] [package.optional-dependencies] +audio = [ + { name = "pycaw" }, +] discovery = [ { name = "zeroconf" }, ] @@ -500,10 +513,12 @@ requires-dist = [ { name = "fastapi", marker = "extra == 'signaling'", specifier = ">=0.115" }, { name = "je-open-cv", specifier = "==0.0.22" }, { name = "mss", specifier = "==10.2.0" }, + { name = "opencv-python", specifier = ">=4.8,<6" }, { name = "openpyxl", marker = "extra == 'office'", specifier = ">=3.1" }, - { name = "pillow", specifier = "==12.2.0" }, - { name = "pyobjc", marker = "sys_platform == 'darwin'", specifier = "==12.1" }, - { name = "pyobjc-core", marker = "sys_platform == 'darwin'", specifier = "==12.1" }, + { name = "pillow", specifier = "==12.3.0" }, + { name = "pycaw", marker = "extra == 'audio'", specifier = ">=20240210" }, + { name = "pyobjc", marker = "sys_platform == 'darwin'", specifier = "==12.2.1" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'", specifier = "==12.2.1" }, { name = "pypdf", marker = "extra == 'pdf'", specifier = ">=4.0" }, { name = "pyside6", marker = "extra == 'gui'", specifier = "==6.11.1" }, { name = "python-docx", marker = "extra == 'office'", specifier = ">=1.1" }, @@ -514,7 +529,7 @@ requires-dist = [ { name = "uvicorn", marker = "extra == 'signaling'", specifier = ">=0.32" }, { name = "zeroconf", marker = "extra == 'discovery'", specifier = ">=0.130" }, ] -provides-extras = ["gui", "webrtc", "signaling", "discovery", "pdf", "office", "fuzzy", "s3", "locale"] +provides-extras = ["gui", "webrtc", "signaling", "discovery", "pdf", "office", "fuzzy", "s3", "locale", "audio"] [[package]] name = "je-open-cv" @@ -943,100 +958,137 @@ wheels = [ [[package]] name = "pillow" -version = "12.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/aa/d0b28e1c811cd4d5f5c2bfe2e022292bd255ae5744a3b9ac7d6c8f72dd75/pillow-12.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:a4e8f36e677d3336f35089648c8955c51c6d386a13cf6ee9c189c5f5bd713a9f", size = 5354355, upload-time = "2026-04-01T14:42:15.402Z" }, - { url = "https://files.pythonhosted.org/packages/27/8e/1d5b39b8ae2bd7650d0c7b6abb9602d16043ead9ebbfef4bc4047454da2a/pillow-12.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e589959f10d9824d39b350472b92f0ce3b443c0a3442ebf41c40cb8361c5b97", size = 4695871, upload-time = "2026-04-01T14:42:18.234Z" }, - { url = "https://files.pythonhosted.org/packages/f0/c5/dcb7a6ca6b7d3be41a76958e90018d56c8462166b3ef223150360850c8da/pillow-12.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a52edc8bfff4429aaabdf4d9ee0daadbbf8562364f940937b941f87a4290f5ff", size = 6269734, upload-time = "2026-04-01T14:42:20.608Z" }, - { url = "https://files.pythonhosted.org/packages/ea/f1/aa1bb13b2f4eba914e9637893c73f2af8e48d7d4023b9d3750d4c5eb2d0c/pillow-12.2.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:975385f4776fafde056abb318f612ef6285b10a1f12b8570f3647ad0d74b48ec", size = 8076080, upload-time = "2026-04-01T14:42:23.095Z" }, - { url = "https://files.pythonhosted.org/packages/a1/2a/8c79d6a53169937784604a8ae8d77e45888c41537f7f6f65ed1f407fe66d/pillow-12.2.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd9c0c7a0c681a347b3194c500cb1e6ca9cab053ea4d82a5cf45b6b754560136", size = 6382236, upload-time = "2026-04-01T14:42:25.82Z" }, - { url = "https://files.pythonhosted.org/packages/b5/42/bbcb6051030e1e421d103ce7a8ecadf837aa2f39b8f82ef1a8d37c3d4ebc/pillow-12.2.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88d387ff40b3ff7c274947ed3125dedf5262ec6919d83946753b5f3d7c67ea4c", size = 7070220, upload-time = "2026-04-01T14:42:28.68Z" }, - { url = "https://files.pythonhosted.org/packages/3f/e1/c2a7d6dd8cfa6b231227da096fd2d58754bab3603b9d73bf609d3c18b64f/pillow-12.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:51c4167c34b0d8ba05b547a3bb23578d0ba17b80a5593f93bd8ecb123dd336a3", size = 6493124, upload-time = "2026-04-01T14:42:31.579Z" }, - { url = "https://files.pythonhosted.org/packages/5f/41/7c8617da5d32e1d2f026e509484fdb6f3ad7efaef1749a0c1928adbb099e/pillow-12.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:34c0d99ecccea270c04882cb3b86e7b57296079c9a4aff88cb3b33563d95afaa", size = 7194324, upload-time = "2026-04-01T14:42:34.615Z" }, - { url = "https://files.pythonhosted.org/packages/2d/de/a777627e19fd6d62f84070ee1521adde5eeda4855b5cf60fe0b149118bca/pillow-12.2.0-cp310-cp310-win32.whl", hash = "sha256:b85f66ae9eb53e860a873b858b789217ba505e5e405a24b85c0464822fe88032", size = 6376363, upload-time = "2026-04-01T14:42:37.19Z" }, - { url = "https://files.pythonhosted.org/packages/e7/34/fc4cb5204896465842767b96d250c08410f01f2f28afc43b257de842eed5/pillow-12.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:673aa32138f3e7531ccdbca7b3901dba9b70940a19ccecc6a37c77d5fdeb05b5", size = 7083523, upload-time = "2026-04-01T14:42:39.62Z" }, - { url = "https://files.pythonhosted.org/packages/2d/a0/32852d36bc7709f14dc3f64f929a275e958ad8c19a6deba9610d458e28b3/pillow-12.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:3e080565d8d7c671db5802eedfb438e5565ffa40115216eabb8cd52d0ecce024", size = 2463318, upload-time = "2026-04-01T14:42:42.063Z" }, - { url = "https://files.pythonhosted.org/packages/68/e1/748f5663efe6edcfc4e74b2b93edfb9b8b99b67f21a854c3ae416500a2d9/pillow-12.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab", size = 5354347, upload-time = "2026-04-01T14:42:44.255Z" }, - { url = "https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65", size = 4695873, upload-time = "2026-04-01T14:42:46.452Z" }, - { url = "https://files.pythonhosted.org/packages/df/21/e3fbdf54408a973c7f7f89a23b2cb97a7ef30c61ab4142af31eee6aebc88/pillow-12.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7", size = 6280168, upload-time = "2026-04-01T14:42:49.228Z" }, - { url = "https://files.pythonhosted.org/packages/d3/f1/00b7278c7dd52b17ad4329153748f87b6756ec195ff786c2bdf12518337d/pillow-12.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e", size = 8088188, upload-time = "2026-04-01T14:42:51.735Z" }, - { url = "https://files.pythonhosted.org/packages/ad/cf/220a5994ef1b10e70e85748b75649d77d506499352be135a4989c957b701/pillow-12.2.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705", size = 6394401, upload-time = "2026-04-01T14:42:54.343Z" }, - { url = "https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176", size = 7079655, upload-time = "2026-04-01T14:42:56.954Z" }, - { url = "https://files.pythonhosted.org/packages/6b/3d/45132c57d5fb4b5744567c3817026480ac7fc3ce5d4c47902bc0e7f6f853/pillow-12.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b", size = 6503105, upload-time = "2026-04-01T14:42:59.847Z" }, - { url = "https://files.pythonhosted.org/packages/7d/2e/9df2fc1e82097b1df3dce58dc43286aa01068e918c07574711fcc53e6fb4/pillow-12.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909", size = 7203402, upload-time = "2026-04-01T14:43:02.664Z" }, - { url = "https://files.pythonhosted.org/packages/bd/2e/2941e42858ebb67e50ae741473de81c2984e6eff7b397017623c676e2e8d/pillow-12.2.0-cp311-cp311-win32.whl", hash = "sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808", size = 6378149, upload-time = "2026-04-01T14:43:05.274Z" }, - { url = "https://files.pythonhosted.org/packages/69/42/836b6f3cd7f3e5fa10a1f1a5420447c17966044c8fbf589cc0452d5502db/pillow-12.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60", size = 7082626, upload-time = "2026-04-01T14:43:08.557Z" }, - { url = "https://files.pythonhosted.org/packages/c2/88/549194b5d6f1f494b485e493edc6693c0a16f4ada488e5bd974ed1f42fad/pillow-12.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe", size = 2463531, upload-time = "2026-04-01T14:43:10.743Z" }, - { url = "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279, upload-time = "2026-04-01T14:43:13.246Z" }, - { url = "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421", size = 4695490, upload-time = "2026-04-01T14:43:15.584Z" }, - { url = "https://files.pythonhosted.org/packages/de/af/4e8e6869cbed569d43c416fad3dc4ecb944cb5d9492defaed89ddd6fe871/pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987", size = 6284462, upload-time = "2026-04-01T14:43:18.268Z" }, - { url = "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76", size = 8094744, upload-time = "2026-04-01T14:43:20.716Z" }, - { url = "https://files.pythonhosted.org/packages/2b/54/1789c455ed10176066b6e7e6da1b01e50e36f94ba584dc68d9eebfe9156d/pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005", size = 6398371, upload-time = "2026-04-01T14:43:23.443Z" }, - { url = "https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780", size = 7087215, upload-time = "2026-04-01T14:43:26.758Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f8/2f6825e441d5b1959d2ca5adec984210f1ec086435b0ed5f52c19b3b8a6e/pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5", size = 6509783, upload-time = "2026-04-01T14:43:29.56Z" }, - { url = "https://files.pythonhosted.org/packages/67/f9/029a27095ad20f854f9dba026b3ea6428548316e057e6fc3545409e86651/pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5", size = 7212112, upload-time = "2026-04-01T14:43:32.091Z" }, - { url = "https://files.pythonhosted.org/packages/be/42/025cfe05d1be22dbfdb4f264fe9de1ccda83f66e4fc3aac94748e784af04/pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940", size = 6378489, upload-time = "2026-04-01T14:43:34.601Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5", size = 7084129, upload-time = "2026-04-01T14:43:37.213Z" }, - { url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612, upload-time = "2026-04-01T14:43:39.421Z" }, - { url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" }, - { url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" }, - { url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" }, - { url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" }, - { url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" }, - { url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" }, - { url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" }, - { url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" }, - { url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" }, - { url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" }, - { url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" }, - { url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" }, - { url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" }, - { url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" }, - { url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" }, - { url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" }, - { url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" }, - { url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" }, - { url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" }, - { url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" }, - { url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" }, - { url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" }, - { url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" }, - { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" }, - { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" }, - { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" }, - { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" }, - { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" }, - { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" }, - { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" }, - { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" }, - { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" }, - { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" }, - { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" }, - { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" }, - { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" }, - { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" }, - { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" }, - { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" }, - { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" }, - { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" }, - { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" }, - { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" }, - { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" }, - { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" }, - { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" }, - { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" }, - { url = "https://files.pythonhosted.org/packages/4e/b7/2437044fb910f499610356d1352e3423753c98e34f915252aafecc64889f/pillow-12.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f", size = 5273969, upload-time = "2026-04-01T14:45:55.538Z" }, - { url = "https://files.pythonhosted.org/packages/f6/f4/8316e31de11b780f4ac08ef3654a75555e624a98db1056ecb2122d008d5a/pillow-12.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d", size = 4659674, upload-time = "2026-04-01T14:45:58.093Z" }, - { url = "https://files.pythonhosted.org/packages/d4/37/664fca7201f8bb2aa1d20e2c3d5564a62e6ae5111741966c8319ca802361/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f", size = 5288479, upload-time = "2026-04-01T14:46:01.141Z" }, - { url = "https://files.pythonhosted.org/packages/49/62/5b0ed78fce87346be7a5cfcfaaad91f6a1f98c26f86bdbafa2066c647ef6/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e", size = 7032230, upload-time = "2026-04-01T14:46:03.874Z" }, - { url = "https://files.pythonhosted.org/packages/c3/28/ec0fc38107fc32536908034e990c47914c57cd7c5a3ece4d8d8f7ffd7e27/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0", size = 5355404, upload-time = "2026-04-01T14:46:06.33Z" }, - { url = "https://files.pythonhosted.org/packages/5e/8b/51b0eddcfa2180d60e41f06bd6d0a62202b20b59c68f5a132e615b75aecf/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1", size = 6002215, upload-time = "2026-04-01T14:46:08.83Z" }, - { url = "https://files.pythonhosted.org/packages/bc/60/5382c03e1970de634027cee8e1b7d39776b778b81812aaf45b694dfe9e28/pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e", size = 7080946, upload-time = "2026-04-01T14:46:11.734Z" }, +version = "12.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/c2/669d88644cddb1485bd9534e63e8cf476c8e51cb3c3a1297677023505c0e/pillow-12.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6c0016e7b354317c4e9e525b937ac8596c38d2d232b419529b9cd7a1cd46e39a", size = 5392418, upload-time = "2026-07-01T11:53:27.808Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ba/3762f376a2948e3036488d773a146e0ae6ecc2ca03ac20e2615bd0b2ba02/pillow-12.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bcc33feacfaefce60c12fd500a277533bdc02b10a19f7f6d348763d8140bbba7", size = 4785287, upload-time = "2026-07-01T11:53:29.761Z" }, + { url = "https://files.pythonhosted.org/packages/07/50/b5d688cc9c52d4482f3d5bcab6ce20bc2a74a85d2343841c907444a3be2c/pillow-12.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5594fc43d548a7ed94949d139aa1341b270f1863f11cfd37f5a6c8b778a6b67f", size = 6253754, upload-time = "2026-07-01T11:53:32.298Z" }, + { url = "https://files.pythonhosted.org/packages/4e/89/36f4cd76cf4baf05c50ababb976249153f18c959171c7f6ba09a6f217260/pillow-12.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0606c8bf2cdefea14a43530f7657cbbb7ecf1c4222512492ef4a4434a9501ec", size = 6925605, upload-time = "2026-07-01T11:53:34.487Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c0/4de58cf6633b9e3a6061ef4be6fb91fc3c90b812ece886f531e3c523d777/pillow-12.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:85f998ea1848bc6757289e739cfbdda3a04adfd58b02fc018ce54d754a5ce468", size = 6327788, upload-time = "2026-07-01T11:53:36.433Z" }, + { url = "https://files.pythonhosted.org/packages/87/3c/14d53682a19550dbbaf3b598f807d5457646c510805a44c7d7891cd1cd1a/pillow-12.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:25b9b82bb22e6e2b3cd07b39c68b7b862001226cb3dff7130d1cb914121b39ed", size = 7036288, upload-time = "2026-07-01T11:53:38.712Z" }, + { url = "https://files.pythonhosted.org/packages/38/1d/36279e3c77efe034e4cc2b0393ee74ffdb5a62391dacbf9b916154f5f0b8/pillow-12.3.0-cp310-cp310-win32.whl", hash = "sha256:37dc8f7bbb66efe481bb60defacef820c950c24713fb44962ed6aa2a50966de1", size = 6472396, upload-time = "2026-07-01T11:53:40.781Z" }, + { url = "https://files.pythonhosted.org/packages/48/7c/8fa0039574c476d7c6fa57dd7c32a130436877c6ec1e5ce1cc8ec44878c1/pillow-12.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:300557495eb45ebb8aec96c2da9c4be642fbf7cd937278b4013ba894ea8eb0eb", size = 7226887, upload-time = "2026-07-01T11:53:42.764Z" }, + { url = "https://files.pythonhosted.org/packages/fa/17/e324be141d173c1c919428066c3259f21c1b8982e564e01a4a81e96dbdcf/pillow-12.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:514435a37670e3e5e08f3945b68718b6ed329bb84367777e16f9f4dfe1e61a0f", size = 2568039, upload-time = "2026-07-01T11:53:45.372Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c8/0a78b0e02d7ac54bc03e5321c9220da52f0c2ea83b21f7c40e7f3169c502/pillow-12.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756", size = 5392415, upload-time = "2026-07-01T11:53:47.162Z" }, + { url = "https://files.pythonhosted.org/packages/b2/5b/a02d30018abd97ced9f5a6c63d28597694a00d066516b9c1c6de45859fc9/pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6", size = 4785266, upload-time = "2026-07-01T11:53:49.079Z" }, + { url = "https://files.pythonhosted.org/packages/c8/98/766667a4be768150a202836acd9fad19c06824ca86c4286d3cf6b274964e/pillow-12.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd", size = 6263814, upload-time = "2026-07-01T11:53:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2d/ede717bc1144f63886c21fd349bb95860b0d1a21149ff16f2bb362b612b6/pillow-12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd", size = 6934408, upload-time = "2026-07-01T11:53:53.487Z" }, + { url = "https://files.pythonhosted.org/packages/a3/48/9c58b685e69d49c31af6c8eb9012055fab7e665785165c84796e2c73ce72/pillow-12.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c", size = 6337160, upload-time = "2026-07-01T11:53:55.457Z" }, + { url = "https://files.pythonhosted.org/packages/ff/fa/dc2a5c0ba6df93f67c31d34b808b7ce440b40cdbf96f0b81cde1d1e6fa93/pillow-12.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5", size = 7045172, upload-time = "2026-07-01T11:53:57.736Z" }, + { url = "https://files.pythonhosted.org/packages/86/a5/444817a4d4c4c2417df00513086ca196f388d8f9ef40c2e4ccd1ad1af54b/pillow-12.3.0-cp311-cp311-win32.whl", hash = "sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b", size = 6472232, upload-time = "2026-07-01T11:53:59.767Z" }, + { url = "https://files.pythonhosted.org/packages/63/c6/4bad1b18d132a50b27e1365e1ab163616f7a5bb56d330f66f9d1d9d4f9d4/pillow-12.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a", size = 7233653, upload-time = "2026-07-01T11:54:02.066Z" }, + { url = "https://files.pythonhosted.org/packages/fd/16/00f91ab7760dc842f5aad55217e80fc4a7067a0604535249bc8a2d6d9870/pillow-12.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26", size = 2568195, upload-time = "2026-07-01T11:54:04.622Z" }, + { url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" }, + { url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" }, + { url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" }, + { url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" }, + { url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" }, + { url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" }, + { url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" }, + { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ac/31fb64e1e7efb5a4b50cd3d92049ba89ac6e4d8d3bb6a74e15048ca3353e/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89", size = 4161684, upload-time = "2026-07-01T11:54:25.934Z" }, + { url = "https://files.pythonhosted.org/packages/87/b4/9805e23d2b4d77842b468513841fda254ee42f0289d25088340e4ff46e2d/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace", size = 4255487, upload-time = "2026-07-01T11:54:27.935Z" }, + { url = "https://files.pythonhosted.org/packages/df/39/ecf519435a200c693fe053a6ee4d835b41cf963a4dfc2551c4e637cb2a71/pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec", size = 3696433, upload-time = "2026-07-01T11:54:29.813Z" }, + { url = "https://files.pythonhosted.org/packages/42/92/2fc3ffad878ae8dd5469ec1bc8eb83b71f48e13efdf68f02709003982a32/pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66", size = 5345889, upload-time = "2026-07-01T11:54:31.97Z" }, + { url = "https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35", size = 4780109, upload-time = "2026-07-01T11:54:34.026Z" }, + { url = "https://files.pythonhosted.org/packages/1f/01/e18aff37cb0b4aac47ac90f016d347a49aca667ef97f190b06ac2aabc928/pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65", size = 6263736, upload-time = "2026-07-01T11:54:36.131Z" }, + { url = "https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3", size = 6937129, upload-time = "2026-07-01T11:54:38.216Z" }, + { url = "https://files.pythonhosted.org/packages/70/4d/105627a13300c5e0df1d174230b32fd1273062c96f7745fd552b945d1e1d/pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a", size = 6339562, upload-time = "2026-07-01T11:54:40.354Z" }, + { url = "https://files.pythonhosted.org/packages/6b/1d/f13de01a553988ab895ba1c722e06cf3144d4f57656fd5b81b6d881f1179/pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e", size = 7049439, upload-time = "2026-07-01T11:54:42.489Z" }, + { url = "https://files.pythonhosted.org/packages/c9/f9/066794cca041b969964f779ee5fa66a9498bbf34248ac39c5d7954e4198f/pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f", size = 6473287, upload-time = "2026-07-01T11:54:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/7a58e61d62be561da3a356fe2384d4059a6345fc130e23ef1c36a5b81d24/pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8", size = 7239691, upload-time = "2026-07-01T11:54:47.141Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b0/c4ed4f0ef8f8fa5ee8351537db6650bb8189f7e118842978dd6589065692/pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b", size = 2568185, upload-time = "2026-07-01T11:54:49.137Z" }, + { url = "https://files.pythonhosted.org/packages/dc/01/001f65b68192f0228cc1dbbc8d2530ab5d58b61037ba0587f946fea607cd/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330", size = 4161736, upload-time = "2026-07-01T11:54:51.156Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d2/0219746d0fd16fc8a84498e79452375be3797d3ce4044596ce565164b84f/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217", size = 4255435, upload-time = "2026-07-01T11:54:53.414Z" }, + { url = "https://files.pythonhosted.org/packages/c8/02/8d0bc62ef0302318c46ff2a512822d2610e81c7aa46c9b3abe6cbaca5ad0/pillow-12.3.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930", size = 3696262, upload-time = "2026-07-01T11:54:55.739Z" }, + { url = "https://files.pythonhosted.org/packages/85/e2/73c77d218410b14f5f2d565e8a998d5317b7b9c75368d29985139f7a46f0/pillow-12.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8", size = 5350344, upload-time = "2026-07-01T11:54:57.657Z" }, + { url = "https://files.pythonhosted.org/packages/c7/da/32c752228ae345f489e3a42499d817b6c3996da7e8a3bc7a04fc806b243b/pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0", size = 4780131, upload-time = "2026-07-01T11:54:59.713Z" }, + { url = "https://files.pythonhosted.org/packages/b1/9d/8b2c807dbef61a5197c047afe99823787eb66f63daf9fb2432f91d6f0462/pillow-12.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321", size = 6263757, upload-time = "2026-07-01T11:55:01.778Z" }, + { url = "https://files.pythonhosted.org/packages/5c/44/c85361f65dbe00eea8576ee467c768d25129989efb76e94f205e9ca9bb46/pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b", size = 6936962, upload-time = "2026-07-01T11:55:03.93Z" }, + { url = "https://files.pythonhosted.org/packages/18/7e/e483414b35800b86b6f08dbbc7803fb5cd52c4d6f897f47d53ea2c7e6f65/pillow-12.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198", size = 6339171, upload-time = "2026-07-01T11:55:05.989Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f4/68c491844841ede6bed70189546b3ee9731cf9f2cbad396faff5e1ccba45/pillow-12.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130", size = 7048116, upload-time = "2026-07-01T11:55:08.131Z" }, + { url = "https://files.pythonhosted.org/packages/a3/34/77f3f793fed8efc7d243f21b33c5a3f0d1c97ee70346d3db855587e155ff/pillow-12.3.0-cp314-cp314-win32.whl", hash = "sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a", size = 6467209, upload-time = "2026-07-01T11:55:10.408Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e0/492879f69d94f91f60fc8cd05ba03650e9520afebb2fb7aa12777d7c7f38/pillow-12.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d", size = 7237707, upload-time = "2026-07-01T11:55:12.745Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ac/6b11f2875f1c2ac040d84e1bbf9cf22a88038f901ca1037898b280b38365/pillow-12.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838", size = 2565995, upload-time = "2026-07-01T11:55:14.736Z" }, + { url = "https://files.pythonhosted.org/packages/52/69/c2208e56af9bfc1913afb24020297a691eb1d4ef688474c8a04913f65e04/pillow-12.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e", size = 5352503, upload-time = "2026-07-01T11:55:17.076Z" }, + { url = "https://files.pythonhosted.org/packages/07/70/e5686d753e898a45d778ff1718dba8516ead6ab6b95d85fc8c4b70650cf2/pillow-12.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17", size = 4782956, upload-time = "2026-07-01T11:55:19.448Z" }, + { url = "https://files.pythonhosted.org/packages/d5/37/25c6692f06927ee973ff18c8d9ee98ad0b4d84ee67a09610c2dd1447958e/pillow-12.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385", size = 6322855, upload-time = "2026-07-01T11:55:21.613Z" }, + { url = "https://files.pythonhosted.org/packages/cc/91/420637fcb8f1bc11029e403b4538e6694744428d8246118e45719f944556/pillow-12.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c", size = 6989642, upload-time = "2026-07-01T11:55:24.006Z" }, + { url = "https://files.pythonhosted.org/packages/10/08/b94d7811281ccf0d143a1cf768d1c49e1e54af63e7b708ab2ee3eb87face/pillow-12.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d", size = 6391281, upload-time = "2026-07-01T11:55:26.252Z" }, + { url = "https://files.pythonhosted.org/packages/d2/87/24233f785f55474dc02ce3e739c5528a77e3a862e9333d1dd7a25cc31f70/pillow-12.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931", size = 7096716, upload-time = "2026-07-01T11:55:28.318Z" }, + { url = "https://files.pythonhosted.org/packages/23/26/fcb2f6e37175b04f53570b59937867e2b80ee1685e744023153028fc14f9/pillow-12.3.0-cp314-cp314t-win32.whl", hash = "sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7", size = 6474125, upload-time = "2026-07-01T11:55:30.956Z" }, + { url = "https://files.pythonhosted.org/packages/90/de/3634abee5f1c9e13c56787b7d5517b0ba8d6de51700b95578cf338349c9f/pillow-12.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c", size = 7242939, upload-time = "2026-07-01T11:55:34.044Z" }, + { url = "https://files.pythonhosted.org/packages/ce/2a/fd13f8eb24de5714a6eb444a3d67e2842c6c576e159a43793adf23051351/pillow-12.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45", size = 2567506, upload-time = "2026-07-01T11:55:35.988Z" }, + { url = "https://files.pythonhosted.org/packages/5d/dc/8fdce34ec725a33c81c6ba122b904d6b9024e50ea9ac7bede62fab54506c/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0feb2e9d6ad6c9e3c06effe9d00f3f1e618a6643273576b016f591e9315a7139", size = 4162063, upload-time = "2026-07-01T11:55:37.941Z" }, + { url = "https://files.pythonhosted.org/packages/76/66/2044b9a63d3b84ff048228dfcb7cd9bf0df983e8470971bf7d4c57b693de/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9e881fca225083806662a5c43d627d215f258ff43c890f831966c7d7ba9c7402", size = 4255549, upload-time = "2026-07-01T11:55:40.022Z" }, + { url = "https://files.pythonhosted.org/packages/52/7e/1f67e6f4ece6b582ee4b539decbcc9f848dc245a93ed8cd7338bafef72f1/pillow-12.3.0-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:4998562bf62a445225f22e07c896bb04b35b1b1f2eb6d760584c9c51d7a5f78c", size = 3696331, upload-time = "2026-07-01T11:55:41.98Z" }, + { url = "https://files.pythonhosted.org/packages/12/40/d306fc2c8e4d45d7f175c77edca7063be7b86fe7fe6e68f4353bf71d808c/pillow-12.3.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:dc624f6bc473dacdf7ef7eb8678d0d08edf15cd94fad6ae5c7d6cc67a4e4902f", size = 5350370, upload-time = "2026-07-01T11:55:44.028Z" }, + { url = "https://files.pythonhosted.org/packages/dd/44/668fb1437e8ce420f62d6106eb66e44a5971602a4d794615bdf79315d82d/pillow-12.3.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:71d6097b330eea8fd15097780c8e89cb1a8ce7838669f48c5bacd6f663dd4701", size = 4780147, upload-time = "2026-07-01T11:55:46.073Z" }, + { url = "https://files.pythonhosted.org/packages/0c/08/93fa2e70e30a2d81547e481b6ee2bb9522117221fb1e0ce4b5df70967677/pillow-12.3.0-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:28ce87c5ab450a9dd970b52e5aca5fe63ed432d18a2eaddd1979a00a1ba24ace", size = 6273659, upload-time = "2026-07-01T11:55:48.264Z" }, + { url = "https://files.pythonhosted.org/packages/f8/6d/043e96ff814fc31a33077e4cba86082167db520c93632afdf2042febbb0c/pillow-12.3.0-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b02afb9b97f65fbca5f31db6a2a3ba21aa93030225f150fa3f249717e938fb4", size = 6947439, upload-time = "2026-07-01T11:55:50.503Z" }, + { url = "https://files.pythonhosted.org/packages/af/92/ba71d2ee2ac0edf3fa33bd9d5ee9ee080da70b1766f3ca3934f9938ddac9/pillow-12.3.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:1182d52bc2d5e5d7d0949503aa7e36d12f42205dc287e4883f407b1988820d39", size = 6353577, upload-time = "2026-07-01T11:55:52.697Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ce/e63064e2122923ff687c8ad792d0d736a7b3920a56a46982e81a7fdd25d6/pillow-12.3.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e795b7eb908249c4e43c7c99fac7c2c75dab0c43566e37db472a355f63693d71", size = 7060394, upload-time = "2026-07-01T11:55:55.149Z" }, + { url = "https://files.pythonhosted.org/packages/54/76/a09cc3ccc8d773a7283d34c38bec1708f9e3cc932093cbc4c5e71ac4060b/pillow-12.3.0-cp315-cp315-win32.whl", hash = "sha256:57b3d78c95ba9059768b10e28b813002261d3f3dfc55cc48b0c988f625175827", size = 6467375, upload-time = "2026-07-01T11:55:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/3e/03/1846c49ba3b1d5550392a4bbd06d6fb4578e1cd91a803198b5c90f5f7d53/pillow-12.3.0-cp315-cp315-win_amd64.whl", hash = "sha256:fa4ecea169a355be7a3ade2c783e2ed12f0e40d2c5621cda8b3297faf7fbb9f5", size = 7237048, upload-time = "2026-07-01T11:55:59.975Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bb/89f35dcc79610423f9f195504d7def7f0d1416a711541b42867e25fe3412/pillow-12.3.0-cp315-cp315-win_arm64.whl", hash = "sha256:877c3f311ff35410f690861c4409e7ccbf0cd2f878e50628a28e5a0bb689e658", size = 2566006, upload-time = "2026-07-01T11:56:02.143Z" }, + { url = "https://files.pythonhosted.org/packages/30/88/707027ba09942dfa2c28759b5c222d769290a41c6d20ea60ec250801941f/pillow-12.3.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:e9871b1ffbfa9656b60aeee92ed5136a5742696006fa322b29ea3d8da0ecc9cf", size = 5352509, upload-time = "2026-07-01T11:56:04.2Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6d/00352fa25332c2569cd387851f568cc5a4b75a9adbfb37ac4fbce4c02eec/pillow-12.3.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:53aa02d20d10c3d814d536aa4e5ac9b84ca0ff5a88377963b085ad6822f93e64", size = 4783167, upload-time = "2026-07-01T11:56:06.631Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/9e049dfa21af7c22427275720e2490267ba8138120add5c4c574deb69782/pillow-12.3.0-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:446c34dcc4324b084a53b705127dc15717b22c5e140ae0a3c38349d4efec071e", size = 6329237, upload-time = "2026-07-01T11:56:08.868Z" }, + { url = "https://files.pythonhosted.org/packages/36/16/cf6eeaae8d0fce8dd390a33437cf68c5d5bd73834a2bc6e2f14efda0ab45/pillow-12.3.0-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf1845d02ad822a369a49f2bb9345b1614744267682e7a03527dc3bf6eea1777", size = 6997047, upload-time = "2026-07-01T11:56:11.379Z" }, + { url = "https://files.pythonhosted.org/packages/1e/69/dbf769bdd55f48bf5733cac28edc6364ffaa072ec9ba336266e4fe66be55/pillow-12.3.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:186941b6aef820ad110fb01fb06eb925374dc3a21b17e37ec9a53b250c6fe2d1", size = 6400440, upload-time = "2026-07-01T11:56:13.908Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e1/ffc9cfc2eea0d178da8018e18e959301ad9d6bc9f3edb7181e748a474b97/pillow-12.3.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f13c32a3abd6079a66d9526e18dad9b6d280384d49d7c54040cd57b6424041d9", size = 7105895, upload-time = "2026-07-01T11:56:16.575Z" }, + { url = "https://files.pythonhosted.org/packages/18/f0/a5595c1e8c3ae44b9828cb2f0fa8155e5095ef04d6327b8f61cf44a3df85/pillow-12.3.0-cp315-cp315t-win32.whl", hash = "sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8", size = 6474384, upload-time = "2026-07-01T11:56:18.855Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/62bcd9f844984c5938d3b05264a61d797a29d3e0812341a8204af70bbdee/pillow-12.3.0-cp315-cp315t-win_amd64.whl", hash = "sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418", size = 7243537, upload-time = "2026-07-01T11:56:21.214Z" }, + { url = "https://files.pythonhosted.org/packages/3d/68/1f3066acedf37673694a7141381d8f811ae97f30d34413d236abe7d489f1/pillow-12.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59", size = 2567491, upload-time = "2026-07-01T11:56:23.506Z" }, + { url = "https://files.pythonhosted.org/packages/75/18/2e8b40223153ccbc60df07f9e8928dc0c76202aa4e55ae9f53962b6510d6/pillow-12.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468", size = 5302510, upload-time = "2026-07-01T11:56:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/46/3e/51fabf59d5ab801ceab709453d3ab6b180083496579549de4c45ced6528a/pillow-12.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94", size = 4736058, upload-time = "2026-07-01T11:56:28.041Z" }, + { url = "https://files.pythonhosted.org/packages/bf/20/22fe9384b7949e25fb1293bcfc84fb82590ff4ea6b37c95b24d26d793d86/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e", size = 5237776, upload-time = "2026-07-01T11:56:30.263Z" }, + { url = "https://files.pythonhosted.org/packages/08/14/f6ba68107680ffa74b39985f3f30884e41318fbc4250caa423c79b4788bb/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7e480451b9fa137494bccd3a7d69adbe8ac65a87d97be61e11f1b1050a5bac3", size = 5860358, upload-time = "2026-07-01T11:56:32.68Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0169bc772ec491108b62f644f8ecf1fe5d8ae5ebafde2ee2142210166903/pillow-12.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a", size = 7231786, upload-time = "2026-07-01T11:56:35.046Z" }, +] + +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + +[[package]] +name = "pycaw" +version = "20251023" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "comtypes" }, + { name = "psutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/81/19181e3118a503f94d8689fbbed60616e85d08cee3a295c11245ae73018f/pycaw-20251023.tar.gz", hash = "sha256:19935b92eb5efc3f3ea5ee8a2d37ca49009e9dc00f83d067b74b9922e84b5b3b", size = 23421, upload-time = "2025-10-23T18:26:45.087Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/bc/f60c1622da5c53770c2d62457b5b6fc9623ab41b9d276269b857cd337e22/pycaw-20251023-py3-none-any.whl", hash = "sha256:ef06d234a2fe3c175925a2a8c7ed8824e450851014fdb7d900af1b56ce422d4e", size = 25849, upload-time = "2025-10-23T18:26:43.955Z" }, ] [[package]] @@ -1215,7 +1267,7 @@ wheels = [ [[package]] name = "pyobjc" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, @@ -1380,133 +1432,139 @@ dependencies = [ { name = "pyobjc-framework-vision", marker = "platform_release >= '17.0'" }, { name = "pyobjc-framework-webkit" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/17/06/d77639ba166cc09aed2d32ae204811b47bc5d40e035cdc9bff7fff72ec5f/pyobjc-12.1.tar.gz", hash = "sha256:686d6db3eb3182fac9846b8ce3eedf4c7d2680b21b8b8d6e6df054a17e92a12d", size = 11345, upload-time = "2025-11-14T10:07:28.155Z" } +sdist = { url = "https://files.pythonhosted.org/packages/73/ef/b4e64fe87051e72608ed4134072e832e9eae28d97e9c9bb0870f01a18ac5/pyobjc-12.2.1.tar.gz", hash = "sha256:0b2cf49d24213e7604620c31863e7b4e42770c4442c10e59b18ad951cd200cd3", size = 12148, upload-time = "2026-06-19T16:19:38.283Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/00/1085de7b73abf37ec27ad59f7a1d7a406e6e6da45720bced2e198fdf1ddf/pyobjc-12.1-py3-none-any.whl", hash = "sha256:6f8c36cf87b1159d2ca1aa387ffc3efcd51cc3da13ef47c65f45e6d9fbccc729", size = 4226, upload-time = "2025-11-14T09:30:25.185Z" }, + { url = "https://files.pythonhosted.org/packages/12/a2/3878e4783c65eeb72a1c06de1f880ef573b9677198a3142ff5082c0810ac/pyobjc-12.2.1-py3-none-any.whl", hash = "sha256:edbbf9e2e249cd2fa660422c7d72e9fe5be1438deab38c64238b26f4c9db9421", size = 4262, upload-time = "2026-06-19T12:50:03.55Z" }, ] [[package]] name = "pyobjc-core" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b8/b6/d5612eb40be4fd5ef88c259339e6313f46ba67577a95d86c3470b951fce0/pyobjc_core-12.1.tar.gz", hash = "sha256:2bb3903f5387f72422145e1466b3ac3f7f0ef2e9960afa9bcd8961c5cbf8bd21", size = 1000532, upload-time = "2025-11-14T10:08:28.292Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/b1/729f7458a63758bd21716648a8abcd9a0c8f2d2e9897763c8a1a1c7fd31b/pyobjc_core-12.2.1.tar.gz", hash = "sha256:7a7b9b018402342cf32bf1956366896350fbe5c0478cb3ef59778f77abed7f07", size = 1063383, upload-time = "2026-06-19T16:19:39.357Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/bf/3dbb1783388da54e650f8a6b88bde03c101d9ba93dfe8ab1b1873f1cd999/pyobjc_core-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:93418e79c1655f66b4352168f8c85c942707cb1d3ea13a1da3e6f6a143bacda7", size = 676748, upload-time = "2025-11-14T09:30:50.023Z" }, - { url = "https://files.pythonhosted.org/packages/95/df/d2b290708e9da86d6e7a9a2a2022b91915cf2e712a5a82e306cb6ee99792/pyobjc_core-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c918ebca280925e7fcb14c5c43ce12dcb9574a33cccb889be7c8c17f3bcce8b6", size = 671263, upload-time = "2025-11-14T09:31:35.231Z" }, - { url = "https://files.pythonhosted.org/packages/64/5a/6b15e499de73050f4a2c88fff664ae154307d25dc04da8fb38998a428358/pyobjc_core-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:818bcc6723561f207e5b5453efe9703f34bc8781d11ce9b8be286bb415eb4962", size = 678335, upload-time = "2025-11-14T09:32:20.107Z" }, - { url = "https://files.pythonhosted.org/packages/f4/d2/29e5e536adc07bc3d33dd09f3f7cf844bf7b4981820dc2a91dd810f3c782/pyobjc_core-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:01c0cf500596f03e21c23aef9b5f326b9fb1f8f118cf0d8b66749b6cf4cbb37a", size = 677370, upload-time = "2025-11-14T09:33:05.273Z" }, - { url = "https://files.pythonhosted.org/packages/1b/f0/4b4ed8924cd04e425f2a07269943018d43949afad1c348c3ed4d9d032787/pyobjc_core-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:177aaca84bb369a483e4961186704f64b2697708046745f8167e818d968c88fc", size = 719586, upload-time = "2025-11-14T09:33:53.302Z" }, - { url = "https://files.pythonhosted.org/packages/25/98/9f4ed07162de69603144ff480be35cd021808faa7f730d082b92f7ebf2b5/pyobjc_core-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:844515f5d86395b979d02152576e7dee9cc679acc0b32dc626ef5bda315eaa43", size = 670164, upload-time = "2025-11-14T09:34:37.458Z" }, - { url = "https://files.pythonhosted.org/packages/62/50/dc076965c96c7f0de25c0a32b7f8aa98133ed244deaeeacfc758783f1f30/pyobjc_core-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:453b191df1a4b80e756445b935491b974714456ae2cbae816840bd96f86db882", size = 712204, upload-time = "2025-11-14T09:35:24.148Z" }, + { url = "https://files.pythonhosted.org/packages/e5/62/80fe6b6bea9e9e7abe2e7a91bfd22115219b2263e435d2da09cf480b6f49/pyobjc_core-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:aa5c889961d79d7704f17eeb27f80ee791335819c1bb14babeff7b9ea665b5f0", size = 6486390, upload-time = "2026-06-19T13:29:49.457Z" }, + { url = "https://files.pythonhosted.org/packages/92/87/16564ef5e4568ee0edd9e712d8111dc8b67621d6bb6ff430646ee2d637dd/pyobjc_core-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:24b76a63caf0b5369d4a377c7c0438cd70df81539057af3db839bfaa3579e04a", size = 6484662, upload-time = "2026-06-19T16:04:44.979Z" }, + { url = "https://files.pythonhosted.org/packages/8c/88/300ad283bed0c971c52dcac6f70113e138169d4ce6d856ddd03d16081e51/pyobjc_core-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a64232bb27ed101d4adc7d42b0e64a6d3331aac7bee7861c037a6777a163f10b", size = 6433347, upload-time = "2026-06-19T16:04:49.341Z" }, + { url = "https://files.pythonhosted.org/packages/3e/1e/b9b0ddffae66996b8779f1f7958adc9f21c13a0448cd3be8d7fe589b5b0f/pyobjc_core-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:af101222762665a4125157906cb4b23f5d5a63d3851d5e0504f72a1eaaa2cfd2", size = 6436004, upload-time = "2026-06-19T16:04:53.257Z" }, + { url = "https://files.pythonhosted.org/packages/8f/26/bd309ede07784c6e5fac4b440c90a5f72a66da7859ed303a9392fe8a5f3f/pyobjc_core-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:efe465e3ecc6fc73f7c7622620345d134a8d34564ab1c29d8247e45f4ed55071", size = 6687044, upload-time = "2026-06-19T16:04:57.42Z" }, + { url = "https://files.pythonhosted.org/packages/bd/8a/cfa4f56939d554dbb342ec6e5226a441e2f552bc2002a0ddf7705bb11bef/pyobjc_core-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:2b8fc0531c27277325e113ac00b8a72a82e6145f0a88175b9425d8de814ff69a", size = 6429289, upload-time = "2026-06-19T16:05:02.191Z" }, + { url = "https://files.pythonhosted.org/packages/42/74/446c89bc18103aaa4a00d1fb85ff8acace9a0dc3f362d9678ebf7571e275/pyobjc_core-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9bef500f979e22d54f9da3aaebf6a48f873234b324858bd69256055a318955c7", size = 6690181, upload-time = "2026-06-19T16:05:06.201Z" }, + { url = "https://files.pythonhosted.org/packages/99/c7/0121ee4c616af07ad2de8cd1a286f6978dc9a227eb58b7c2e875cb68a1df/pyobjc_core-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:047c226eeb58a2993ace5e8904e71cc9426ee20d064c617f8fbf32717d37093e", size = 6487078, upload-time = "2026-06-19T16:05:10.093Z" }, + { url = "https://files.pythonhosted.org/packages/b5/a8/cb9fcc150f97d0bf22a2028f88b24cc35949beb1bcc7b8bc5c17d4401677/pyobjc_core-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:1188613805336270279570467e4455b74cb6c0f60913ac74c917ee1c37cfaecb", size = 6733064, upload-time = "2026-06-19T16:05:14.313Z" }, ] [[package]] name = "pyobjc-framework-accessibility" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-quartz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2d/87/8ca40428d05a668fecc638f2f47dba86054dbdc35351d247f039749de955/pyobjc_framework_accessibility-12.1.tar.gz", hash = "sha256:5ff362c3425edc242d49deec11f5f3e26e565cefb6a2872eda59ab7362149772", size = 29800, upload-time = "2025-11-14T10:08:31.949Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/4b/b0a0f0183b359a07663ed31a3f790986cf880bda909b623fead15cb2afbd/pyobjc_framework_accessibility-12.2.1.tar.gz", hash = "sha256:1e0ad06b5b6ae623f443d15c11780f97908d5c41fdb79532e96c6a4a76066fd8", size = 34377, upload-time = "2026-06-19T16:19:40.592Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/49/ff2da96d70cd96d5cc16015be0103474be75c71fb5c56e35d0a39517c4a2/pyobjc_framework_accessibility-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ca72a049a9dc56686cfe7b8c0bbb556205fb7a955a48cabf7f90447708d89651", size = 11300, upload-time = "2025-11-14T09:35:26.7Z" }, - { url = "https://files.pythonhosted.org/packages/76/00/182c57584ad8e5946a82dacdc83c9791567e10bffdea1fe92272b3fdec14/pyobjc_framework_accessibility-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5e29dac0ce8327cd5a8b9a5a8bd8aa83e4070018b93699e97ac0c3af09b42a9a", size = 11301, upload-time = "2025-11-14T09:35:28.678Z" }, - { url = "https://files.pythonhosted.org/packages/cc/95/9ea0d1c16316b4b5babf4b0515e9a133ac64269d3ec031f15ee9c7c2a8c1/pyobjc_framework_accessibility-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:537691a0b28fedb8385cd093df069a6e5d7e027629671fc47b50210404eca20b", size = 11335, upload-time = "2025-11-14T09:35:30.81Z" }, - { url = "https://files.pythonhosted.org/packages/40/71/aa9625b1b064f7d3e1bbc0b6b40cf92d1d46c7f798e0b345594d626f5510/pyobjc_framework_accessibility-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:44d872d8a1f9d1569da0590c5a9185d2c02dc2e08e410c84a03aa54ca6e05c2c", size = 11352, upload-time = "2025-11-14T09:35:32.967Z" }, - { url = "https://files.pythonhosted.org/packages/e9/d8/ff4c720d6140f7a20eaed15d5430af1fc8be372998674b82931993177261/pyobjc_framework_accessibility-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4b9e2079ad0da736ba32a10e63698ff1db9667b5f6342a81220aa86cfa0de8c8", size = 11521, upload-time = "2025-11-14T09:35:35.112Z" }, - { url = "https://files.pythonhosted.org/packages/98/ce/21a076746ada1c03015ce23ee87aa3a3f052885ec386296d4d90c4fb0eb2/pyobjc_framework_accessibility-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0a14c794af7f38d8b59f6d7b03f708e61473a42d4a43663e7a2a6355121d11f7", size = 11414, upload-time = "2025-11-14T09:35:36.92Z" }, - { url = "https://files.pythonhosted.org/packages/22/f0/a195f213d7bbcd765d216a90904a2104199da734bae81c10da9736ebd55d/pyobjc_framework_accessibility-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:bc517a0eff3989ea98197858fbe4bbb4c673e171f4acbb94dc8cf94415b11e0b", size = 11594, upload-time = "2025-11-14T09:35:38.763Z" }, + { url = "https://files.pythonhosted.org/packages/e6/76/44fcae21a640ecb41acc43679bc20bea83ca3083b5ba4a10ac85103bc2bc/pyobjc_framework_accessibility-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0422e40d207f4fd04ba9d3b097f42bd4d9f6ac76b398e47b5b8ccb0f295d024", size = 11542, upload-time = "2026-06-19T16:05:15.893Z" }, + { url = "https://files.pythonhosted.org/packages/81/b8/6937060aca105b75d17fcc296341c12ad2028ab901af784fec40d92c5be4/pyobjc_framework_accessibility-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b2064190604ed1ecfb05fc6a0fd04b7ceb2d9d3ebd02d6899607b5635ac9b12d", size = 11542, upload-time = "2026-06-19T16:05:17.024Z" }, + { url = "https://files.pythonhosted.org/packages/43/22/28a3096e9bb6332d851a7bb4191d6591088165a8465d3540e82a7f3fd9fe/pyobjc_framework_accessibility-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a906f4d6447a6394f10fc7d77cfb755815fb07cdb97c9e3018bd7c528600df3f", size = 11570, upload-time = "2026-06-19T16:05:17.802Z" }, + { url = "https://files.pythonhosted.org/packages/f6/70/0630c4c67f262d3d018aee43d185d8e58a8aa32a34222be3177fe9a8c21a/pyobjc_framework_accessibility-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d6814e863cc1a29e62d9ec97f7a9535598e46748b9d734cf0b903d190fe0e224", size = 11586, upload-time = "2026-06-19T16:05:18.772Z" }, + { url = "https://files.pythonhosted.org/packages/10/c4/6966cd83fb01c183d778799de67100761f7ffe9b7de34b543c4b5034c7a0/pyobjc_framework_accessibility-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5213d57f9b1d6a7b452af78f904e3eed1b4372e9a850e772cf275177038c8512", size = 11756, upload-time = "2026-06-19T16:05:19.545Z" }, + { url = "https://files.pythonhosted.org/packages/be/2a/6f0e49d8a76891f1416e9ee8ca8e89fdc353a2770c6651512803d6de4ccf/pyobjc_framework_accessibility-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:cd6d0740f72f99bea723b3ea0b3fc9c8a4bb15965ecaeca6b534dd5c5c766048", size = 11650, upload-time = "2026-06-19T16:05:20.388Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e4/ba2f8ad4a4d2b29c35b6370ffdac9dbe1f39552b1c9d8174940814d40b0f/pyobjc_framework_accessibility-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:327885b50b9d7a46844c063330ee8e6d76d1e68916e9eb92a8ad00657baee014", size = 11832, upload-time = "2026-06-19T16:05:21.142Z" }, + { url = "https://files.pythonhosted.org/packages/8b/c7/924357300836590c51614c6fe51a684818a973d88fe07c79088bab802f73/pyobjc_framework_accessibility-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:6b229742dc42a337b32314563bb6d2ff8aafdb1ff1c951e86db5918412351437", size = 11640, upload-time = "2026-06-19T16:05:21.966Z" }, + { url = "https://files.pythonhosted.org/packages/80/27/9eafd7a630657accd3ead893418d5aed203883ec6b2bd85294ae64ceadb8/pyobjc_framework_accessibility-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:97d564e2c7e7b12aaa2acfaa1a22c50b6606d0198f4beb1a641b46f50d2ee8d3", size = 11829, upload-time = "2026-06-19T16:05:22.966Z" }, ] [[package]] name = "pyobjc-framework-accounts" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/65/10/f6fe336c7624d6753c1f6edac102310ce4434d49b548c479e8e6420d4024/pyobjc_framework_accounts-12.1.tar.gz", hash = "sha256:76d62c5e7b831eb8f4c9ca6abaf79d9ed961dfffe24d89a041fb1de97fe56a3e", size = 15202, upload-time = "2025-11-14T10:08:33.995Z" } +sdist = { url = "https://files.pythonhosted.org/packages/08/14/6086edbaeb0f48ac1b915d38d11a36efd8de918277da86abc2058a50baad/pyobjc_framework_accounts-12.2.1.tar.gz", hash = "sha256:6e6d603e10182238cd77596380262a38cbb0a9141d1eca6bf522b2213c6d6751", size = 16209, upload-time = "2026-06-19T16:19:41.451Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/70/5f9214250f92fbe2e07f35778875d2771d612f313af2a0e4bacba80af28e/pyobjc_framework_accounts-12.1-py2.py3-none-any.whl", hash = "sha256:e1544ad11a2f889a7aaed649188d0e76d58595a27eec07ca663847a7adb21ae5", size = 5104, upload-time = "2025-11-14T09:35:40.246Z" }, + { url = "https://files.pythonhosted.org/packages/15/2b/f8138fa91af3ada0bae01dc5430f0f3a79daeabfd36e02ac165423f67041/pyobjc_framework_accounts-12.2.1-py2.py3-none-any.whl", hash = "sha256:b86cba0f2a9219a1c57aba6c0f0973f7a06f10bf9ccd39b74fdb567f9b34a041", size = 5133, upload-time = "2026-06-19T16:05:23.786Z" }, ] [[package]] name = "pyobjc-framework-addressbook" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/18/28/0404af2a1c6fa8fd266df26fb6196a8f3fb500d6fe3dab94701949247bea/pyobjc_framework_addressbook-12.1.tar.gz", hash = "sha256:c48b740cf981103cef1743d0804a226d86481fcb839bd84b80e9a586187e8000", size = 44359, upload-time = "2025-11-14T10:08:37.687Z" } +sdist = { url = "https://files.pythonhosted.org/packages/29/20/70dad64d397f9ba513a314ad4d1e1f7e4903c21caee98dfab2f7a39aaedb/pyobjc_framework_addressbook-12.2.1.tar.gz", hash = "sha256:bb113fd5bcae93da00d67bf870704d2cfb73da49c4a281249d8a5abf9809aa91", size = 47685, upload-time = "2026-06-19T16:19:42.321Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/e0/e483b773845f7944a008d060361deca8eef3692674a0c9c126fc0a1fd143/pyobjc_framework_addressbook-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e6291d67436112057df27f76d758a8fb9a6ff1557420dee0baf52e61cf174872", size = 12881, upload-time = "2025-11-14T09:35:42.721Z" }, - { url = "https://files.pythonhosted.org/packages/9f/5a/2ecaa94e5f56c6631f0820ec4209f8075c1b7561fe37495e2d024de1c8df/pyobjc_framework_addressbook-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:681755ada6c95bd4a096bc2b9f9c24661ffe6bff19a96963ee3fad34f3d61d2b", size = 12879, upload-time = "2025-11-14T09:35:45.21Z" }, - { url = "https://files.pythonhosted.org/packages/b6/33/da709c69cbb60df9522cd614d5c23c15b649b72e5d62fed1048e75c70e7b/pyobjc_framework_addressbook-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7893dd784322f4674299fb3ca40cb03385e5eddb78defd38f08c0b730813b56c", size = 12894, upload-time = "2025-11-14T09:35:47.498Z" }, - { url = "https://files.pythonhosted.org/packages/62/eb/de0d539bbf31685050dd9fe8894bd2dbc1632bf5311fc74c2c3c46ce61d0/pyobjc_framework_addressbook-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f03312faeb3c381e040f965b288379468d567b1449c1cfe66d150885b48510a3", size = 12910, upload-time = "2025-11-14T09:35:49.694Z" }, - { url = "https://files.pythonhosted.org/packages/e7/59/720da201349f67bca9e6b577fea1a8a3344e88a6527c48933be898c9559d/pyobjc_framework_addressbook-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:3b6931f78e01a215df3d9a27d1a10aab04659e636b0836ac448f8dd7fc56a581", size = 13064, upload-time = "2025-11-14T09:35:51.664Z" }, - { url = "https://files.pythonhosted.org/packages/1c/bc/7a0648f3b56f16eab76e349e873f21cc5d33864d9915bb33ade9a100d1c0/pyobjc_framework_addressbook-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e4e24094fa293f158ed21fcd57414b759dc1220c23efec4ee8a7672d726b3576", size = 12968, upload-time = "2025-11-14T09:35:53.639Z" }, - { url = "https://files.pythonhosted.org/packages/4c/e1/96093b6180e6af5f98b04de159f30d2d0cdde4caac1967f371ccbea662f2/pyobjc_framework_addressbook-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:184bc73e38bd062dce1eb97eb2f14be322f2421daf78efe2747aedb886d93eb0", size = 13132, upload-time = "2025-11-14T09:35:55.947Z" }, + { url = "https://files.pythonhosted.org/packages/c1/cb/5a722a638c15d8d934b88bf706d7d65d7ffffdcdbe78628e8c3d47964f6e/pyobjc_framework_addressbook-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:859be4a66e09548dfda796757b106a44965e288707fc1a0f0cd1b60796933c85", size = 12827, upload-time = "2026-06-19T16:05:24.729Z" }, + { url = "https://files.pythonhosted.org/packages/9a/7a/1c3b84602dc52e68d4422088ade9323047d5ed2c8f877d67afb0b90b74b7/pyobjc_framework_addressbook-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9daf115aa1225ebc1d7946a82a7b553ddd4158dc82705e60bb8ca9ae2bb38788", size = 12823, upload-time = "2026-06-19T16:05:25.706Z" }, + { url = "https://files.pythonhosted.org/packages/8d/01/83d10cdd6bcec2b63ded8dc5dd1036fd0dc8b8bb2f3a00b2c57ea785c32e/pyobjc_framework_addressbook-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1bd586c3aa24345cc9e1cf31a8860cda1298cb92a70fe73b05bdf31cb69edcb2", size = 12835, upload-time = "2026-06-19T16:05:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/50/64/909d16cbf2efcd95807da7b9966d6de35225cad192444a93eb07c1c8de3e/pyobjc_framework_addressbook-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca9a94a3d27cf9b9ae22ac8fc48bfce392757b283419ac38567eeac73b6f8ffc", size = 12853, upload-time = "2026-06-19T16:05:27.495Z" }, + { url = "https://files.pythonhosted.org/packages/58/b7/ac71ac95e5d1bae7aa4ec67e444683780274b826b0e6cad3d57252b29f1f/pyobjc_framework_addressbook-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:32a21916f38dff86226c37e0478dd86e8e5fa070403f8ca27dedf5b48f30a059", size = 13010, upload-time = "2026-06-19T16:05:28.426Z" }, + { url = "https://files.pythonhosted.org/packages/8c/b4/dd48553aa1daf16768ae3fc8cb168e4b01c29ac4b99be36679e7f0639d5e/pyobjc_framework_addressbook-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:687c1752dd0a3b649444839453c51c3f7ad9b540f04ad6d5e68d94678700d524", size = 12911, upload-time = "2026-06-19T16:05:29.504Z" }, + { url = "https://files.pythonhosted.org/packages/b4/13/34d930ad908a3d9fca0096ab57a231993a0d090038627b5926f09b66a204/pyobjc_framework_addressbook-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6578aaa8015b4e2db6139081efef3ff1c87b9d8b7a2b2ffbd99964b2f0f232e6", size = 13075, upload-time = "2026-06-19T16:05:30.665Z" }, + { url = "https://files.pythonhosted.org/packages/ab/0a/b718c14bae7522cd65e2a4afc23c7e3016a8dd1883b75e6aa93ea86d46af/pyobjc_framework_addressbook-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:73e3b8c88f839a00648b9ce5a35ef184bdd78075ecebeb5e8d6210cf07d7c8fd", size = 12901, upload-time = "2026-06-19T16:05:31.665Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/7e83e806be5f788251c9c93a8c54f3e71f0014d2a48ec66070b47771bf98/pyobjc_framework_addressbook-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:1adc06442fea100f8524282891d8358d621b8906bad30cbd61552e5edf92dcb7", size = 13066, upload-time = "2026-06-19T16:05:32.605Z" }, ] [[package]] name = "pyobjc-framework-adservices" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/04/1c3d3e0a1ac981664f30b33407dcdf8956046ecde6abc88832cf2aa535f4/pyobjc_framework_adservices-12.1.tar.gz", hash = "sha256:7a31fc8d5c6fd58f012db87c89ba581361fc905114bfb912e0a3a87475c02183", size = 11793, upload-time = "2025-11-14T10:08:39.56Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/de/6e9fa436a7aacaebe664c918dabfad14a19aa0f6ccaf24384d0c0b55b6f0/pyobjc_framework_adservices-12.2.1.tar.gz", hash = "sha256:6668fbef1b383c5cafae8479f4a8b53e824bd4d568611a53a3075e8c6dc4e39a", size = 12269, upload-time = "2026-06-19T16:19:43.102Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/13/f7796469b25f50750299c4b0e95dc2f75c7c7fc4c93ef2c644f947f10529/pyobjc_framework_adservices-12.1-py2.py3-none-any.whl", hash = "sha256:9ca3c55e35b2abb3149a0bce5de9a1f7e8ee4f8642036910ca8586ab2e161538", size = 3492, upload-time = "2025-11-14T09:35:57.344Z" }, + { url = "https://files.pythonhosted.org/packages/7d/43/c8d1a8be6faf0b3c60b28f8dbc31198ccbb72acdf0eb42fa8effbc9eaccd/pyobjc_framework_adservices-12.2.1-py2.py3-none-any.whl", hash = "sha256:631eaa86145179631624788ac1ee485fc79c2f9de347ab7d0cb3ea3413cd0cb1", size = 3510, upload-time = "2026-06-19T16:05:33.49Z" }, ] [[package]] name = "pyobjc-framework-adsupport" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/43/77/f26a2e9994d4df32e9b3680c8014e350b0f1c78d7673b3eba9de2e04816f/pyobjc_framework_adsupport-12.1.tar.gz", hash = "sha256:9a68480e76de567c339dca29a8c739d6d7b5cad30e1cd585ff6e49ec2fc283dd", size = 11645, upload-time = "2025-11-14T10:08:41.439Z" } +sdist = { url = "https://files.pythonhosted.org/packages/13/53/d73eafbc080b7eed68917f6a62758dafca80c7a6ac98ef35660185f8ea89/pyobjc_framework_adsupport-12.2.1.tar.gz", hash = "sha256:c659ac447b1bb3b1a54add100d72932cfd50482384a97c22926d281c9d97a6c0", size = 12098, upload-time = "2026-06-19T16:19:43.985Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/1a/3e90d5a09953bde7b60946cd09cca1411aed05dea855cb88cb9e944c7006/pyobjc_framework_adsupport-12.1-py2.py3-none-any.whl", hash = "sha256:97dcd8799dd61f047bb2eb788bbde81f86e95241b5e5173a3a61cfc05b5598b1", size = 3401, upload-time = "2025-11-14T09:35:59.039Z" }, + { url = "https://files.pythonhosted.org/packages/0d/49/1e309b2c03498b2d0848382aabea2b068f314c40dc58f1e3dbd5bedbddaf/pyobjc_framework_adsupport-12.2.1-py2.py3-none-any.whl", hash = "sha256:46a18c775a12565efbe46fbd0258ed63aa74002773362ff7d5e5be525013b221", size = 3424, upload-time = "2026-06-19T16:05:34.492Z" }, ] [[package]] name = "pyobjc-framework-applescriptkit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/f1/e0c07b2a9eb98f1a2050f153d287a52a92f873eeddb41b74c52c144d8767/pyobjc_framework_applescriptkit-12.1.tar.gz", hash = "sha256:cb09f88cf0ad9753dedc02720065818f854b50e33eb4194f0ea34de6d7a3eb33", size = 11451, upload-time = "2025-11-14T10:08:43.328Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/04/187611b7f3e51532b45df08c3b22edd53f163f337a88d7c03c4dc904e2ed/pyobjc_framework_applescriptkit-12.2.1.tar.gz", hash = "sha256:fa3a55933ec090aebc695f3575a5afe8a2d37015162cd30e6c00948748d08f83", size = 11668, upload-time = "2026-06-19T16:19:44.68Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/70/6c399c6ebc37a4e48acf63967e0a916878aedfe420531f6d739215184c0c/pyobjc_framework_applescriptkit-12.1-py2.py3-none-any.whl", hash = "sha256:b955fc017b524027f635d92a8a45a5fd9fbae898f3e03de16ecd94aa4c4db987", size = 4352, upload-time = "2025-11-14T09:36:00.705Z" }, + { url = "https://files.pythonhosted.org/packages/84/82/f83a29b54c8fbf5a7a6cc53676767968fa970fa902b599ae92ecf4208fd1/pyobjc_framework_applescriptkit-12.2.1-py2.py3-none-any.whl", hash = "sha256:db6a0aae4d9421068ed8a0838c509e3a7858813975dde40c40ef1dcc15328d1d", size = 4381, upload-time = "2026-06-19T16:05:35.356Z" }, ] [[package]] name = "pyobjc-framework-applescriptobjc" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c0/4b/e4d1592207cbe17355e01828bdd11dd58f31356108f6a49f5e0484a5df50/pyobjc_framework_applescriptobjc-12.1.tar.gz", hash = "sha256:dce080ed07409b0dda2fee75d559bd312ea1ef0243a4338606440f282a6a0f5f", size = 11588, upload-time = "2025-11-14T10:08:45.037Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/b8/67273037f4f10334d9660001bf12f5cc8b483f9cf9c8df91aa39701bcce4/pyobjc_framework_applescriptobjc-12.2.1.tar.gz", hash = "sha256:c60b751a6c20148f23eb1d556aa36612c7e39b14e0bd87abaea53744ff8eabc9", size = 11777, upload-time = "2026-06-19T16:19:45.417Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/5f/9ce6706399706930eb29c5308037109c30cfb36f943a6df66fdf38cc842a/pyobjc_framework_applescriptobjc-12.1-py2.py3-none-any.whl", hash = "sha256:79068f982cc22471712ce808c0a8fd5deea11258fc8d8c61968a84b1962a3d10", size = 4454, upload-time = "2025-11-14T09:36:02.276Z" }, + { url = "https://files.pythonhosted.org/packages/d9/8f/d66814f05748297c1c281d3b2f4a96600d4a3de87c65b6abf6a92151cac0/pyobjc_framework_applescriptobjc-12.2.1-py2.py3-none-any.whl", hash = "sha256:59a3f7668b1f88707c06ae1cd263856ac29c2bf1650a9dbfa21288e0baebe298", size = 4479, upload-time = "2026-06-19T16:05:36.364Z" }, ] [[package]] name = "pyobjc-framework-applicationservices" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, @@ -1514,122 +1572,132 @@ dependencies = [ { name = "pyobjc-framework-coretext" }, { name = "pyobjc-framework-quartz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/be/6a/d4e613c8e926a5744fc47a9e9fea08384a510dc4f27d844f7ad7a2d793bd/pyobjc_framework_applicationservices-12.1.tar.gz", hash = "sha256:c06abb74f119bc27aeb41bf1aef8102c0ae1288aec1ac8665ea186a067a8945b", size = 103247, upload-time = "2025-11-14T10:08:52.18Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/4d/0ebdd8144aba94b8fe9828ccee5616a4bf53d1f8bc51cff55f3cce86d695/pyobjc_framework_applicationservices-12.2.1.tar.gz", hash = "sha256:048ea663c9ac75c44a15dc7d5b8d78cbb4c97bf1c76e83835e8d5498e184001f", size = 109342, upload-time = "2026-06-19T16:19:46.149Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/9d/3cf36e7b08832e71f5d48ddfa1047865cf2dfc53df8c0f2a82843ea9507a/pyobjc_framework_applicationservices-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c4fd1b008757182b9e2603a63c6ffa930cc412fab47294ec64260ab3f8ec695d", size = 32791, upload-time = "2025-11-14T09:36:05.576Z" }, - { url = "https://files.pythonhosted.org/packages/17/86/d07eff705ff909a0ffa96d14fc14026e9fc9dd716233648c53dfd5056b8e/pyobjc_framework_applicationservices-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bdddd492eeac6d14ff2f5bd342aba29e30dffa72a2d358c08444da22129890e2", size = 32784, upload-time = "2025-11-14T09:36:08.755Z" }, - { url = "https://files.pythonhosted.org/packages/37/a7/55fa88def5c02732c4b747606ff1cbce6e1f890734bbd00f5596b21eaa02/pyobjc_framework_applicationservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c8f6e2fb3b3e9214ab4864ef04eee18f592b46a986c86ea0113448b310520532", size = 32835, upload-time = "2025-11-14T09:36:11.855Z" }, - { url = "https://files.pythonhosted.org/packages/fc/21/79e42ee836f1010f5fe9e97d2817a006736bd287c15a3674c399190a2e77/pyobjc_framework_applicationservices-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bd1f4dbb38234a24ae6819f5e22485cf7dd3dd4074ff3bf9a9fdb4c01a3b4a38", size = 32859, upload-time = "2025-11-14T09:36:15.208Z" }, - { url = "https://files.pythonhosted.org/packages/66/3a/0f1d4dcf2345e875e5ea9761d5a70969e241d24089133d21f008dde596f5/pyobjc_framework_applicationservices-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:8a5d2845249b6a85ba9e320a9848468c3f8cd6f59605a9a43f406a7810eaa830", size = 33115, upload-time = "2025-11-14T09:36:18.384Z" }, - { url = "https://files.pythonhosted.org/packages/40/44/3196b40fec68b4413c92875311f17ccf4c3ff7d2e53676f8fc18ad29bd18/pyobjc_framework_applicationservices-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:f43c9a24ad97a9121276d4d571aa04a924282c80d7291cfb3b29839c3e2013a8", size = 32997, upload-time = "2025-11-14T09:36:21.58Z" }, - { url = "https://files.pythonhosted.org/packages/fd/bb/dab21d2210d3ef7dd0616df7e8ea89b5d8d62444133a25f76e649a947168/pyobjc_framework_applicationservices-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1f72e20009a4ebfd5ed5b23dc11c1528ad6b55cc63ee71952ddb2a5e5f1cb7da", size = 33238, upload-time = "2025-11-14T09:36:24.751Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e7/f51cd6aa79390bf64f2fb09d57f33737bf050eb798137df9392fbba311f9/pyobjc_framework_applicationservices-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9fb187b7176e61983e1c4760bba951fd9b3fc539dca4f05998d33628e7539121", size = 32714, upload-time = "2026-06-19T16:05:37.289Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8a/5a9310929bb303c31c04a1c6dc7b3213c9bd964a692d024a00c0643af2c8/pyobjc_framework_applicationservices-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dabec481217b0d0c1ea835e9fef6b0681381b14b3f16a30d4a9d801ee3852dd2", size = 32715, upload-time = "2026-06-19T16:05:38.448Z" }, + { url = "https://files.pythonhosted.org/packages/bf/89/39a7462006afbc06c69029fe4181b7359a9da25ae7864ef75f9d3ffb9272/pyobjc_framework_applicationservices-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f519ced13888d03410cd7da1f08fc56ee2944099e607216cef7ca26ecfdef61b", size = 32764, upload-time = "2026-06-19T16:05:39.26Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6e/8e928d5e3025529ed92c6eb5fd88a5e6e485cc6df945c541f29b4af7f2c6/pyobjc_framework_applicationservices-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8749290f796e6cca341d443769b79329dde5d157bcc4413c1f7fdb68ea4a8e48", size = 32782, upload-time = "2026-06-19T16:05:40.284Z" }, + { url = "https://files.pythonhosted.org/packages/50/a5/c7b5a31777fe2ce7c07b9c16941ff4fbf0a150bf755164d96228d32ccb4f/pyobjc_framework_applicationservices-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9ee11677fbd6a0987234814c7dde88ffd11242e8c1f76952e6654ea07f2370ac", size = 33048, upload-time = "2026-06-19T16:05:41.308Z" }, + { url = "https://files.pythonhosted.org/packages/8b/47/cd2bd76b862686c0aa78568ed9dff175764353c209a3096c72d6e2a9b151/pyobjc_framework_applicationservices-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a1c0ee536cb8bd7f5a811165ec323a9207b1e8dad9534fe2081f767fb90b0411", size = 32921, upload-time = "2026-06-19T16:05:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/6989a96f8501aa3a16513d08b2c4c78ca906a10b6a4e5a33c4c59fd1bea9/pyobjc_framework_applicationservices-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0d55dd5be19e4a1363662bc8b48894d45714d07f0ee3958665fc9ea7df0f61b7", size = 33163, upload-time = "2026-06-19T16:05:43.256Z" }, + { url = "https://files.pythonhosted.org/packages/5c/41/66f2bcd12454a85f0479393f5825021332ee84b49583c7954cd20310cab5/pyobjc_framework_applicationservices-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:e91c84238b2f68f608473854bcabb8770f15ad837e7561d217f24a1e482e42be", size = 32915, upload-time = "2026-06-19T16:05:44.151Z" }, + { url = "https://files.pythonhosted.org/packages/81/cf/04b6b1eb181fa3071e9743bab7551f5d2ec3f650ac74bab790f21717ba51/pyobjc_framework_applicationservices-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:09bfa27765d4c74155323fd8185b7aba6d639ce06c0a9f00ee2d9e7dce3d7800", size = 33158, upload-time = "2026-06-19T16:05:45.017Z" }, ] [[package]] name = "pyobjc-framework-apptrackingtransparency" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0d/de/f24348982ecab0cb13067c348fc5fbc882c60d704ca290bada9a2b3e594b/pyobjc_framework_apptrackingtransparency-12.1.tar.gz", hash = "sha256:e25bf4e4dfa2d929993ee8e852b28fdf332fa6cde0a33328fdc3b2f502fa50ec", size = 12407, upload-time = "2025-11-14T10:08:54.118Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/c3/2f6b30c7010b32450769d679c30393a148d0b1531b31f1c0d3a600fc999b/pyobjc_framework_apptrackingtransparency-12.2.1.tar.gz", hash = "sha256:3eff48469eb07e4637408f410ce2690711f5c3de2fbfaa8844daa718ed3479f7", size = 12795, upload-time = "2026-06-19T16:19:47.034Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/19/b2/90120b93ecfb099b6af21696c26356ad0f2182bdef72b6cba28aa6472ca6/pyobjc_framework_apptrackingtransparency-12.1-py2.py3-none-any.whl", hash = "sha256:23a98ade55495f2f992ecf62c3cbd8f648cbd68ba5539c3f795bf66de82e37ca", size = 3879, upload-time = "2025-11-14T09:36:26.425Z" }, + { url = "https://files.pythonhosted.org/packages/ed/2d/a78781f57f2c28155a60eb5ef255d50a7d34b036d16e395cdbc27c2c02da/pyobjc_framework_apptrackingtransparency-12.2.1-py2.py3-none-any.whl", hash = "sha256:29fb3e38e124932eb566a8427dc0db759eba63396cf530862b081432316ff863", size = 3930, upload-time = "2026-06-19T16:05:45.898Z" }, ] [[package]] name = "pyobjc-framework-arkit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/8b/843fe08e696bca8e7fc129344965ab6280f8336f64f01ba0a8862d219c3f/pyobjc_framework_arkit-12.1.tar.gz", hash = "sha256:0c5c6b702926179700b68ba29b8247464c3b609fd002a07a3308e72cfa953adf", size = 35814, upload-time = "2025-11-14T10:08:57.55Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/be/db21fafd315dc479925d79bd144f132cb38fb37583212753db8252b765d8/pyobjc_framework_arkit-12.2.1.tar.gz", hash = "sha256:ed4f67b1594a427b66ab751657ce6183a93a07ba32d3ba3bbefd7e0b4f6bf64d", size = 40145, upload-time = "2026-06-19T16:19:47.704Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/21/1e/64c55b409243b3eb9abc7a99e7b27ad4e16b9e74bc4b507fb7e7b81fd41a/pyobjc_framework_arkit-12.1-py2.py3-none-any.whl", hash = "sha256:f6d39e28d858ee03f052d6780a552247e682204382dbc090f1d3192fa1b21493", size = 8302, upload-time = "2025-11-14T09:36:28.127Z" }, + { url = "https://files.pythonhosted.org/packages/53/34/439b5e0505a369ab741daf65dee78a58e9110df79bf2fe20883b0c93666b/pyobjc_framework_arkit-12.2.1-py2.py3-none-any.whl", hash = "sha256:2834c497fe9f6cbfcdfdcb0202945b7ad36708438afbc71c77186ce3fb972366", size = 8328, upload-time = "2026-06-19T16:05:47.05Z" }, ] [[package]] name = "pyobjc-framework-audiovideobridging" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/51/f81581e7a3c5cb6c9254c6f1e1ee1d614930493761dec491b5b0d49544b9/pyobjc_framework_audiovideobridging-12.1.tar.gz", hash = "sha256:6230ace6bec1f38e8a727c35d054a7be54e039b3053f98e6dd8d08d6baee2625", size = 38457, upload-time = "2025-11-14T10:09:01.122Z" } +sdist = { url = "https://files.pythonhosted.org/packages/14/6b/cacbe1a5f8e72c76f546689b551853e9312a00a612e8a2087e75f0dc1e3a/pyobjc_framework_audiovideobridging-12.2.1.tar.gz", hash = "sha256:7b6890ebfb1d346988dad7ff20182373c5c15026b1f9a50f64f654b4ff255e76", size = 44241, upload-time = "2026-06-19T16:19:48.555Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/7c/9b1a3a8f138ff171e08bbf8af8bb66e0e6e98a23b62658ab9e47dc3cb610/pyobjc_framework_audiovideobridging-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c2fa51d674ba30801cfe67d1b5e71424635b62f6377b96602bce124ae3086823", size = 11037, upload-time = "2025-11-14T09:36:30.574Z" }, - { url = "https://files.pythonhosted.org/packages/0a/f8/c614630fa382720bbd42a0ff567378630c36d10f114476d6c70b73f73b49/pyobjc_framework_audiovideobridging-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6bc24a7063b08c7d9f1749a4641430d363b6dba642c04d09b58abcee7a5260cb", size = 11037, upload-time = "2025-11-14T09:36:32.583Z" }, - { url = "https://files.pythonhosted.org/packages/f3/8e/a28badfcc6c731696e3d3a8a83927bd844d992f9152f903c2fee355702ca/pyobjc_framework_audiovideobridging-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:010021502649e2cca4e999a7c09358d48c6b0ed83530bbc0b85bba6834340e4b", size = 11052, upload-time = "2025-11-14T09:36:34.475Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e7/d6436115ebb623dbc14283f5e76577245fa6460995e9f7981e79e97003d3/pyobjc_framework_audiovideobridging-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a9901a88b6c8dbc982d8605c6b1ff0330ff80647a0a96a8187b6784249eb42dc", size = 11065, upload-time = "2025-11-14T09:36:36.69Z" }, - { url = "https://files.pythonhosted.org/packages/97/ca/d6740b0f666dca9fc28d4e08358a7a2fffaf879cf9c49d2c99c470b83ef8/pyobjc_framework_audiovideobridging-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0c57fdf1762f616d10549c0eddf84e59c193800f4a7932aaa7d5f13c123609c0", size = 11239, upload-time = "2025-11-14T09:36:38.992Z" }, - { url = "https://files.pythonhosted.org/packages/98/9a/f4b435523c297cdf25bfe0d0a8bb25ae0d3fa19813c2365cf1e93f462948/pyobjc_framework_audiovideobridging-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:88f97bf62cba0d07f623650a7b2a58f73aedcc03b523e2bcd5653042dd50c152", size = 11130, upload-time = "2025-11-14T09:36:40.918Z" }, - { url = "https://files.pythonhosted.org/packages/da/96/33c5aec0940ff3f81ad11b3a154d3cae94803d48376f1436392c4484b6ff/pyobjc_framework_audiovideobridging-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:84d466e0c2fbf466fd5ca9209139e321ddf3f96bbd987308c73bb4a243ab80b2", size = 11302, upload-time = "2025-11-14T09:36:42.734Z" }, + { url = "https://files.pythonhosted.org/packages/34/2b/9025f1f68546e0261abed9a29d1c7097cc8796dacb6149dce9f44eb74f3b/pyobjc_framework_audiovideobridging-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d88209b5fe94e0c7e3744a661bd8a98071073404ae97230db7d4fa7ad7a0a99c", size = 11072, upload-time = "2026-06-19T16:05:47.95Z" }, + { url = "https://files.pythonhosted.org/packages/71/97/770348fe032e648c09555b7c9611bdf61ac35a7909abea05980836ccd0c9/pyobjc_framework_audiovideobridging-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c0a62ec1acbf2182e272272a73e16cdc9a8de38d87a6f178c07ede661b01902b", size = 11077, upload-time = "2026-06-19T16:05:48.984Z" }, + { url = "https://files.pythonhosted.org/packages/9a/18/aa7624312ec1aff0243232c75e8f911441c64c417be5a2fd904b89e705a2/pyobjc_framework_audiovideobridging-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a96bac7a308bed774a5332069ea013e999fa269d58ea67b67ef31dfde705186", size = 11085, upload-time = "2026-06-19T16:05:49.743Z" }, + { url = "https://files.pythonhosted.org/packages/ca/75/5221bb910b6d4d91bc0653eafa111fe125ca181da7254b4d1bd5e09dea5a/pyobjc_framework_audiovideobridging-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ceebe03b803be2684412050afc9e661d1e9fc7857ca34f27beff3a11b2cad773", size = 11098, upload-time = "2026-06-19T16:05:50.524Z" }, + { url = "https://files.pythonhosted.org/packages/39/87/9fbd555fb110f3210a39405c604de75561f68e5cf8e1daeddf4101905f5a/pyobjc_framework_audiovideobridging-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:271d3a0a46cc13437b27c790bc7a5fd9dba8f445a743c243de2b41cc2b396aef", size = 11268, upload-time = "2026-06-19T16:05:51.378Z" }, + { url = "https://files.pythonhosted.org/packages/e1/a2/35db7aed073c8d403b965668c66fa4c84c9557ceb248def2fda7276699d4/pyobjc_framework_audiovideobridging-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ce46d2afc7cc5dacea90dc671c1981d8366239d0aa83f06c0bd7ccb5e8218f19", size = 11156, upload-time = "2026-06-19T16:05:52.196Z" }, + { url = "https://files.pythonhosted.org/packages/9d/49/b7e4728e86dbaca3134cc630876b82c0b1955232015e156e0477ba3e499d/pyobjc_framework_audiovideobridging-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:622afa4c3a12878746d10994ef7536d6c81fba75123b3969613ff23da771e36c", size = 11334, upload-time = "2026-06-19T16:05:53.181Z" }, + { url = "https://files.pythonhosted.org/packages/68/de/3e3b00ed974a7351d9972273c515b65ce0ec0dc677d1f4956ed6cd4eca7d/pyobjc_framework_audiovideobridging-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:9770aad1e6f915fd162ff2f867c21114b134a0341d1ceed7fa067d3b02d47c38", size = 11156, upload-time = "2026-06-19T16:05:53.978Z" }, + { url = "https://files.pythonhosted.org/packages/d7/71/a32171ea36e8c890dff834f6529912f281062d3f50b73c356e416663101f/pyobjc_framework_audiovideobridging-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:81fbcaff1304d1aac41a5a661736d7f8ddd31b3b1352bc1fe471906956997b38", size = 11327, upload-time = "2026-06-19T16:05:54.917Z" }, ] [[package]] name = "pyobjc-framework-authenticationservices" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6c/18/86218de3bf67fc1d810065f353d9df70c740de567ebee8550d476cb23862/pyobjc_framework_authenticationservices-12.1.tar.gz", hash = "sha256:cef71faeae2559f5c0ff9a81c9ceea1c81108e2f4ec7de52a98c269feff7a4b6", size = 58683, upload-time = "2025-11-14T10:09:06.003Z" } +sdist = { url = "https://files.pythonhosted.org/packages/97/1f/fa7506fb8df1c30f7a1fddc9812705494421b2064391106dc00cac948ce8/pyobjc_framework_authenticationservices-12.2.1.tar.gz", hash = "sha256:da70cd842a41276e6f9958b1d3e227a3de452e696c56f8e2b439add45e578665", size = 75693, upload-time = "2026-06-19T16:19:49.411Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/8e/8547d8b8574c8d42802b6b904e3354243fb23daed9106333a59323b5154b/pyobjc_framework_authenticationservices-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:39bba6cc6041467d0046a04610e8aacc852961c82055d84bf3981971780ee5eb", size = 20636, upload-time = "2025-11-14T09:36:45.048Z" }, - { url = "https://files.pythonhosted.org/packages/c2/16/2f19d8a95f0cf8e940f7b7fb506ced805d5522b4118336c8e640c34517ae/pyobjc_framework_authenticationservices-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c15bb81282356f3f062ac79ff4166c93097448edc44b17dcf686e1dac78cc832", size = 20636, upload-time = "2025-11-14T09:36:48.35Z" }, - { url = "https://files.pythonhosted.org/packages/f1/1d/e9f296fe1ee9a074ff6c45ce9eb109fc3b45696de000f373265c8e42fd47/pyobjc_framework_authenticationservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6fd5ce10fe5359cbbfe03eb12cab3e01992b32ab65653c579b00ac93cf674985", size = 20738, upload-time = "2025-11-14T09:36:51.094Z" }, - { url = "https://files.pythonhosted.org/packages/23/2f/7016b3ca344b079932abe56d7d6216c88cac715d81ca687753aed4b749f7/pyobjc_framework_authenticationservices-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4491a2352cd53a38c7d057d674b1aa40d05eddb8dd7a1a2f415d9f2858b52d40", size = 20746, upload-time = "2025-11-14T09:36:53.762Z" }, - { url = "https://files.pythonhosted.org/packages/5b/63/f2d1137e542b2badb5803e01628a61e9df8853b773513a6a066524c77903/pyobjc_framework_authenticationservices-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a3957039eae3a82ada418ee475a347619e42ba10c45a57cd6ca83b1a0e61c2ad", size = 20994, upload-time = "2025-11-14T09:36:56.153Z" }, - { url = "https://files.pythonhosted.org/packages/a2/93/13232a82318153ec392a46c0f674baeb64ce0aaab05683d4c129ac0fafec/pyobjc_framework_authenticationservices-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:3ee69de818ce91c3bea6f87deba59ab8392a2c17c48f3d6fce0639c0e548bb0c", size = 20753, upload-time = "2025-11-14T09:36:59.075Z" }, - { url = "https://files.pythonhosted.org/packages/d3/95/c941a19224a132b206948e1d329a1e708e41e013ef0d316162af7cfc54c6/pyobjc_framework_authenticationservices-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:b14997d96887127f393434d42e3e108eeca2116ca935dd7e37e91c709a93b422", size = 21032, upload-time = "2025-11-14T09:37:01.358Z" }, + { url = "https://files.pythonhosted.org/packages/78/e0/5c3d5c4adaba77d35ab04585fed0ea3147f8c15cfd146f09b07e8024a788/pyobjc_framework_authenticationservices-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:73ef0a6157059cd33d53ae19d1e3c818f5068849f850774a6b0534e12236e9a3", size = 21285, upload-time = "2026-06-19T16:05:55.839Z" }, + { url = "https://files.pythonhosted.org/packages/44/0b/71951c26dc99ccca7afc5297e6805c2aee8842c887f42372355b2e8292f9/pyobjc_framework_authenticationservices-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d1c679a6625ab639c42a2ea9e69d9b1d13c639effb2fec92176e107f53d4b90", size = 21286, upload-time = "2026-06-19T16:05:56.836Z" }, + { url = "https://files.pythonhosted.org/packages/1f/85/5019b9a1768b0b9e002029e036d7028312d5704ea48a18bb39e24d22c0dc/pyobjc_framework_authenticationservices-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac2f78923734fb477ec4de4a2bea243ae418b33675c2ee62bf12ae333b31dad8", size = 21388, upload-time = "2026-06-19T16:05:57.874Z" }, + { url = "https://files.pythonhosted.org/packages/46/c1/98c8ec590df7d76f394b90e6bffacdaadcf30555e34b2955ac3348316845/pyobjc_framework_authenticationservices-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a3ec1ff47578c7163718aed572fb5d4902f2e33df0b178b01c00797b75802225", size = 21399, upload-time = "2026-06-19T16:05:58.683Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ee/6775170a378c836767c785b2b99294926c9ddd7f9d38a599e5cef52fe6d1/pyobjc_framework_authenticationservices-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:19194a7266ed75d9d083cf7e8b3d8ac3b241ae259d8c6cd3b4f68bda70751522", size = 21645, upload-time = "2026-06-19T16:05:59.537Z" }, + { url = "https://files.pythonhosted.org/packages/1a/fe/bc580ed2693cfaae762989d90b9d7186b7b53f7c5b5deae94b77f52df0a6/pyobjc_framework_authenticationservices-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b5b5f66e21df6bf856406dbc984c6400f23b43aef4f22603c7685bf074dc749a", size = 21400, upload-time = "2026-06-19T16:06:00.367Z" }, + { url = "https://files.pythonhosted.org/packages/f9/02/c5e0b7e5aacfc527ce788d5dd3b836f0a8660fe1bf3af85a5ab7e47f2eed/pyobjc_framework_authenticationservices-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:90e7c3595fabaf1f905473a553f33572d3bb7d53decc323c18df0a7a2e6c4660", size = 21678, upload-time = "2026-06-19T16:06:01.274Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c0/2824fa1036adb44090d1131e9699d9bfb849f5662a084b579e8a2370ed0f/pyobjc_framework_authenticationservices-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:d431787ad7a9718ab7468411e279ab56e59967744dad6b998be623e3fe9df0dc", size = 21401, upload-time = "2026-06-19T16:06:02.088Z" }, + { url = "https://files.pythonhosted.org/packages/28/85/ca5af44bbd71fab51bab17c18aa39b258cdb5c5eb22db4bfaefc297bfcb9/pyobjc_framework_authenticationservices-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:fd0592d7257eeb47cf398f4a579a617e977a5b949440a60a34f130c0d811d071", size = 21685, upload-time = "2026-06-19T16:06:02.978Z" }, ] [[package]] name = "pyobjc-framework-automaticassessmentconfiguration" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e4/24/080afe8189c47c4bb3daa191ccfd962400ca31a67c14b0f7c2d002c2e249/pyobjc_framework_automaticassessmentconfiguration-12.1.tar.gz", hash = "sha256:2b732c02d9097682ca16e48f5d3b10056b740bc091e217ee4d5715194c8970b1", size = 21895, upload-time = "2025-11-14T10:09:08.779Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/d1/1124aaf5aa1a35126836c623e30eb7ea47c9e64e758f7c3156aa61dbffbd/pyobjc_framework_automaticassessmentconfiguration-12.2.1.tar.gz", hash = "sha256:6888ec9846d04cb7983525d9a134b838044d9857182fe5404224c3071f4cc64f", size = 24775, upload-time = "2026-06-19T16:19:50.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/4d/e9c86cd84905f565d3bdef675f0b90516b280f18aa2f20c84be0f02e0f49/pyobjc_framework_automaticassessmentconfiguration-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:096c1f0dcd96a6e9903b70b3090747e76344324c02066026c4f7c347bc1823ae", size = 9320, upload-time = "2025-11-14T09:37:03.234Z" }, - { url = "https://files.pythonhosted.org/packages/fc/c9/4d2785565cc470daa222f93f3d332af97de600aef6bd23507ec07501999d/pyobjc_framework_automaticassessmentconfiguration-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d94a4a3beb77b3b2ab7b610c4b41e28593d15571724a9e6ab196b82acc98dc13", size = 9316, upload-time = "2025-11-14T09:37:05.052Z" }, - { url = "https://files.pythonhosted.org/packages/f2/b2/fbec3d649bf275d7a9604e5f56015be02ef8dcf002f4ae4d760436b8e222/pyobjc_framework_automaticassessmentconfiguration-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c2e22ea67d7e6d6a84d968169f83d92b59857a49ab12132de07345adbfea8a62", size = 9332, upload-time = "2025-11-14T09:37:07.083Z" }, - { url = "https://files.pythonhosted.org/packages/52/85/42cf8718bbfef47e67228a39d4f25b86b6fa9676f5ca5904af21ae42ad43/pyobjc_framework_automaticassessmentconfiguration-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:467739e70ddbc259bf453056cc9ce4ed96de8e6aad8122fa4035d2e6ecf9fc9c", size = 9344, upload-time = "2025-11-14T09:37:09.02Z" }, - { url = "https://files.pythonhosted.org/packages/09/ec/a889dd812adfa446238853cf3cf6a7a2691e3096247a7ef75970d135e5bb/pyobjc_framework_automaticassessmentconfiguration-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b4ea4b00f70bf242a5d8ce9c420987239dbc74285588c141ac1e0d6bd71fcd4c", size = 9501, upload-time = "2025-11-14T09:37:10.684Z" }, - { url = "https://files.pythonhosted.org/packages/dd/36/b7a59d77cf0f3dfe8676ecd0ab22dca215df11a0f1623cb0dbac29bb30d2/pyobjc_framework_automaticassessmentconfiguration-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:f5f1818c6f77daf64d954878bbbda6b3f5e41e23b599210da08fefed1f1d5981", size = 9392, upload-time = "2025-11-14T09:37:12.35Z" }, - { url = "https://files.pythonhosted.org/packages/f8/b4/bc5de9b5cce1d243823b283e0942bb353f72998c01688fb3b3da9061a731/pyobjc_framework_automaticassessmentconfiguration-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:2e84dee31c3cb7dda4cded047f8b2080378da5c13e8682e45852be5e34b647ed", size = 9541, upload-time = "2025-11-14T09:37:14.358Z" }, + { url = "https://files.pythonhosted.org/packages/a8/91/7cdb3c79e8e0ecf2f6b959295983bbf7df99d3a2ff937a455a263d4ed884/pyobjc_framework_automaticassessmentconfiguration-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0da513d180cc3575fea017b67301e7f7527e123462fa55a241f92a578f5435f4", size = 9373, upload-time = "2026-06-19T16:06:03.86Z" }, + { url = "https://files.pythonhosted.org/packages/a2/d4/cbae4c491afda8ce2bed85a0320cb7cc798fc62c2e1187b18d7eb0529f89/pyobjc_framework_automaticassessmentconfiguration-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2fa2291aec48dc6ef7ea1233399427f8cb15c3ec28ed937a8f0f0739c06361d5", size = 9364, upload-time = "2026-06-19T16:06:04.755Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f9/2e284b1b296745e899d1e920c163d3e0d1a7b1111b8735e3b2f9cb57e5d7/pyobjc_framework_automaticassessmentconfiguration-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:742f022d3a43478e6d322ea1d2d608080263de75456b5bae7a465a9f463089bf", size = 9382, upload-time = "2026-06-19T16:06:05.516Z" }, + { url = "https://files.pythonhosted.org/packages/f6/a0/61e92054653173c358b17579058e7e5037679a43d0962fa0296070a0f2aa/pyobjc_framework_automaticassessmentconfiguration-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c4c233d052d699cf6db479dd8ac0a741fe920e192adc716121c8f4ff3f0ce864", size = 9393, upload-time = "2026-06-19T16:06:06.3Z" }, + { url = "https://files.pythonhosted.org/packages/81/a5/a5bfc8caa00251b2b1abc22d3a5ef28f4e40c14fad9a8ea1c4817d568495/pyobjc_framework_automaticassessmentconfiguration-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:90942b9e126b67f151f0965ca84e00795724a96971195e910137dff2c6c0a20b", size = 9547, upload-time = "2026-06-19T16:06:07.188Z" }, + { url = "https://files.pythonhosted.org/packages/ec/8e/61b26f0be555d71142533c18e21e28d5cc7929b8451f42753fe43c12ba79/pyobjc_framework_automaticassessmentconfiguration-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:d1117a6d3481c8e5ec9f984d201c356598e116daaa0a5db6341c7f52a30e0d4e", size = 9440, upload-time = "2026-06-19T16:06:08.048Z" }, + { url = "https://files.pythonhosted.org/packages/64/0b/35c209314ceb17601e01a39e3e47a574ca7a3e536b39a4923aafe2a25941/pyobjc_framework_automaticassessmentconfiguration-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:aa564494b074a5020e27b307d73cf695cc9256284996304d9e5d22cf8efb9629", size = 9592, upload-time = "2026-06-19T16:06:08.824Z" }, + { url = "https://files.pythonhosted.org/packages/35/f0/ef2aa598ea223ad2ea2b295f0e16299d4e1bddac81768a6b6b1d2afcafbd/pyobjc_framework_automaticassessmentconfiguration-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:5fea5ebc25b053b979536f1b40d19ad9ea80457f47e3df475709b5ab8cf9d7bc", size = 9449, upload-time = "2026-06-19T16:06:10.138Z" }, + { url = "https://files.pythonhosted.org/packages/63/33/49fee458fba33c76e9db0fa602b694cd893a10e43332c749fe69ff9c0b5f/pyobjc_framework_automaticassessmentconfiguration-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:170a62e3635f1ddec3e1ec82694ed7330f3ac659ac7ffefc4cee4770d3cc3242", size = 9597, upload-time = "2026-06-19T16:06:10.998Z" }, ] [[package]] name = "pyobjc-framework-automator" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7e/08/362bf6ac2bba393c46cf56078d4578b692b56857c385e47690637a72f0dd/pyobjc_framework_automator-12.1.tar.gz", hash = "sha256:7491a99347bb30da3a3f744052a03434ee29bee3e2ae520576f7e796740e4ba7", size = 186068, upload-time = "2025-11-14T10:09:20.82Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/2f/6669c037108799e2319894f21e0067c3119c2a185b18ca70aaf645309199/pyobjc_framework_automator-12.2.1.tar.gz", hash = "sha256:6ea468966d911292d73f52672603eee50bb4a3d651094f63de25dd6d5818d347", size = 188942, upload-time = "2026-06-19T16:19:51.099Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/30/57d6e93adfac2d19d8607700352fb1a2e3a11a952da9986847da2e7b20b3/pyobjc_framework_automator-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fa9189b481db4429e3aea6b567b0888f8c6f6ba388064be2ce6f287e19cea096", size = 10014, upload-time = "2025-11-14T09:37:16.34Z" }, - { url = "https://files.pythonhosted.org/packages/e7/99/480e07eef053a2ad2a5cf1e15f71982f21d7f4119daafac338fa0352309c/pyobjc_framework_automator-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4f3d96da10d28c5c197193a9d805a13157b1cb694b6c535983f8572f5f8746ea", size = 10016, upload-time = "2025-11-14T09:37:18.621Z" }, - { url = "https://files.pythonhosted.org/packages/e3/36/2e8c36ddf20d501f9d344ed694e39021190faffc44b596f3a430bf437174/pyobjc_framework_automator-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4df9aec77f0fbca66cd3534d1b8398fe6f3e3c2748c0fc12fec2546c7f2e3ffd", size = 10034, upload-time = "2025-11-14T09:37:20.293Z" }, - { url = "https://files.pythonhosted.org/packages/1f/cd/666e44c8deb41e5c9dc5930abf8379edd80bff14eb4d0a56380cdbbbbf9a/pyobjc_framework_automator-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cdda7b8c48c0f8e15cbb97600ac848fd76cf9837ca3353286a7c02281e9c17a3", size = 10045, upload-time = "2025-11-14T09:37:22.179Z" }, - { url = "https://files.pythonhosted.org/packages/08/92/75fa03ad8673336689bd663ba153b378e070f159122d8478deb0940039c0/pyobjc_framework_automator-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e9962ea45875fda6a648449015ccc26cc1229fdbd0166556a7271c60ba6d9011", size = 10192, upload-time = "2025-11-14T09:37:24.836Z" }, - { url = "https://files.pythonhosted.org/packages/c6/be/97fcdb60072f443ec360d2aa07e45469125eed57e0158d50f00ef5431240/pyobjc_framework_automator-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fb6a177cac056f2ecacaae1d4815f4e10529025cb13184fdee297989b55846f7", size = 10092, upload-time = "2025-11-14T09:37:26.574Z" }, - { url = "https://files.pythonhosted.org/packages/06/7b/af089d11c6bdc9773e4e0f68b1beabe523d663290080e6ec2e853226a8bb/pyobjc_framework_automator-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:275ed04d339c5a5849a4be8ef82c2035be07ab92ccbf69007f544bcfabe060ad", size = 10240, upload-time = "2025-11-14T09:37:28.232Z" }, + { url = "https://files.pythonhosted.org/packages/3d/5f/09c286fc575762a99bda9958280e75dd0b26876068cb245a8925b2464e5d/pyobjc_framework_automator-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2e3d227418f23c30e8f284db99dce259d02813a85291e16129dd47b661ffdb72", size = 10046, upload-time = "2026-06-19T16:06:11.83Z" }, + { url = "https://files.pythonhosted.org/packages/42/40/0344eafeffe5b17edad349265349f6046acf1e36db825359cb453feb5d94/pyobjc_framework_automator-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c512602073fbb51e5c2c442628029ca7eb1d19a0c53632dbe9aa1b0c47454868", size = 10043, upload-time = "2026-06-19T16:06:13.138Z" }, + { url = "https://files.pythonhosted.org/packages/67/3c/296fbea8ecdfb45944eeb427328d729280b1dd1a811563a40d8baa0b6fdf/pyobjc_framework_automator-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:275dad426c9cee7b683fa5f800e2779b565d1a1e75cb82bb79ad2289ed802f56", size = 10060, upload-time = "2026-06-19T16:06:13.935Z" }, + { url = "https://files.pythonhosted.org/packages/88/2c/4dfeec84201af81949f7edf670bffe7e88ce022402cab7f62a8ed2234b06/pyobjc_framework_automator-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b74cc160c2f751885d70b6db4d02506468ee4b149672ce06cace88484d8fa769", size = 10076, upload-time = "2026-06-19T16:06:14.725Z" }, + { url = "https://files.pythonhosted.org/packages/96/62/349e5b92592094e7fe50fbd46e87fc1b5d1f9e09adb48a7e67d3afe4f721/pyobjc_framework_automator-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c41866f4068789acf653416e61ed914279f0d83e12f62b3d5c7eea658df8dbb2", size = 10220, upload-time = "2026-06-19T16:06:15.914Z" }, + { url = "https://files.pythonhosted.org/packages/12/f8/7452651974e5c7f7c20b03d25b046ca3883ba649cd26f396ab3fa1c3312a/pyobjc_framework_automator-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6c6c203c6468df9d958a2e2c5283518b862e87cf1b5db867471ccbb8d0cb9dd0", size = 10124, upload-time = "2026-06-19T16:06:16.732Z" }, + { url = "https://files.pythonhosted.org/packages/c3/ce/de1866c4e99e45e282a53259a266f10f6f70a606c2ea3c8bca19a8b62f49/pyobjc_framework_automator-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:dc742dc85b078fcd40f4a6e68f4d712791245b2c64ade046c78661680cec57b1", size = 10266, upload-time = "2026-06-19T16:06:17.526Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e8/7d4d106e3da67c0235e4ebdcda4b2e4e7fdb4e5c784bff93c836a25bafeb/pyobjc_framework_automator-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:146fa43a474585ec27f7edb70513c1b75c4fae53b4f8bf971e7a45f1d37c093c", size = 10116, upload-time = "2026-06-19T16:06:18.53Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d7/39f36aa3137dd34902c1f496218e1398b80f7acc5222867aefb6859c7f0c/pyobjc_framework_automator-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:3da3eac88bd2c1615040eefb6df7f98b8dc120eee08a27b86c5bfee5f295edc5", size = 10268, upload-time = "2026-06-19T16:06:19.388Z" }, ] [[package]] name = "pyobjc-framework-avfoundation" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, @@ -1638,78 +1706,86 @@ dependencies = [ { name = "pyobjc-framework-coremedia" }, { name = "pyobjc-framework-quartz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/42/c026ab308edc2ed5582d8b4b93da6b15d1b6557c0086914a4aabedd1f032/pyobjc_framework_avfoundation-12.1.tar.gz", hash = "sha256:eda0bb60be380f9ba2344600c4231dd58a3efafa99fdc65d3673ecfbb83f6fcb", size = 310047, upload-time = "2025-11-14T10:09:40.069Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/26/7616f0bc8e4eaaba948cf5d220c8f55e0f54f617a2812392a82f19c30f39/pyobjc_framework_avfoundation-12.2.1.tar.gz", hash = "sha256:2735e4f1c345d2b533541577e292f3ad2f75d19200eff99f1a2db16d78b4f1a3", size = 410329, upload-time = "2026-06-19T16:19:52.413Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/71/f3/9e59aea0a3568766f3c75ab9d5f4abf661ed9e288292ef0997a71065ca1d/pyobjc_framework_avfoundation-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:370d5f1149c1041028cb1f5fb61b9f56655fe53bbffafc79393b0824a474bef0", size = 83325, upload-time = "2025-11-14T09:37:34.346Z" }, - { url = "https://files.pythonhosted.org/packages/9a/5a/4ef36b309138840ff8cd85364f66c29e27023f291004c335a99f6e87e599/pyobjc_framework_avfoundation-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:82cc2c2d9ab6cc04feeb4700ff251d00f1fcafff573c63d4e87168ff80adb926", size = 83328, upload-time = "2025-11-14T09:37:40.808Z" }, - { url = "https://files.pythonhosted.org/packages/a6/00/ca471e5dd33f040f69320832e45415d00440260bf7f8221a9df4c4662659/pyobjc_framework_avfoundation-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bf634f89265b4d93126153200d885b6de4859ed6b3bc65e69ff75540bc398406", size = 83375, upload-time = "2025-11-14T09:37:47.262Z" }, - { url = "https://files.pythonhosted.org/packages/b3/d4/ade88067deff45858b457648dd82c9363977eb1915efd257232cd06bdac1/pyobjc_framework_avfoundation-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f8ac7f7e0884ac8f12009cdb9d4fefc2f269294ab2ccfd84520a560859b69cec", size = 83413, upload-time = "2025-11-14T09:37:53.759Z" }, - { url = "https://files.pythonhosted.org/packages/a7/3a/fa699d748d6351fa0aeca656ea2f9eacc36e31203dfa56bc13c8a3d26d7d/pyobjc_framework_avfoundation-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:51aba2c6816badfb1fb5a2de1b68b33a23f065bf9e3b99d46ede0c8c774ac7a4", size = 83860, upload-time = "2025-11-14T09:38:00.051Z" }, - { url = "https://files.pythonhosted.org/packages/0c/65/a79cf3b8935a78329ac1107056b91868a581096a90ab6ddff5fd28db4947/pyobjc_framework_avfoundation-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9a3ffd1ae90bd72dbcf2875aa9254369e805b904140362a7338ebf1af54201a6", size = 83629, upload-time = "2025-11-14T09:38:06.697Z" }, - { url = "https://files.pythonhosted.org/packages/8a/03/4125204a17cd7b4de1fdfc38b280a47d0d8f8691a4ee306ebb41b58ff030/pyobjc_framework_avfoundation-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:394c99876b9a38db4851ddf8146db363556895c12e9c711ccd3c3f907ac8e273", size = 83962, upload-time = "2025-11-14T09:38:13.153Z" }, + { url = "https://files.pythonhosted.org/packages/37/6e/38e0187e1f8aad7cf4fcc904a0df5442d1043474181352c73cca426bbf6b/pyobjc_framework_avfoundation-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8af3928933f29ff977663f2c402cb4edc0ba3496d273be4b1400b74e2e103f14", size = 85543, upload-time = "2026-06-19T16:06:20.202Z" }, + { url = "https://files.pythonhosted.org/packages/2f/88/38ccef918dea4188e0001e644f3bc16e26d11959b04838e5e243798f703a/pyobjc_framework_avfoundation-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d3ce4b2bb2ae51ad5c27f1d2f2a623bef8b443bccc34423b5149df010f9f501c", size = 85536, upload-time = "2026-06-19T16:06:21.308Z" }, + { url = "https://files.pythonhosted.org/packages/49/bb/e427b2fd705e9dabbfa0ab89b863305788906e34064db2bb930f1c3ba216/pyobjc_framework_avfoundation-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f372800274df35f8d964cad0ad6da7636f1b6720e802e28e5a6bc1f16f16f135", size = 85578, upload-time = "2026-06-19T16:06:22.292Z" }, + { url = "https://files.pythonhosted.org/packages/98/f5/b38da31d9f95770d0f0f9b9724a898d240d6fb630796e04e9682384d086c/pyobjc_framework_avfoundation-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9291848b0f0f66031bd3af2549e1e0cdf43e4872b9c562bd29f0239c6ef2b75f", size = 85628, upload-time = "2026-06-19T16:06:23.317Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a4/f1bc5fc239015d8fb4414a83592d69b85b8bbdc68f2403c21dbcdcb8ce12/pyobjc_framework_avfoundation-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:3e92a8502d389469b1307a6a2c386f20d2e77878c7e35e3e14120596b47eb205", size = 86077, upload-time = "2026-06-19T16:06:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/4a/a2/4a65d749d57dd16b034ee0935b7daa1228e8a735bf920e9eab9f4aaa9f1e/pyobjc_framework_avfoundation-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:043aedd86adbc0a3bfe8f76d1a1ce920539e47b5dd5d965358f93e0063c9a53d", size = 85836, upload-time = "2026-06-19T16:06:25.377Z" }, + { url = "https://files.pythonhosted.org/packages/bc/49/b9e8f51821a9e5a4a76bea7ab10c2dc1b51b8a141cf4bccbba022f2db19f/pyobjc_framework_avfoundation-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:50e2b65e5c3845eb8ad0d37868a7f040656fe69e6c03f4282670e7dc52ed7a4a", size = 86173, upload-time = "2026-06-19T16:06:26.423Z" }, + { url = "https://files.pythonhosted.org/packages/e1/3d/eda090c53a98293d6ad78eb2a0639e9ab402c14555c2d9e3c311fc482051/pyobjc_framework_avfoundation-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:3f74e199c933df54020b972b985af5a5d2e63134278ea02dfd81923976500794", size = 85889, upload-time = "2026-06-19T16:06:27.517Z" }, + { url = "https://files.pythonhosted.org/packages/a4/58/88126080195ab3fc95b08562ce30f9f599bdabf282b3a4e57bd2c8c2595c/pyobjc_framework_avfoundation-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:1ec2fdfe6eee51f12e7d933e0109ea8ad553acf4301c01f46b79d0a9d378d705", size = 86232, upload-time = "2026-06-19T16:06:28.535Z" }, ] [[package]] name = "pyobjc-framework-avkit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-quartz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/a9/e44db1a1f26e2882c140f1d502d508b1f240af9048909dcf1e1a687375b4/pyobjc_framework_avkit-12.1.tar.gz", hash = "sha256:a5c0ddb0cb700f9b09c8afeca2c58952d554139e9bb078236d2355b1fddfb588", size = 28473, upload-time = "2025-11-14T10:09:43.105Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/b2/8be6b94ad46e50f7f868b487b98ade01914809cf440d5ec892a16600d5c4/pyobjc_framework_avkit-12.2.1.tar.gz", hash = "sha256:9180734ba1ef34000ee0463727dbb73624cc4610a298447c8200aa8a7515e0b6", size = 33618, upload-time = "2026-06-19T16:19:53.448Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/10/128070679c48e6289167d3498a9e6ea5ddc758f74c8d1377aa69cefc2a08/pyobjc_framework_avkit-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ec696a9ba354cdf4271d0076ed2daf7e4779ce809a7dc3d6c9cff4e14c88c1b0", size = 11591, upload-time = "2025-11-14T09:38:15.428Z" }, - { url = "https://files.pythonhosted.org/packages/8c/68/409ee30f3418b76573c70aa05fa4c38e9b8b1d4864093edcc781d66019c2/pyobjc_framework_avkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:78bd31a8aed48644e5407b444dec8b1e15ff77af765607b52edf88b8f1213ac7", size = 11583, upload-time = "2025-11-14T09:38:17.569Z" }, - { url = "https://files.pythonhosted.org/packages/75/34/e77b18f7ed0bd707afd388702e910bdf2d0acee39d1139e8619c916d3eb4/pyobjc_framework_avkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eef2c0a51465de025a4509db05ef18ca2b678bb00ee0a8fbad7fd470edfd58f9", size = 11613, upload-time = "2025-11-14T09:38:19.78Z" }, - { url = "https://files.pythonhosted.org/packages/11/f2/4a55fdc8baca23dd315dab39479203396db54468a4c5a3e2480748ac68af/pyobjc_framework_avkit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c0241548fc7ca3fcd335da05c3dd15d7314fe58debd792317a725d8ae9cf90fa", size = 11620, upload-time = "2025-11-14T09:38:21.904Z" }, - { url = "https://files.pythonhosted.org/packages/d7/37/76d67c86db80f13f0746b493ae025482cb407b875f3138fc6a6e1fd3d5e3/pyobjc_framework_avkit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:869fd54ccdac097abe36d7d4ef8945c80b9c886d881173f590b382f6c743ff12", size = 11824, upload-time = "2025-11-14T09:38:23.777Z" }, - { url = "https://files.pythonhosted.org/packages/29/4e/bd28968f538f5b4f806431c782556aaa5c17567c83edb6df0ef83c7a26ca/pyobjc_framework_avkit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:f49ee90e4f8737ae5dea7579016cdf344b64092810bf5b5acf0cb9c1c6a0d328", size = 11614, upload-time = "2025-11-14T09:38:25.919Z" }, - { url = "https://files.pythonhosted.org/packages/ea/e7/3efb6c782d09abedb74fdecdb374c0b16ccdb43b8da55f47953a4cacf3a6/pyobjc_framework_avkit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:19d46d8da214d8fad03f0a8edd384762dea55933c0c094425a34ac6e53eacb71", size = 11827, upload-time = "2025-11-14T09:38:27.716Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b2/bdf77ecb7c62143198d023488f60dd63de4130b8bbc0b806cc6739994c4b/pyobjc_framework_avkit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b0a0b8a0d3a7292d6411ecab298f7448876fae61c22f6d41a4339f3c191ce69f", size = 12344, upload-time = "2026-06-19T16:06:29.535Z" }, + { url = "https://files.pythonhosted.org/packages/19/d0/595fdb5f088b01f214822b3a36fb2fb91e420a46b5cff52c859ff6817506/pyobjc_framework_avkit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5114a50272cc0afb7df802329b498fd4e8340adf5fc0614a251264b09ce3bb14", size = 12344, upload-time = "2026-06-19T16:06:30.76Z" }, + { url = "https://files.pythonhosted.org/packages/c9/62/ef72553ed6ff62578f9618551f84d76be247082f9c0baabf8b8672f65f29/pyobjc_framework_avkit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f8514a6ca882e55738bf5c2c8fd498b4920b3344481ca6747b32526fe99ac7c4", size = 12375, upload-time = "2026-06-19T16:06:31.682Z" }, + { url = "https://files.pythonhosted.org/packages/9f/18/9812526e8246048e8873ca243868ed1766e4e25335e33deff79c2802ae3b/pyobjc_framework_avkit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7743cd8455031f2580188d6428411516bd927fef619b3764c1450379f030ea75", size = 12387, upload-time = "2026-06-19T16:06:32.49Z" }, + { url = "https://files.pythonhosted.org/packages/ae/ae/da5f02922f7ea01e4e36cb62029c8ea4110520bb4a0edd3f5b3cf761cb29/pyobjc_framework_avkit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9dce2ef4ecb2dd97fecdb25eccf2c1506b5406699e5cf50c3124dc231e6729c0", size = 12575, upload-time = "2026-06-19T16:06:33.328Z" }, + { url = "https://files.pythonhosted.org/packages/e5/86/6d8fcdd0a79bfc1486a9db2ff1548eb561561aa20ed11c0e8923a7945558/pyobjc_framework_avkit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:eba6105589792316b40ce2b23f4957d0185130daf482c37708424b5e61a5a7bf", size = 12396, upload-time = "2026-06-19T16:06:34.136Z" }, + { url = "https://files.pythonhosted.org/packages/d1/28/81aaed729f87f904339f072dd89e5f79807bcfc1ceca0362fbbd1276fb4c/pyobjc_framework_avkit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:2424165d424359a2eaa4f593fe71864c2aa167efa476226fa0be2d07676be967", size = 12589, upload-time = "2026-06-19T16:06:35.11Z" }, + { url = "https://files.pythonhosted.org/packages/40/15/82a1e4f9400894e09353f2f6a2faba85ea27d0b3e2bad9f6308ee1f62d44/pyobjc_framework_avkit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:3cf369d8a7afc4585a6abdc8f13fc7bc41da3cba36459fcd9cd3b7fa8f7968d9", size = 12386, upload-time = "2026-06-19T16:06:35.93Z" }, + { url = "https://files.pythonhosted.org/packages/2b/5a/620afa55572fd2302c6a3495a26c6ba6f781c4e0c559607348507b5aced8/pyobjc_framework_avkit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:fc3994a696f550393558dc974b7c8f72dd8ca43cf645e5c755f7d5ac167dc93d", size = 12584, upload-time = "2026-06-19T16:06:36.937Z" }, ] [[package]] name = "pyobjc-framework-avrouting" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6e/83/15bf6c28ec100dae7f92d37c9e117b3b4ee6b4873db062833e16f1cfd6c4/pyobjc_framework_avrouting-12.1.tar.gz", hash = "sha256:6a6c5e583d14f6501df530a9d0559a32269a821fc8140e3646015f097155cd1c", size = 20031, upload-time = "2025-11-14T10:09:45.701Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/74/75a7a33349407c3801fd014cfdfa6ceb3e8e83a020277d5e99eedb3a3728/pyobjc_framework_avrouting-12.2.1.tar.gz", hash = "sha256:8fd237f7a5c8d905f194fcdfeb6771e69c7106fc544c24e9725d952505f0fdc7", size = 20905, upload-time = "2026-06-19T16:19:54.202Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/3a/3172fb969eaa782859ed466f9cbeb2ee8771da5a340bb052a34b54efda90/pyobjc_framework_avrouting-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9724fcd834a8ee206df25139c3033bd508a4a830ac6d91dd7c611a03085386", size = 8431, upload-time = "2025-11-14T09:38:30.93Z" }, - { url = "https://files.pythonhosted.org/packages/69/a7/5c5725db9c91b492ffbd4ae3e40025deeb9e60fcc7c8fbd5279b52280b95/pyobjc_framework_avrouting-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a79f05fb66e337cabc19a9d949c8b29a5145c879f42e29ba02b601b7700d1bb", size = 8431, upload-time = "2025-11-14T09:38:33.018Z" }, - { url = "https://files.pythonhosted.org/packages/68/54/fa24f666525c1332a11b2de959c9877b0fe08f00f29ecf96964b24246c13/pyobjc_framework_avrouting-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4c0fb0d3d260527320377a70c87688ca5e4a208b09fddcae2b4257d7fe9b1e18", size = 8450, upload-time = "2025-11-14T09:38:34.941Z" }, - { url = "https://files.pythonhosted.org/packages/3b/a4/cdbbe5745a49c9c5f5503dbbdd1b90084d4be83bd8503c998db160bb378e/pyobjc_framework_avrouting-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:18c62af1ce9ac99b04c36f66959ca64530d51b62aa0e6f00400dea600112e370", size = 8465, upload-time = "2025-11-14T09:38:37.638Z" }, - { url = "https://files.pythonhosted.org/packages/29/d7/c709d277e872495f452fe797c619d9b202cd388b655ccf7196724dbbb600/pyobjc_framework_avrouting-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e5a1d2e4e431aae815e38b75dbe644aa1fd495f8ec1e2194fc175132d7cfc1d3", size = 8630, upload-time = "2025-11-14T09:38:39.284Z" }, - { url = "https://files.pythonhosted.org/packages/b0/0a/9e9bf48c70f129c1fa42e84e091901b6aa6d11074365d93aa22a42d13ba6/pyobjc_framework_avrouting-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:defaad8e98793dfaceb7e36eba3da9bf92d0840207d39e39b018ce6eb41d80f8", size = 8525, upload-time = "2025-11-14T09:38:41.001Z" }, - { url = "https://files.pythonhosted.org/packages/33/75/56ab32b061b4a51f661998ef96ca91a34aee86527e6a4d5f4f10db906066/pyobjc_framework_avrouting-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:c5f80ba96f5f874193fc0d9656aa6b4ed0df43c7c88ecfbf6cd4760d75776157", size = 8687, upload-time = "2025-11-14T09:38:43.215Z" }, + { url = "https://files.pythonhosted.org/packages/f4/60/f4f65c9befb7d7bdc1448d0e874d87e3de2312b59449814fe82618fe4476/pyobjc_framework_avrouting-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:56cfdd0b438ca5fa3e6495a063c5c3a98d21b30e136c908ed8340b846630bad5", size = 8477, upload-time = "2026-06-19T16:06:37.928Z" }, + { url = "https://files.pythonhosted.org/packages/ae/63/6a6b596706bbe9335e8fc1b6d41f89afd94b09d876d69a9c77b6aabd2085/pyobjc_framework_avrouting-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ccdd40b590221f2db3969bfc84782cb720163647b2e8aa4c6c623c59a4e07740", size = 8476, upload-time = "2026-06-19T16:06:39.353Z" }, + { url = "https://files.pythonhosted.org/packages/df/73/b54c6d24904c3a2eef50a12f3ec0d77d7e2d74567164e088a26b3aac629a/pyobjc_framework_avrouting-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e31a7399fc04d8cdc63f2cd26809dc670acfd6bed4674288df4622c18123a43", size = 8493, upload-time = "2026-06-19T16:06:40.129Z" }, + { url = "https://files.pythonhosted.org/packages/07/fd/69f18627456e0e9e5bb2838bd1c015c662fa351adb6162d03337a52eec81/pyobjc_framework_avrouting-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2678d7f058cde4156c6c78cc87d71636c432071d0eee10c0d4263a21e6ef23f3", size = 8509, upload-time = "2026-06-19T16:06:41.098Z" }, + { url = "https://files.pythonhosted.org/packages/3b/8c/8d8122f40b14c69cdc6a86d5211998ecda10cd86bb3fa9c9f27c00c6c3f3/pyobjc_framework_avrouting-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:00401319b0fc450af6ea673af2733543b5e4a4e85b53a8ba15e42106f77a645f", size = 8673, upload-time = "2026-06-19T16:06:41.925Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/1fe8aec1f8c37a331fe9ac7abff251d26b5211e941bec8f5d70ddb41d825/pyobjc_framework_avrouting-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:849822d569032704d68d28eb6395c8e7bffac6966c9212066f5939e1844df5bb", size = 8566, upload-time = "2026-06-19T16:06:42.818Z" }, + { url = "https://files.pythonhosted.org/packages/ea/7c/1a31edec8cc1f6cfb41d4a70ebef488d04e5ea22facda4699bc05541ddd0/pyobjc_framework_avrouting-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:58b73bb613e6c7dc26c76a6ae2ab759dd58207847c333d9b1d0fa384bd2fbd61", size = 8728, upload-time = "2026-06-19T16:06:43.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/c6/4904ad65742b3b751291478d89a83367433a01569966bc24941ca65909ee/pyobjc_framework_avrouting-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:1e112761fdaf8fa4558d908aa70b71b2ac1c3d867dd87217eaec7b2c884ac792", size = 8557, upload-time = "2026-06-19T16:06:44.582Z" }, + { url = "https://files.pythonhosted.org/packages/c1/6b/0d2392882edf18c6441c25b40e86a455a67a7710168c0f307bdc4128cb68/pyobjc_framework_avrouting-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:835f4c32a2ee715e1ce9b694e448279bad9c1912e23725323acb0f5d8b968716", size = 8726, upload-time = "2026-06-19T16:06:45.504Z" }, ] [[package]] name = "pyobjc-framework-backgroundassets" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/d1/e917fba82790495152fd3508c5053827658881cf7e9887ba60def5e3f221/pyobjc_framework_backgroundassets-12.1.tar.gz", hash = "sha256:8da34df9ae4519c360c429415477fdaf3fbba5addbc647b3340b8783454eb419", size = 26210, upload-time = "2025-11-14T10:09:48.792Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/92/2b680e44df5d3a81a2431acfbf6eb2e6fba93f1c382651a77040060d4a83/pyobjc_framework_backgroundassets-12.2.1.tar.gz", hash = "sha256:771fc7a45a10a4b4afb5465b1528958078f456629b63c36a7a43505f93acbaea", size = 29376, upload-time = "2026-06-19T16:19:55.028Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/28/6684b16c1d69305e8802732c7069d0e0d9b72b8f9b020ebec8f9da719798/pyobjc_framework_backgroundassets-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5af9d7db623e14876e3cbb79cd4234558162dad307fd341d141089652bb94bed", size = 10761, upload-time = "2025-11-14T09:38:44.917Z" }, - { url = "https://files.pythonhosted.org/packages/c1/49/33c1c3eaf26a7d89dd414e14939d4f02063d66252d0f51c02082350223e0/pyobjc_framework_backgroundassets-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:17de7990b5ea8047d447339f9e9e6f54b954ffc06647c830932a1688c4743fea", size = 10763, upload-time = "2025-11-14T09:38:46.671Z" }, - { url = "https://files.pythonhosted.org/packages/de/34/bbba61f0e8ecb0fe0da7aa2c9ea15f7cb0dca2fb2914fcdcd77b782b5c11/pyobjc_framework_backgroundassets-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2c11cb98650c1a4bc68eeb4b040541ba96613434c5957e98e9bb363413b23c91", size = 10786, upload-time = "2025-11-14T09:38:48.341Z" }, - { url = "https://files.pythonhosted.org/packages/04/9b/872f9ff0593ffb9dbc029dc775390b0e45fe3278068b28aade8060503003/pyobjc_framework_backgroundassets-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a089a71b2db471f5af703e35f7a61060164d61eb60a3f482076826dfa5697c7c", size = 10803, upload-time = "2025-11-14T09:38:49.996Z" }, - { url = "https://files.pythonhosted.org/packages/cc/44/4afc2e8bcf16919b1ab82eaf88067469ea255b0a3390d353fec1002dbd0a/pyobjc_framework_backgroundassets-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e8c560f1aaa7a4bf6e336806749ce0a20f2a792ab924d9424714e299a59b3edf", size = 11058, upload-time = "2025-11-14T09:38:51.743Z" }, - { url = "https://files.pythonhosted.org/packages/f1/8b/80cd655122c20fd29edd3b2b609e6be006cef4bdc830d71944399c6abcd5/pyobjc_framework_backgroundassets-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:57d77b1babd450b18e32e852a47dd1095329323e1bed9f258b46c43e20e6d0fc", size = 10854, upload-time = "2025-11-14T09:38:53.386Z" }, - { url = "https://files.pythonhosted.org/packages/11/24/4048476f84c0566c1e146dbbd20a637bda14df5c1e52dc907e23b0329ab2/pyobjc_framework_backgroundassets-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:acaa091ff12acb24536745803af95e10d535b22e2e123fd2dd5920f3d47338ee", size = 11061, upload-time = "2025-11-14T09:38:55.043Z" }, + { url = "https://files.pythonhosted.org/packages/5c/7b/dc5eb6724fce5d08f28771088424f438746d0bd2138241c0c5413c04050f/pyobjc_framework_backgroundassets-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0742738cbf4531b17eeb2236d81f020b8c3771e75d332a042e1ac2451d74dad0", size = 10924, upload-time = "2026-06-19T16:06:46.308Z" }, + { url = "https://files.pythonhosted.org/packages/e3/02/9200ca88a64e1f0c74330c45610761914d5814aec48bf457d1fe6f9fbb78/pyobjc_framework_backgroundassets-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:071058cbf4a83f11d5abf39d04fb2e449938eb2c2180d4c9f4dd0568577c6c2d", size = 10928, upload-time = "2026-06-19T16:06:47.161Z" }, + { url = "https://files.pythonhosted.org/packages/19/32/9a08bc3f2aa8eeb5d5be2801a5766a755856b079b3e7afbc1fe31d679dba/pyobjc_framework_backgroundassets-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dac0d3a65a95ea886598f381080c4efffc3acfabcca3e692550fbe615627db7e", size = 10942, upload-time = "2026-06-19T16:06:47.915Z" }, + { url = "https://files.pythonhosted.org/packages/a0/89/4e42e961fd771db5c4ba3243ea499926b8242c681671818dcb613f41add7/pyobjc_framework_backgroundassets-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bd07fcd0324ba4c6ba00c0eb3af6832f102e51895afd1933fe9c0c2efe9b9734", size = 10965, upload-time = "2026-06-19T16:06:48.669Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d8/c05a4e2433c1eb61773fb4f4ff46ef6dec4926af4aa98c8a6776bf3cb95f/pyobjc_framework_backgroundassets-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:2730c6e01c16d9c42cc46b0c23d37b3ce9de70cd0a93a2f712f44a84bd786bb0", size = 11218, upload-time = "2026-06-19T16:06:49.531Z" }, + { url = "https://files.pythonhosted.org/packages/11/36/0c38885fb8e03428c5d451453830f91386f7522087e4e4919bbfe9e1375a/pyobjc_framework_backgroundassets-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9b4cd5797eeaae395ab00da89a141396a3f8c29bed13481ca52cbd66eba07406", size = 11013, upload-time = "2026-06-19T16:06:50.321Z" }, + { url = "https://files.pythonhosted.org/packages/c7/6a/aee1901a06775f0a8716005fcab6a6ebd38b66e8dcc3d864b7414b23326e/pyobjc_framework_backgroundassets-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e315f1e22c54603dfd7a924a916cee4975c444ed097e34c5e4f2667e50ddc471", size = 11218, upload-time = "2026-06-19T16:06:51.236Z" }, + { url = "https://files.pythonhosted.org/packages/27/d4/75270e3d290e6387b61ef780629daf5f40f5f5eef85049db0a91c4187196/pyobjc_framework_backgroundassets-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:16573c33421d45b7eb5354c7ce1d2e21141404c93d3500cf4702ab37e1372dee", size = 11009, upload-time = "2026-06-19T16:06:52.085Z" }, + { url = "https://files.pythonhosted.org/packages/0b/df/8a564b1bc0fe6d8a308eb7c7a72cf9fde0b0f7bf8461d4b1d7292bb7ff83/pyobjc_framework_backgroundassets-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:b848dfe93b3afddae60c5bd9b6b899c51f89591faebe3e7788b37928aead2d5b", size = 11218, upload-time = "2026-06-19T16:06:52.845Z" }, ] [[package]] name = "pyobjc-framework-browserenginekit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, @@ -1718,97 +1794,103 @@ dependencies = [ { name = "pyobjc-framework-coremedia" }, { name = "pyobjc-framework-quartz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5d/b9/39f9de1730e6f8e73be0e4f0c6087cd9439cbe11645b8052d22e1fb8e69b/pyobjc_framework_browserenginekit-12.1.tar.gz", hash = "sha256:6a1a34a155778ab55ab5f463e885f2a3b4680231264e1fe078e62ddeccce49ed", size = 29120, upload-time = "2025-11-14T10:09:51.582Z" } +sdist = { url = "https://files.pythonhosted.org/packages/40/45/955a13e96d79e369754a99ad185b59edb721e95196a893f215f299bcb9bc/pyobjc_framework_browserenginekit-12.2.1.tar.gz", hash = "sha256:6e07b9582fb7e9b9a9ea40280d5815e4130cd0f9194fdc6bdd3a3bc07d6d2f85", size = 32607, upload-time = "2026-06-19T16:19:55.949Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/f5/3d8dadd9f2da1f5a754fa7b2433013addeb56b1a3c512b6e147503b25eb7/pyobjc_framework_browserenginekit-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8161ac3a9e19b794d47c0022cd2b956818956bda355e4897a687850faf6ab380", size = 11527, upload-time = "2025-11-14T09:38:56.874Z" }, - { url = "https://files.pythonhosted.org/packages/f2/a4/2d576d71b2e4b3e1a9aa9fd62eb73167d90cdc2e07b425bbaba8edd32ff5/pyobjc_framework_browserenginekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:41229c766fb3e5bba2de5e580776388297303b4d63d3065fef3f67b77ec46c3f", size = 11526, upload-time = "2025-11-14T09:38:58.861Z" }, - { url = "https://files.pythonhosted.org/packages/46/e0/8d2cebbfcfd6aacb805ae0ae7ba931f6a39140540b2e1e96719e7be28359/pyobjc_framework_browserenginekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d15766bb841b081447015c9626e2a766febfe651f487893d29c5d72bef976b94", size = 11545, upload-time = "2025-11-14T09:39:00.988Z" }, - { url = "https://files.pythonhosted.org/packages/5b/2c/d39ab696b0316e1faf112a3aee24ef3bcb5fb42eb5db18ba2d74264a41a8/pyobjc_framework_browserenginekit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1aa2da131bbdf81748894c18d253cd2711dc535f1711263c6c604e20cdc094a6", size = 11567, upload-time = "2025-11-14T09:39:02.811Z" }, - { url = "https://files.pythonhosted.org/packages/0e/dd/624d273beea036ec20e16f8bdaaca6b062da647b785dedf90fa2a92a8cc0/pyobjc_framework_browserenginekit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:657d78bb5c1a51097560cb3219692321640d0d5c8e57e9160765e1ecfb3fe7ef", size = 11738, upload-time = "2025-11-14T09:39:04.651Z" }, - { url = "https://files.pythonhosted.org/packages/13/4d/a340f75fc6daa482d9d3470fe449da0d8e1263a6f77803f2b1185b3a69af/pyobjc_framework_browserenginekit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ad7896751accf7a6f866e64e8155f97b6cf0fc0e6efd64e9940346d8fbf0ec66", size = 11620, upload-time = "2025-11-14T09:39:06.752Z" }, - { url = "https://files.pythonhosted.org/packages/3d/fa/5c0278bfebee573d97fd78ee0f41c9e8cb8f7a79ed7e4bd6a8f8ee00abe4/pyobjc_framework_browserenginekit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:c52a3b0000e67fbaa51eef0b455d90b1140e3f6a0014945227cedf242fa57dcc", size = 11805, upload-time = "2025-11-14T09:39:09.033Z" }, + { url = "https://files.pythonhosted.org/packages/47/fd/a5a32745b5cac8e97a96e32d16d24f05fa96cb1087be35b9cdfbd8c90e96/pyobjc_framework_browserenginekit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:af55aef42bd8dc15f7a88e4b788252ace487dbb1810f29ff6a55abd62c39ffcf", size = 11740, upload-time = "2026-06-19T16:06:53.658Z" }, + { url = "https://files.pythonhosted.org/packages/35/ac/25182295ad94f504b1ab99f291e377d0e54ffeda7f95073a257403cdca43/pyobjc_framework_browserenginekit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ca3005e7b6bbeb790c9604eae3ff1f3f1bb6aa9e524a9b36606904147f0c2596", size = 11740, upload-time = "2026-06-19T16:06:54.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ff/f2ecf8207db7a91a7ae778d041cce343e847ee63fc5975fbe1d53d9b62a7/pyobjc_framework_browserenginekit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a57b1e941107346d8b948201501654541ed40a361dac81c20c154d428b8a0c37", size = 11760, upload-time = "2026-06-19T16:06:55.761Z" }, + { url = "https://files.pythonhosted.org/packages/18/98/ad604a0a16bce24d8297d4a651d6ad207137df507f59ff69271fad084bfa/pyobjc_framework_browserenginekit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:eb7148c7f32c98d40046eb3338f7ceb1ab055edb122065a7b37cbc84865aa41a", size = 11782, upload-time = "2026-06-19T16:06:56.666Z" }, + { url = "https://files.pythonhosted.org/packages/3e/c8/baae0dc88d8f52cfbdc5623e9ae718fc44c9d72b1c9edbd85a525fc47c6f/pyobjc_framework_browserenginekit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:52b212f57572e5d3b31b2dafcbe6e1e47c4af025cb396aba90ac74101ae0e717", size = 11949, upload-time = "2026-06-19T16:06:57.487Z" }, + { url = "https://files.pythonhosted.org/packages/95/20/e636a02bb9ee6b546054aa098ff8ff4d66e62413a91d31ac78e229b9717c/pyobjc_framework_browserenginekit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0b55960a96fe709ba9362638e794a887afd8c66ebcd75ccb758b4bf87233a249", size = 11830, upload-time = "2026-06-19T16:06:58.255Z" }, + { url = "https://files.pythonhosted.org/packages/64/df/d2a7a7fd5ab5d86dda2256c891ae96c722c53099f8875fd09a06eef74904/pyobjc_framework_browserenginekit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:2884addef8f1f987cc625f4f123dde6e8e3eaf58915bc2452aa4090c24388338", size = 12021, upload-time = "2026-06-19T16:06:59.066Z" }, + { url = "https://files.pythonhosted.org/packages/e9/dc/ce1b20bacc0dad78d864f0b350e97a38588d02403f629aed1616ca23b97e/pyobjc_framework_browserenginekit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:a20ca22bd18a0d773cd483499761690cf00a6241a5a3186dd407e52ce054a36d", size = 11824, upload-time = "2026-06-19T16:07:00.051Z" }, + { url = "https://files.pythonhosted.org/packages/79/2e/8f3a89bab39a61cca4f707f3b7f09b650b2a94040c438a29d621484669b9/pyobjc_framework_browserenginekit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:ca3a58c7be5463c65566d2b41d18cf5b9f0a1f70f27540c1a108e74d2487f1e3", size = 12016, upload-time = "2026-06-19T16:07:01.071Z" }, ] [[package]] name = "pyobjc-framework-businesschat" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4d/da/bc09b6ed19e9ea38ecca9387c291ca11fa680a8132d82b27030f82551c23/pyobjc_framework_businesschat-12.1.tar.gz", hash = "sha256:f6fa3a8369a1a51363e1757530128741d9d09ed90692a1d6777a4c0fbad25868", size = 12055, upload-time = "2025-11-14T10:09:53.436Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/26/c92176248363ef510e991c7b537de7aaa4c66b232de1d6c7c527270d9911/pyobjc_framework_businesschat-12.2.1.tar.gz", hash = "sha256:2847c422d202fb8e1eb892c7151b2251b2880a5e6618b7affbdec2b5521a6072", size = 12409, upload-time = "2026-06-19T16:19:56.76Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/88/4c727424b05efa33ed7f6c45e40333e5a8a8dc5bb238e34695addd68463b/pyobjc_framework_businesschat-12.1-py2.py3-none-any.whl", hash = "sha256:f66ce741507b324de3c301d72ba0cfa6aaf7093d7235972332807645c118cc29", size = 3474, upload-time = "2025-11-14T09:39:10.771Z" }, + { url = "https://files.pythonhosted.org/packages/6b/58/cbe465cb6fcd155af2de412982e59ee9cc354be1b477cb0441e089613f57/pyobjc_framework_businesschat-12.2.1-py2.py3-none-any.whl", hash = "sha256:61ef7e7fb1846a1731c7e7268e032ed6fd2e6df3de195c6c35d4df4c81e18801", size = 3501, upload-time = "2026-06-19T16:07:01.885Z" }, ] [[package]] name = "pyobjc-framework-calendarstore" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/88/41/ae955d1c44dcc18b5b9df45c679e9a08311a0f853b9d981bca760cf1eef2/pyobjc_framework_calendarstore-12.1.tar.gz", hash = "sha256:f9a798d560a3c99ad4c0d2af68767bc5695d8b1aabef04d8377861cd1d6d1670", size = 52272, upload-time = "2025-11-14T10:09:58.48Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/f7/65fe8ddcfd0e88442139b9dd737184aeb6f83d6748315061190ecd0987ad/pyobjc_framework_calendarstore-12.2.1.tar.gz", hash = "sha256:5659f2d59dd49423d3880295cd4b395a61c0b7aea8db654e63dab6dd32d28dee", size = 54448, upload-time = "2026-06-19T16:19:57.485Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/70/f68aebdb7d3fa2dec2e9da9e9cdaa76d370de326a495917dbcde7bb7711e/pyobjc_framework_calendarstore-12.1-py2.py3-none-any.whl", hash = "sha256:18533e0fcbcdd29ee5884dfbd30606710f65df9b688bf47daee1438ee22e50cc", size = 5285, upload-time = "2025-11-14T09:39:12.473Z" }, + { url = "https://files.pythonhosted.org/packages/f9/30/f11d28e0d4bea633a5a99e185b5574a8a04a0944372d05bd0a28167f2156/pyobjc_framework_calendarstore-12.2.1-py2.py3-none-any.whl", hash = "sha256:d144a2e2b2566b2954f0e365bf7592ccd17cb459165805ed73c6ed8d0feb2ede", size = 5312, upload-time = "2026-06-19T16:07:03.099Z" }, ] [[package]] name = "pyobjc-framework-callkit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1a/c0/1859d4532d39254df085309aff55b85323576f00a883626325af40da4653/pyobjc_framework_callkit-12.1.tar.gz", hash = "sha256:fd6dc9688b785aab360139d683be56f0844bf68bf5e45d0eb770cb68221083cc", size = 29171, upload-time = "2025-11-14T10:10:01.336Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/0b/0cef47cd370e195c113125205e83443e23a1da838df7419ad9131539b266/pyobjc_framework_callkit-12.2.1.tar.gz", hash = "sha256:66dfbb864c6aa253ff90ac91cdc23dc7158dee8eb76b1764126f2eae59e771a8", size = 32653, upload-time = "2026-06-19T16:19:58.251Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/00/cca0a68bc794afe570a0633d886d0476fe9cecaf6800364eeec77f1a3e6a/pyobjc_framework_callkit-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:57e8c04d8e4f9358d0fe1f14862caf9aa74ae5ba90c8cae1751798a24b459166", size = 11275, upload-time = "2025-11-14T09:39:14.458Z" }, - { url = "https://files.pythonhosted.org/packages/2b/f6/aafd14b31e00d59d830f9a8e8e46c4f41a249f0370499d5b017599362cf1/pyobjc_framework_callkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e73beae08e6a32bcced8d5bdb45b52d6a0866dd1485eaaddba6063f17d41fcb0", size = 11273, upload-time = "2025-11-14T09:39:16.837Z" }, - { url = "https://files.pythonhosted.org/packages/2e/b7/b3a498b14751b4be6af5272c9be9ded718aa850ebf769b052c7d610a142a/pyobjc_framework_callkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:12adc0ace464a057f8908187698e1d417c6c53619797a69d096f4329bffb1089", size = 11334, upload-time = "2025-11-14T09:39:18.622Z" }, - { url = "https://files.pythonhosted.org/packages/37/30/f434921c17a59d8db06783189ca98ccf291d5366be364f96439e987c1b13/pyobjc_framework_callkit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b8909402f8690ea2fe8fa7c0256b5c491435f20881832808b86433f526ff28f8", size = 11347, upload-time = "2025-11-14T09:39:20.412Z" }, - { url = "https://files.pythonhosted.org/packages/f0/b8/c6a52c3c2e1e0bd23a84fef0d2cb089c456d62add59f87d8510ffe871068/pyobjc_framework_callkit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9ec6635b6a6fecde6e5252ceff76c71d699ed8e0f3ebc6fd220a351dc653040b", size = 11558, upload-time = "2025-11-14T09:39:22.266Z" }, - { url = "https://files.pythonhosted.org/packages/e3/db/e8bcdde2b9cf109ebdf389e730900de7acf792664aa0a7fbc630cd61a82a/pyobjc_framework_callkit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a2438a252ff428bca1c1d1db2fca921d2cc572ee5c582f000a713fb61b29324f", size = 11333, upload-time = "2025-11-14T09:39:24.326Z" }, - { url = "https://files.pythonhosted.org/packages/2b/14/4bb4718a4dab3040c23d91c01283ae46cbfd4b709692ef98dae92e4a3247/pyobjc_framework_callkit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:b6a1767e7391652ef75eb46d12d49f31f591063da45357aad2c4e0d40f8fe702", size = 11556, upload-time = "2025-11-14T09:39:26.174Z" }, + { url = "https://files.pythonhosted.org/packages/24/dd/4f65a009104e443a0eea51eb6047b3666c2b81e7817b2cc502f8900679e8/pyobjc_framework_callkit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4950ebb651243765d205e051b5057542624476c2a5e3c21828e490de1cc8062e", size = 11331, upload-time = "2026-06-19T16:07:04.122Z" }, + { url = "https://files.pythonhosted.org/packages/99/c3/29d18f69cf2478b241844e303cf176e906eb1085795a2fe57e37b8679888/pyobjc_framework_callkit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c3ebce966fc18c2d1a0aa36899f4c33c10b190e8b310d03649a65f61e437b91e", size = 11330, upload-time = "2026-06-19T16:07:05.031Z" }, + { url = "https://files.pythonhosted.org/packages/e7/6a/27a76b1153822a64624dffb9cf93e0f815e3e8d618e4c6e49101cb602645/pyobjc_framework_callkit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e259627de99e751d2f05f39d135281a58f75a18d3c5ba9dda27c6bf88bc673a4", size = 11387, upload-time = "2026-06-19T16:07:05.784Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a5/c05d4ac28acdbd24c0eb80b73f6a2968943892a624a90289f7d46c07205d/pyobjc_framework_callkit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:afeea8cf2d302994481ebc4e7889168138b2ba82892496866135ad666023e1ed", size = 11402, upload-time = "2026-06-19T16:07:06.766Z" }, + { url = "https://files.pythonhosted.org/packages/01/20/2fe9ef2735187d4f587a7ae698de5eb34fdaa5cbdc1abb771b3c62d9e3b5/pyobjc_framework_callkit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9cc081df5897d0253c4924ff6da6f7c5c03ab6bca07302c99eea6e26738050a4", size = 11616, upload-time = "2026-06-19T16:07:07.628Z" }, + { url = "https://files.pythonhosted.org/packages/5c/5b/4be62a9902ac84c347c893583615227ea479dbf94edf6bad51c314ff94fb/pyobjc_framework_callkit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:3c0bea7ed9c38d1c5e4a5ebf01a99b93e3eb9cee83ed3de1c3386ce2294bde55", size = 11387, upload-time = "2026-06-19T16:07:08.44Z" }, + { url = "https://files.pythonhosted.org/packages/c0/e6/a542b46d954429de9f84e164877abd4cba65441792b31a6f39a087c77993/pyobjc_framework_callkit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:b26b5ab9f54dcfd629458a85d53288595df40aac2c51df0493c9e03967f9cf29", size = 11616, upload-time = "2026-06-19T16:07:09.257Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9a/d85b65ff7708699141b4af62be7def5fd1cbc5f82b0ccb0f74642a7488a0/pyobjc_framework_callkit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:40d424890b084261ffb8f17f68b815c4724e529d985b88281eb48c76a325c148", size = 11394, upload-time = "2026-06-19T16:07:10.044Z" }, + { url = "https://files.pythonhosted.org/packages/62/e4/08d9e342c89c3f96f0783edff6192fafd56f5cfabb23323bf35ccbae832d/pyobjc_framework_callkit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:446f8c3bc014ebea37e191539971b1dc39f10a259db0e7d552e738249ae43252", size = 11620, upload-time = "2026-06-19T16:07:10.957Z" }, ] [[package]] name = "pyobjc-framework-carbon" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0c/0f/9ab8e518a4e5ac4a1e2fdde38a054c32aef82787ff7f30927345c18b7765/pyobjc_framework_carbon-12.1.tar.gz", hash = "sha256:57a72807db252d5746caccc46da4bd20ff8ea9e82109af9f72735579645ff4f0", size = 37293, upload-time = "2025-11-14T10:10:04.464Z" } +sdist = { url = "https://files.pythonhosted.org/packages/07/1f/c5df0b0f276542be355e56541af59ae77e98b22e5265d483601a2324c4e0/pyobjc_framework_carbon-12.2.1.tar.gz", hash = "sha256:a14ca4a45e697c2d187753bc851f3bb49d5bcf66d4ef1d2363fd9962417d8638", size = 39755, upload-time = "2026-06-19T16:19:59.099Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/9e/91853c8f98b9d5bccf464113908620c94cc12c2a3e4625f3ce172e3ea4bc/pyobjc_framework_carbon-12.1-py2.py3-none-any.whl", hash = "sha256:f8b719b3c7c5cf1d61ac7c45a8a70b5e5e5a83fa02f5194c2a48a7e81a3d1b7f", size = 4625, upload-time = "2025-11-14T09:39:27.937Z" }, + { url = "https://files.pythonhosted.org/packages/02/08/6ec19751ee486409164e94ca6bba29050c491467540cefed1398ac520c09/pyobjc_framework_carbon-12.2.1-py2.py3-none-any.whl", hash = "sha256:603cf2bced196dd90d6400bcbca32ab573166fc4058193bfbd7c1bd95cdefd4c", size = 4646, upload-time = "2026-06-19T16:07:11.76Z" }, ] [[package]] name = "pyobjc-framework-cfnetwork" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d2/6a/f5f0f191956e187db85312cbffcc41bf863670d121b9190b4a35f0d36403/pyobjc_framework_cfnetwork-12.1.tar.gz", hash = "sha256:2d16e820f2d43522c793f55833fda89888139d7a84ca5758548ba1f3a325a88d", size = 44383, upload-time = "2025-11-14T10:10:08.428Z" } +sdist = { url = "https://files.pythonhosted.org/packages/78/96/189e013d494489e6468d9b0b81c4b0e2f338574e1a777b7a43a6b37573e6/pyobjc_framework_cfnetwork-12.2.1.tar.gz", hash = "sha256:cadc9f65a97c20cf839259229c34ecdb5b54a3ade816c43374a1eb25d3900925", size = 47652, upload-time = "2026-06-19T16:20:00.352Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/21/1cebc1396e966d4acede1e3368b3cf8c2def32f4b35f8c65fd003a3f5510/pyobjc_framework_cfnetwork-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d54206b13f44f2503db53efbdb76d892c719959fb620266875d9934ceb586f2d", size = 18945, upload-time = "2025-11-14T09:39:30.172Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7e/82aca783499b690163dd19d5ccbba580398970874a3431bfd7c14ceddbb3/pyobjc_framework_cfnetwork-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3bf93c0f3d262f629e72f8dd43384d0930ed8e610b3fc5ff555c0c1a1e05334a", size = 18949, upload-time = "2025-11-14T09:39:32.924Z" }, - { url = "https://files.pythonhosted.org/packages/f9/0b/28034e63f3a25b30ede814469c3f57d44268cbced19664c84a8664200f9d/pyobjc_framework_cfnetwork-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:92760da248c757085fc39bce4388a0f6f0b67540e51edf60a92ad60ca907d071", size = 19135, upload-time = "2025-11-14T09:39:36.382Z" }, - { url = "https://files.pythonhosted.org/packages/f4/36/d6b95a5b156de5e2c071ecb7f7056f0badb3a0d09e0dbcf0d8d35743f822/pyobjc_framework_cfnetwork-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:86cc3f650d3169cd8ce4a1438219aa750accac0efc29539920ab0a7e75e25ab4", size = 19135, upload-time = "2025-11-14T09:39:39.95Z" }, - { url = "https://files.pythonhosted.org/packages/4b/23/ff66133af4592e123320337f443aa6e36993cc48d6c10f6e7436e01678b1/pyobjc_framework_cfnetwork-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5ff3e246e5186b9bad23b2e4e856ca87eaa9329f5904643c5484510059a07e24", size = 19412, upload-time = "2025-11-14T09:39:42.412Z" }, - { url = "https://files.pythonhosted.org/packages/6e/63/931cda003b627cc04c8e5bf9efecc391006305462192414b3d29eb16b5fd/pyobjc_framework_cfnetwork-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b94c190bdfdf0c8f3f6f7bf8e19ccc2847ecb67adab0068f8d12a25ab7df3c1a", size = 19185, upload-time = "2025-11-14T09:39:45.245Z" }, - { url = "https://files.pythonhosted.org/packages/ac/92/5843dd96da7711e72dae489bf91441d91c4dc15f17f34b89b04f2c22aee2/pyobjc_framework_cfnetwork-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:8c5313e146d436de05afae2ab203cfa1966f56d34661939629e2b932efd8da1a", size = 19402, upload-time = "2025-11-14T09:39:47.497Z" }, + { url = "https://files.pythonhosted.org/packages/88/67/e89fd75691ad9f06636f1d785dcb87d9424096dc2cb938565755917fc627/pyobjc_framework_cfnetwork-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:810e87ee2e0a9aa525c56c5f619de6b42c0f022b46247b6b44cb881a8cea86b2", size = 19974, upload-time = "2026-06-19T16:07:12.811Z" }, + { url = "https://files.pythonhosted.org/packages/72/6d/7eba39749600eb43df55445be2947826255469aa07402829c3fb23c6c9c6/pyobjc_framework_cfnetwork-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:045913bd2535281a16be306fe87e0cd419a98273f44abb7e7058d67d71083324", size = 19975, upload-time = "2026-06-19T16:07:14.066Z" }, + { url = "https://files.pythonhosted.org/packages/77/15/227b45b0780c1a0bab0b1e7343a59871d393150661e7e28b9e980c959a71/pyobjc_framework_cfnetwork-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f9be16c691c6f1f58e91551c9e67069abcbf3d5e9a789b1b95b1f84620f0308f", size = 20155, upload-time = "2026-06-19T16:07:15.201Z" }, + { url = "https://files.pythonhosted.org/packages/3c/17/1565bbd61caedeba02f86871a9db0dc3e103e5c0cb2e4264089741e4dc79/pyobjc_framework_cfnetwork-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e8efd441705148fc715c18e57c30c68da001465d2c443decbc56bd4417a8725a", size = 20173, upload-time = "2026-06-19T16:07:16.147Z" }, + { url = "https://files.pythonhosted.org/packages/64/92/1d1fe63cad93c423f85ae41471bc1992902a7c3a90b106b1907fd460061b/pyobjc_framework_cfnetwork-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:d040fd88f3b8e60f7c35096b0901cb6f05c0c5a7f0c90af783fab5547bed3061", size = 20473, upload-time = "2026-06-19T16:07:17.265Z" }, + { url = "https://files.pythonhosted.org/packages/ec/f6/4af038de8529f9d13ed896b25d9923005bfe70d3b29053b515d484f7c639/pyobjc_framework_cfnetwork-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:d9a9399799d5889b0746860c1ebc569b813e3fe836d80d19080901a966094121", size = 20218, upload-time = "2026-06-19T16:07:18.089Z" }, + { url = "https://files.pythonhosted.org/packages/28/8f/642e3270ba88868cec357a00f5641d448159287c1df896b10315328b8ef9/pyobjc_framework_cfnetwork-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:828d104edb558be33667ce3fa9cc37a853839fadcb10bed7017709b0511d8801", size = 20454, upload-time = "2026-06-19T16:07:19.104Z" }, + { url = "https://files.pythonhosted.org/packages/28/21/5a0e30a9fcce01796cd1539d277e912729766723384b509efb2428fbdf30/pyobjc_framework_cfnetwork-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:89725cfeeb24fb277be2944c81f2f6f9dedd4da25c4af26a3d7b4aadeb421734", size = 20218, upload-time = "2026-06-19T16:07:21.604Z" }, + { url = "https://files.pythonhosted.org/packages/45/b6/9edc4c12f9445d43e98d13812495098d9d342e9d9ab6e9b1d9aa57bd3d0b/pyobjc_framework_cfnetwork-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:eedf28c9e2b69607e646b775bf2149835bdafac9133847c561e00e0c87e0e9e9", size = 20459, upload-time = "2026-06-19T16:07:22.742Z" }, ] [[package]] name = "pyobjc-framework-cinematic" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, @@ -1817,33 +1899,35 @@ dependencies = [ { name = "pyobjc-framework-coremedia" }, { name = "pyobjc-framework-metal" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/67/4e/f4cc7f9f7f66df0290c90fe445f1ff5aa514c6634f5203fe049161053716/pyobjc_framework_cinematic-12.1.tar.gz", hash = "sha256:795068c30447548c0e8614e9c432d4b288b13d5614622ef2f9e3246132329b06", size = 21215, upload-time = "2025-11-14T10:10:10.795Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/49/a942906b8753161e58b89e6d986dc24a435a8cbe6ab6ea80162d31b37895/pyobjc_framework_cinematic-12.2.1.tar.gz", hash = "sha256:cd251ded9393ff4a993a3689d4d3ce7ba3926e7f884f9cd333cf9610338ac28b", size = 24948, upload-time = "2026-06-19T16:20:01.374Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/a0/cd85c827ce5535c08d936e5723c16ee49f7ff633f2e9881f4f58bf83e4ce/pyobjc_framework_cinematic-12.1-py2.py3-none-any.whl", hash = "sha256:c003543bb6908379680a93dfd77a44228686b86c118cf3bc930f60241d0cd141", size = 5031, upload-time = "2025-11-14T09:39:49.003Z" }, + { url = "https://files.pythonhosted.org/packages/dd/e4/9a0d9725de7a31c08eb470b6ccd9c5f3628b972a6309b3214406ed9f74cd/pyobjc_framework_cinematic-12.2.1-py2.py3-none-any.whl", hash = "sha256:73720492fc29eee04cbbeae701dd21c5ddd56dc4e19e59b2af520b8f53a129a3", size = 5123, upload-time = "2026-06-19T16:07:23.663Z" }, ] [[package]] name = "pyobjc-framework-classkit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/ef/67815278023b344a79c7e95f748f647245d6f5305136fc80615254ad447c/pyobjc_framework_classkit-12.1.tar.gz", hash = "sha256:8d1e9dd75c3d14938ff533d88b72bca2d34918e4461f418ea323bfb2498473b4", size = 26298, upload-time = "2025-11-14T10:10:13.406Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/33/d2933e1dcf122be5b785220713c80cf07a24d2e21bd429b39d723059f653/pyobjc_framework_classkit-12.2.1.tar.gz", hash = "sha256:2f14c6056486b274e487deabf2ce94ccf17db5df6cd332617592ff2d306d7b5c", size = 28972, upload-time = "2026-06-19T16:20:02.296Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/23/75ce71520f0f1930cea460522a217819094f074ae8e6296166f86f872ed9/pyobjc_framework_classkit-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7fbca9dbd9485ec21c3bc64f0e27ce1037095db71bd16718f883b6f22ab0f9a0", size = 8858, upload-time = "2025-11-14T09:39:50.778Z" }, - { url = "https://files.pythonhosted.org/packages/14/e2/67bd062fbc9761c34b9911ed099ee50ccddc3032779ce420ca40083ee15c/pyobjc_framework_classkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bd90aacc68eff3412204a9040fa81eb18348cbd88ed56d33558349f3e51bff52", size = 8857, upload-time = "2025-11-14T09:39:53.283Z" }, - { url = "https://files.pythonhosted.org/packages/87/5e/cf43c647af872499fc8e80cc6ac6e9ad77d9c77861dc2e62bdd9b01473ce/pyobjc_framework_classkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c027a3cd9be5fee3f605589118b8b278297c384a271f224c1a98b224e0c087e6", size = 8877, upload-time = "2025-11-14T09:39:54.979Z" }, - { url = "https://files.pythonhosted.org/packages/a5/47/f89917b4683a8f61c64d5d30d64ed0a5c1cfd9f0dd9dfb099b3465c73bcf/pyobjc_framework_classkit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0ac959a4e91a40865f12f041c083fa8862672f13e596c983f2b99afc8c67bc4e", size = 8890, upload-time = "2025-11-14T09:39:56.65Z" }, - { url = "https://files.pythonhosted.org/packages/b4/9b/8a0dc753e73001026663fe8556895b23fbf6c238a705bfc86d8ce191eee3/pyobjc_framework_classkit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:61fdac9e3bad384b47725587b77f932dbed71d0ae63b749eddfa390791eed4a2", size = 9043, upload-time = "2025-11-14T09:39:58.684Z" }, - { url = "https://files.pythonhosted.org/packages/2e/0b/7f25a43b0820a220a00c4a334d93c36cfa9e4248764054d6f9901eacbbd4/pyobjc_framework_classkit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:5d0a5cd026c51a22d13eb75404f8317089aabb3faef723aeafc4ca9a0c17e66e", size = 8952, upload-time = "2025-11-14T09:40:00.405Z" }, - { url = "https://files.pythonhosted.org/packages/1a/be/d33b868da5c646e8251521f3e523510eb85b34f329bb9267506d306acbd5/pyobjc_framework_classkit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:c95cd6a4f598e877197a93cc202d40d0d830bf09be5a2b15942e5a1b03e29cd4", size = 9115, upload-time = "2025-11-14T09:40:02.088Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ec/90cd2b4854892a77dfe505389c7c7895c5eb4b40d73405655dad0f68448a/pyobjc_framework_classkit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9c1006c28ee2be95e68beb9afba1505690ff7a01ffee586e08e4e8d53061d6fa", size = 8930, upload-time = "2026-06-19T16:07:24.724Z" }, + { url = "https://files.pythonhosted.org/packages/9f/f9/1ad14ee12321a13eed23edd20e75af14ef62a3676459dc9dc8f8e3586ba2/pyobjc_framework_classkit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:63b2669246b0545c0c8357cc73294fac902bfc8a69617b3012395dac974c0ec1", size = 8934, upload-time = "2026-06-19T16:07:25.787Z" }, + { url = "https://files.pythonhosted.org/packages/e6/62/2acd49bb925b8785a2051825bae02d7db2e92d7fa1573585ccdeb131e76c/pyobjc_framework_classkit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f39c78209ad615bb1ef54a3b78b56438aa734bb01143d954ab29d9cee59ec34d", size = 8950, upload-time = "2026-06-19T16:07:26.848Z" }, + { url = "https://files.pythonhosted.org/packages/c0/2a/b7ff453762489443997a3d39d5b6e6865c736ed57db7be67cfab982106fc/pyobjc_framework_classkit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b96d7b6416e954be53ac4fc1908bad22c6609d116dd50ef1f25f057bf8274625", size = 8964, upload-time = "2026-06-19T16:07:27.966Z" }, + { url = "https://files.pythonhosted.org/packages/76/1f/83661aec2aa68f7c571370230a0dcca12f2c1b6bf615ab96ffbde12c2ea6/pyobjc_framework_classkit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:d689fb7bcf0120963b0d4518399c2440a4bf95378e9841289835846008bebac2", size = 9111, upload-time = "2026-06-19T16:07:28.953Z" }, + { url = "https://files.pythonhosted.org/packages/63/9f/b5ad19a4570757500433263c135408160c500301bc9aa09fe5a2c5598659/pyobjc_framework_classkit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ad7e412ab76fc061b0ac40d98bee454cb4e211ca4ac8d0615a42d2e585ff927e", size = 9029, upload-time = "2026-06-19T16:07:29.838Z" }, + { url = "https://files.pythonhosted.org/packages/f7/3a/0919a7c440882763f333534c84cd6ff2c65e980e7e3039d55f87573d2e0c/pyobjc_framework_classkit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0cf34015821023ea317bad59087c44bb63c3e8be14177904f7204bd1e53cf8a1", size = 9183, upload-time = "2026-06-19T16:07:30.627Z" }, + { url = "https://files.pythonhosted.org/packages/e7/34/8f9e45ebab8eacdcf2d69e355719b20f7b36e20fcd02948f59bf2857138b/pyobjc_framework_classkit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:5b65626323c4e8fd51b85b0474a4a2c10fc4eca097857406837ca1570310fb34", size = 9030, upload-time = "2026-06-19T16:07:31.574Z" }, + { url = "https://files.pythonhosted.org/packages/c8/99/9cc70a07b4f44c484d66fc800198043dffb65924b5d50ba2eac865e3c2c9/pyobjc_framework_classkit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:6a71f9152dc08502fb299d664afde0936682e60b4023a887fcec6eb485dbe91d", size = 9180, upload-time = "2026-06-19T16:07:33.292Z" }, ] [[package]] name = "pyobjc-framework-cloudkit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, @@ -1852,1092 +1936,1166 @@ dependencies = [ { name = "pyobjc-framework-coredata" }, { name = "pyobjc-framework-corelocation" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2d/09/762ee4f3ae8568b8e0e5392c705bc4aa1929aa454646c124ca470f1bf9fc/pyobjc_framework_cloudkit-12.1.tar.gz", hash = "sha256:1dddd38e60863f88adb3d1d37d3b4ccb9cbff48c4ef02ab50e36fa40c2379d2f", size = 53730, upload-time = "2025-11-14T10:10:17.831Z" } +sdist = { url = "https://files.pythonhosted.org/packages/89/3a/ff99bc394e051086f7d63ade09460de85e2540cf823fb1cd759225f2c744/pyobjc_framework_cloudkit-12.2.1.tar.gz", hash = "sha256:7d3810347fe8de6171d8a4377916750b4ba3bfa874b5f8e5bd0ca6e62d2c4f43", size = 71962, upload-time = "2026-06-19T16:20:03.084Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/35/71/cbef7179bf1a594558ea27f1e5ad18f5c17ef71a8a24192aae16127bc849/pyobjc_framework_cloudkit-12.1-py2.py3-none-any.whl", hash = "sha256:875e37bf1a2ce3d05c2492692650104f2d908b56b71a0aedf6620bc517c6c9ca", size = 11090, upload-time = "2025-11-14T09:40:04.207Z" }, + { url = "https://files.pythonhosted.org/packages/14/3e/093157f31d29e06bad916d12f6903bcae9c7708ff89fef5bb612cf305f44/pyobjc_framework_cloudkit-12.2.1-py2.py3-none-any.whl", hash = "sha256:4badd0d3b9d78fab900415a2b0319579c5be3a30fa99e631a9065f3768076e35", size = 11437, upload-time = "2026-06-19T16:07:34.479Z" }, ] [[package]] name = "pyobjc-framework-cocoa" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/02/a3/16ca9a15e77c061a9250afbae2eae26f2e1579eb8ca9462ae2d2c71e1169/pyobjc_framework_cocoa-12.1.tar.gz", hash = "sha256:5556c87db95711b985d5efdaaf01c917ddd41d148b1e52a0c66b1a2e2c5c1640", size = 2772191, upload-time = "2025-11-14T10:13:02.069Z" } +sdist = { url = "https://files.pythonhosted.org/packages/51/34/fbe38a204643aa4e1b91391cdce07a34da565a69171ebcad08de7438a556/pyobjc_framework_cocoa-12.2.1.tar.gz", hash = "sha256:b94b37fe5730e5ae1fb0052912cd174e6ec329b0bfba4a012ae5db1014b5864b", size = 3125751, upload-time = "2026-06-19T16:20:05.159Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/aa/2b2d7ec3ac4b112a605e9bd5c5e5e4fd31d60a8a4b610ab19cc4838aa92a/pyobjc_framework_cocoa-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9b880d3bdcd102809d704b6d8e14e31611443aa892d9f60e8491e457182fdd48", size = 383825, upload-time = "2025-11-14T09:40:28.354Z" }, - { url = "https://files.pythonhosted.org/packages/3f/07/5760735c0fffc65107e648eaf7e0991f46da442ac4493501be5380e6d9d4/pyobjc_framework_cocoa-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f52228bcf38da64b77328787967d464e28b981492b33a7675585141e1b0a01e6", size = 383812, upload-time = "2025-11-14T09:40:53.169Z" }, - { url = "https://files.pythonhosted.org/packages/95/bf/ee4f27ec3920d5c6fc63c63e797c5b2cc4e20fe439217085d01ea5b63856/pyobjc_framework_cocoa-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:547c182837214b7ec4796dac5aee3aa25abc665757b75d7f44f83c994bcb0858", size = 384590, upload-time = "2025-11-14T09:41:17.336Z" }, - { url = "https://files.pythonhosted.org/packages/ad/31/0c2e734165abb46215797bd830c4bdcb780b699854b15f2b6240515edcc6/pyobjc_framework_cocoa-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5a3dcd491cacc2f5a197142b3c556d8aafa3963011110102a093349017705118", size = 384689, upload-time = "2025-11-14T09:41:41.478Z" }, - { url = "https://files.pythonhosted.org/packages/23/3b/b9f61be7b9f9b4e0a6db18b3c35c4c4d589f2d04e963e2174d38c6555a92/pyobjc_framework_cocoa-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:914b74328c22d8ca261d78c23ef2befc29776e0b85555973927b338c5734ca44", size = 388843, upload-time = "2025-11-14T09:42:05.719Z" }, - { url = "https://files.pythonhosted.org/packages/59/bb/f777cc9e775fc7dae77b569254570fe46eb842516b3e4fe383ab49eab598/pyobjc_framework_cocoa-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:03342a60fc0015bcdf9b93ac0b4f457d3938e9ef761b28df9564c91a14f0129a", size = 384932, upload-time = "2025-11-14T09:42:29.771Z" }, - { url = "https://files.pythonhosted.org/packages/58/27/b457b7b37089cad692c8aada90119162dfb4c4a16f513b79a8b2b022b33b/pyobjc_framework_cocoa-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6ba1dc1bfa4da42d04e93d2363491275fb2e2be5c20790e561c8a9e09b8cf2cc", size = 388970, upload-time = "2025-11-14T09:42:53.964Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c5/f6a5458cc5a598baa7a79ffe86248560c631f5a86f4a3c678fb6a815e78b/pyobjc_framework_cocoa-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05443c1494532779cccff83e9792fdd6f0a4800cac56620132bf28e0bf966e9d", size = 387303, upload-time = "2026-06-19T16:07:35.831Z" }, + { url = "https://files.pythonhosted.org/packages/f4/d6/dc66ea8519a0475efbccf73f82cc28066339bb300a27f5e1bf91ab1d7002/pyobjc_framework_cocoa-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dc6da84f4fc62cc25463bbb85e77a57b8d5ac6caf9a60702daf2edb601332f15", size = 387298, upload-time = "2026-06-19T16:07:37.412Z" }, + { url = "https://files.pythonhosted.org/packages/f7/cf/1b3b32b2f28f66cc053c3438ef4e6df36a1591945bf05e7399da18d74553/pyobjc_framework_cocoa-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:28b9b8bab1c36efb94744786918752d0c1842f5fbb67e7d5ca97b5f736512080", size = 388113, upload-time = "2026-06-19T16:07:38.9Z" }, + { url = "https://files.pythonhosted.org/packages/cc/46/68e8e4d926a2f70fed0437047bc3f9fe08af8fe620d94d80656ebc3cfa9b/pyobjc_framework_cocoa-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3b74a78fa7803e547b32e5e8ec1b49987b52fe318383e793bc6cd49b80efbd9f", size = 388183, upload-time = "2026-06-19T16:07:40.483Z" }, + { url = "https://files.pythonhosted.org/packages/2e/f3/dfc9af4c9eb2e5389c860ad5ef252be9fe456db09f39d537555dc5057aa1/pyobjc_framework_cocoa-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:dc2eaca2f13c7bcd8e41e51a372e47825dea9dd3126108760eed7ba883d2945c", size = 392275, upload-time = "2026-06-19T16:07:42.078Z" }, + { url = "https://files.pythonhosted.org/packages/ec/c8/b90baa8f3592eded79b4be98fb59d2b8dc16b62361e34292bd95806ebd9f/pyobjc_framework_cocoa-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b386c324d64ae565c1f6b7dfb77be68f640a1c7c23caa6966ab661131f519561", size = 388357, upload-time = "2026-06-19T16:07:43.364Z" }, + { url = "https://files.pythonhosted.org/packages/98/d8/64a94651b9294702d55e748d94de30e25bc59d0784526be7643f4467eccd/pyobjc_framework_cocoa-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a6c584e2af0813cb2f6103b184e632665a26f58c1bd5b08ffd6e95a19c617f7b", size = 392404, upload-time = "2026-06-19T16:07:44.955Z" }, + { url = "https://files.pythonhosted.org/packages/5c/cc/26e8a7bf1f5e8caa38b7f80d486296f9fd3c97e71ad7e5444ef22e802758/pyobjc_framework_cocoa-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:b6023657b8d6cc049a21bd6b4752425f2f53c42f9f0b02d64c7608cc484bf103", size = 388589, upload-time = "2026-06-19T16:07:46.276Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f3/eedf743a303ea742b8e082afe3613fb4d6618bc1a48cf2568b004ce906f7/pyobjc_framework_cocoa-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:c685ccd8e266a07cf912a2c5a13b1f2eff2a868a1aff163b4801b4687bd425e1", size = 392691, upload-time = "2026-06-19T16:07:47.477Z" }, ] [[package]] name = "pyobjc-framework-collaboration" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/64/21/77fe64b39eae98412de1a0d33e9c735aa9949d53fff6b2d81403572b410b/pyobjc_framework_collaboration-12.1.tar.gz", hash = "sha256:2afa264d3233fc0a03a56789c6fefe655ffd81a2da4ba1dc79ea0c45931ad47b", size = 14299, upload-time = "2025-11-14T10:13:04.631Z" } +sdist = { url = "https://files.pythonhosted.org/packages/15/b0/9cd5d547543a87ab199a5dc6e4c489b01b825994b52b0d17d89abd7219c6/pyobjc_framework_collaboration-12.2.1.tar.gz", hash = "sha256:d293b191823cf8c5cc17e74279e640312961a9226da97e6258580d1b6085e1e4", size = 15065, upload-time = "2026-06-19T16:20:06.502Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/66/1507de01f1e2b309f8e11553a52769e4e2e9939ed770b5b560ef5bc27bc1/pyobjc_framework_collaboration-12.1-py2.py3-none-any.whl", hash = "sha256:182d6e6080833b97f9bef61738ae7bacb509714538f0d7281e5f0814c804b315", size = 4907, upload-time = "2025-11-14T09:42:55.781Z" }, + { url = "https://files.pythonhosted.org/packages/65/bd/7803dd4c6c472a610cd83f7ad7136b61b8bb3dab803a35a6ca4ed0561688/pyobjc_framework_collaboration-12.2.1-py2.py3-none-any.whl", hash = "sha256:03404e679dc77314ea94029c8e54dae25424194b5f39751fb6fa0eca4af99fdf", size = 4880, upload-time = "2026-06-19T16:07:48.59Z" }, ] [[package]] name = "pyobjc-framework-colorsync" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c0/b4/706e4cc9db25b400201fc90f3edfaa1ab2d51b400b19437b043a68532078/pyobjc_framework_colorsync-12.1.tar.gz", hash = "sha256:d69dab7df01245a8c1bd536b9231c97993a5d1a2765d77692ce40ebbe6c1b8e9", size = 25269, upload-time = "2025-11-14T10:13:07.522Z" } +sdist = { url = "https://files.pythonhosted.org/packages/92/dd/8f292cc041fb2a836a3ee7432c3f64347a15b5b4d64278277e4954ba28e1/pyobjc_framework_colorsync-12.2.1.tar.gz", hash = "sha256:1e682586a319f49d3ee3c93e54a527a1ff93de06f653e77c0c4ff4d9671469f9", size = 26902, upload-time = "2026-06-19T16:20:07.213Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/e1/82e45c712f43905ee1e6d585180764e8fa6b6f1377feb872f9f03c8c1fb8/pyobjc_framework_colorsync-12.1-py2.py3-none-any.whl", hash = "sha256:41e08d5b9a7af4b380c9adab24c7ff59dfd607b3073ae466693a3e791d8ffdc9", size = 6020, upload-time = "2025-11-14T09:42:57.504Z" }, + { url = "https://files.pythonhosted.org/packages/46/99/9c1d4d010cf51b17aba298664ff2dac7818a3d568202d36b0587fd07fd5a/pyobjc_framework_colorsync-12.2.1-py2.py3-none-any.whl", hash = "sha256:0bafd1bfdf77687910e4a6f01c640ec47def060fb88e3abaddf4cd7e7d469e87", size = 6028, upload-time = "2026-06-19T16:07:49.817Z" }, ] [[package]] name = "pyobjc-framework-compositorservices" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-metal" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/54/c5/0ba31d7af7e464b7f7ece8c2bd09112bdb0b7260848402e79ba6aacc622c/pyobjc_framework_compositorservices-12.1.tar.gz", hash = "sha256:028e357bbee7fbd3723339a321bbe14e6da5a772708a661a13eea5f17c89e4ab", size = 23292, upload-time = "2025-11-14T10:13:10.392Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/f3/2136460770ff0b32e64282eed79c37a07c1dfa9df761f2679462391d4ada/pyobjc_framework_compositorservices-12.2.1.tar.gz", hash = "sha256:683b765077ce3bf9b680bfce23124ace85208738ff3c14768e1ca80fd78c1565", size = 24941, upload-time = "2026-06-19T16:20:08.032Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/34/5a2de8d531dbb88023898e0b5d2ce8edee14751af6c70e6103f6aa31a669/pyobjc_framework_compositorservices-12.1-py2.py3-none-any.whl", hash = "sha256:9ef22d4eacd492e13099b9b8936db892cdbbef1e3d23c3484e0ed749f83c4984", size = 5910, upload-time = "2025-11-14T09:42:59.154Z" }, + { url = "https://files.pythonhosted.org/packages/af/0a/2e3177c354db49ffe137410431baed816fca9101bee9da808a5e5cbab9e9/pyobjc_framework_compositorservices-12.2.1-py2.py3-none-any.whl", hash = "sha256:1c1cc5ca97e060afe2125b338651d5143f8f6b40e17748116dedc334449773d0", size = 5995, upload-time = "2026-06-19T16:07:50.803Z" }, ] [[package]] name = "pyobjc-framework-contacts" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4b/a0/ce0542d211d4ea02f5cbcf72ee0a16b66b0d477a4ba5c32e00117703f2f0/pyobjc_framework_contacts-12.1.tar.gz", hash = "sha256:89bca3c5cf31404b714abaa1673577e1aaad6f2ef49d4141c6dbcc0643a789ad", size = 42378, upload-time = "2025-11-14T10:13:14.203Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/e2/a9c33480e4f6de3f20e2d2668ea05520061b8eae31e8461b09940c01f2dc/pyobjc_framework_contacts-12.2.1.tar.gz", hash = "sha256:b009f1e85d672e659c30cefbba02a1825b4ca2b604e65826445fb8620159725e", size = 48701, upload-time = "2026-06-19T16:20:08.856Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/49/a95ea5ab95170b1e497275928e275d871ab698c4d65611fcc2a685b6bf4d/pyobjc_framework_contacts-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b570807aaead01b51c25cb425526f4ac3527eec25fa8672fd450a1b558c037", size = 12086, upload-time = "2025-11-14T09:43:01.115Z" }, - { url = "https://files.pythonhosted.org/packages/94/f5/5d2c03cf5219f2e35f3f908afa11868e9096aff33b29b41d63f2de3595f2/pyobjc_framework_contacts-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8ab86070895a005239256d207e18209b1a79d35335b6604db160e8375a7165e6", size = 12086, upload-time = "2025-11-14T09:43:03.225Z" }, - { url = "https://files.pythonhosted.org/packages/32/c8/2c4638c0d06447886a34070eebb9ba57407d4dd5f0fcb7ab642568272b88/pyobjc_framework_contacts-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2e5ce33b686eb9c0a39351938a756442ea8dea88f6ae2f16bff5494a8569c687", size = 12165, upload-time = "2025-11-14T09:43:05.119Z" }, - { url = "https://files.pythonhosted.org/packages/25/43/e322dd14c77eada5a4f327f5bc094061c90efabc774b30396d1155a69c44/pyobjc_framework_contacts-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:62d985098aa86a86d23bff408aac47389680da4edc61f6acf10b2197efcbd0e0", size = 12177, upload-time = "2025-11-14T09:43:06.957Z" }, - { url = "https://files.pythonhosted.org/packages/0a/37/53eba15f2e31950056c63b78732b73379ddbf946c5e6681f3b2773dcf282/pyobjc_framework_contacts-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ab1d78f363dfede16bd5d951327332564bae86f68834d1e657dd18fe4dc12082", size = 12346, upload-time = "2025-11-14T09:43:08.865Z" }, - { url = "https://files.pythonhosted.org/packages/7e/8b/3200f69b77ea85fe69caa1afea444387b5e41bf44ceff11e772954d8a0d5/pyobjc_framework_contacts-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:65576c359eb31c5a5ef95e0c6714686a94bb154a508d791885ff7c33dbc8afa3", size = 12259, upload-time = "2025-11-14T09:43:10.705Z" }, - { url = "https://files.pythonhosted.org/packages/a2/81/0da71a88273aa73841cd3669431c30be627600162ec89cd170759dbffeaf/pyobjc_framework_contacts-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1fac7feca7428047abf3f094fab678c4d0413296f34c30085119850509bc2905", size = 12410, upload-time = "2025-11-14T09:43:12.667Z" }, + { url = "https://files.pythonhosted.org/packages/11/f9/72d436a5cd6aee730dbbdd234b33645d85e64d440e39ec0a329cad728137/pyobjc_framework_contacts-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a95f1cc61c2a36b310230976f49d8ec8cf7cf7a8eb81b038f25410bea99a7c50", size = 12090, upload-time = "2026-06-19T16:07:51.866Z" }, + { url = "https://files.pythonhosted.org/packages/65/b9/6793fe551d7c2ddf202b6a787debf6d3da251ef8f13f4fd49e83ba92d512/pyobjc_framework_contacts-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:01fea11ba777721add31ccff3497334eb0fde7ba1c69cf2c845a85e1515d5c18", size = 12089, upload-time = "2026-06-19T16:07:52.986Z" }, + { url = "https://files.pythonhosted.org/packages/70/c1/c4435f4dfb8ef0038a9556d2472cd259322f73627c21f94f3e233bd73587/pyobjc_framework_contacts-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9e31c635d1a8ed48d0e3332120d0eafa6a5d40b30fe4832aaa700fa03fe14c8d", size = 12172, upload-time = "2026-06-19T16:07:53.999Z" }, + { url = "https://files.pythonhosted.org/packages/50/0a/9a518f0ebecd5e27493230de7f5dc746b7abdf0ae3f192a14abc7c23cd15/pyobjc_framework_contacts-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:93facd1772971b6d88243fa28781498ab5f479c0fe286f8c03870f93c515b517", size = 12186, upload-time = "2026-06-19T16:07:54.774Z" }, + { url = "https://files.pythonhosted.org/packages/73/74/57cbdbe46884d890e8321a9ea3cb85a163def2e0100bc38e14b41cdcde35/pyobjc_framework_contacts-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:d0f73ff7f92986b5920155e9cb16bc74cf049e166e6bc2a7df1621135b4e6d79", size = 12351, upload-time = "2026-06-19T16:07:55.543Z" }, + { url = "https://files.pythonhosted.org/packages/7a/a6/3fc0bc4c50fdf5005501c276613d39c09a35e236f8d5c8f344ae914895eb/pyobjc_framework_contacts-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:31d0906bce2e5c348a1453ac390d9e255ce547ca763e93875ea3582deba69070", size = 12259, upload-time = "2026-06-19T16:07:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/d2/4b/31ca64f27d6d9fd1ee2efed4bc979abefc21c743d6b7eab1257c2de2d359/pyobjc_framework_contacts-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:85ec30abb7b27655c715e3b2e01ed6229e24b7f14f235e6c04fb77afedad3983", size = 12415, upload-time = "2026-06-19T16:07:57.705Z" }, + { url = "https://files.pythonhosted.org/packages/cd/51/9b4ec4d4c5a316a2fd3fe6d70c10080e82a4af95e8ffc229f1d04285b60e/pyobjc_framework_contacts-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:d21bce70f97cb4b5da7cfb35da64897ce69f96ffbecacfe64364ce220b74d075", size = 12257, upload-time = "2026-06-19T16:07:58.721Z" }, + { url = "https://files.pythonhosted.org/packages/d5/c7/9215dab638d28aaa2af87b66324b954f53dd78060e0387f9300710332e4e/pyobjc_framework_contacts-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:536a46a5a55040142a2cba108ac48045f6da266a042f2fa0ec2bf4b7f8bc1e34", size = 12413, upload-time = "2026-06-19T16:07:59.544Z" }, ] [[package]] name = "pyobjc-framework-contactsui" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-contacts" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/0c/7bb7f898456a81d88d06a1084a42e374519d2e40a668a872b69b11f8c1f9/pyobjc_framework_contactsui-12.1.tar.gz", hash = "sha256:aaeca7c9e0c9c4e224d73636f9a558f9368c2c7422155a41fd4d7a13613a77c1", size = 18769, upload-time = "2025-11-14T10:13:16.301Z" } +sdist = { url = "https://files.pythonhosted.org/packages/39/22/a01e677c4e44c3e03850c7765b4cc20e3b8fbad53ee92300eddd77dc8627/pyobjc_framework_contactsui-12.2.1.tar.gz", hash = "sha256:6ddfaf3d3f159bc4bb8ccd65414e94b4b6539a5588e84efb01838b19e3b1ba0b", size = 19329, upload-time = "2026-06-19T16:20:09.679Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/6d/4d5070c4ce85e0aaf95aed8a1d482fafd031ebe30f70f7788c2a7737d661/pyobjc_framework_contactsui-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9aedac00f41c1fae02befc70263f22f74518cc392fe19b66cc325d8f95e78b2c", size = 7874, upload-time = "2025-11-14T09:43:14.698Z" }, - { url = "https://files.pythonhosted.org/packages/04/e3/8d330640bf0337289834334c54c599fec2dad38a8a3b736d40bcb5d8db6e/pyobjc_framework_contactsui-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:10e7ce3b105795919605be89ebeecffd656e82dbf1bafa5db6d51d6def2265ee", size = 7871, upload-time = "2025-11-14T09:43:16.973Z" }, - { url = "https://files.pythonhosted.org/packages/f3/ab/319aa52dfe6f836f4dc542282c2c13996222d4f5c9ea7ff8f391b12dac83/pyobjc_framework_contactsui-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:057f40d2f6eb1b169a300675ec75cc7a747cddcbcee8ece133e652a7086c5ab5", size = 7888, upload-time = "2025-11-14T09:43:18.502Z" }, - { url = "https://files.pythonhosted.org/packages/fd/9c/c9a71681e2ad8695222dbdbbe740af22cc354e9130df6108f9bfe90a4100/pyobjc_framework_contactsui-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2ee2eccb633bc772ecb49dba7199546154efc2db5727992229cdf84b3f6ac84f", size = 7907, upload-time = "2025-11-14T09:43:20.409Z" }, - { url = "https://files.pythonhosted.org/packages/a0/54/abdb4c5f53323edc1e02bd0916133c4e6b82ad268eded668ef7b40a1e6c9/pyobjc_framework_contactsui-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c9d64bbc4cfae0f082627b57f7e29e71b924af970f344b106b17fb68e13f7da0", size = 8056, upload-time = "2025-11-14T09:43:22Z" }, - { url = "https://files.pythonhosted.org/packages/4b/d4/fe84efe4301a4367a2ab427214f20e13bfb3a64dc5e29649acc15022c0ad/pyobjc_framework_contactsui-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:eb06b422ce8d422dce2c9af49a2bd093f78761e5aa3f1c866582a4c60cf31f79", size = 7961, upload-time = "2025-11-14T09:43:23.819Z" }, - { url = "https://files.pythonhosted.org/packages/39/c1/3ed9be7e479b13e4fd483c704c4833008ff8e63ee3acd66922f2f7a60292/pyobjc_framework_contactsui-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1bbb9bee9535505398771886ac43399400ffc9a84836e845e6d9708ac88e2d5d", size = 8120, upload-time = "2025-11-14T09:43:25.362Z" }, + { url = "https://files.pythonhosted.org/packages/b8/bc/17b75180892b453c2858abba7395ab6e94fd8b9224c8439bf8c5e5331284/pyobjc_framework_contactsui-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4c7901389587d903e2e1dfcf9ab5cc7cb189457cc90f423793ac418599003ae5", size = 7894, upload-time = "2026-06-19T16:08:00.36Z" }, + { url = "https://files.pythonhosted.org/packages/d8/53/deaff064fd9e4ec7996e9e218ebc9ed60f9ba00e18b30e85b5cbcc1eeb65/pyobjc_framework_contactsui-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:92fcf91334dbad85da45d66b12e8ff9105e3ba6a204b07342463318f31758fb9", size = 7890, upload-time = "2026-06-19T16:08:01.353Z" }, + { url = "https://files.pythonhosted.org/packages/73/1a/bbcf4c5a21ff5aeb93cec6f629fd9e4fc7806fa03d11711346449b0147ea/pyobjc_framework_contactsui-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1a447f3143759dc545a0a8caf23b3f6b5d4a27d50451046093a01f478a81bd8b", size = 7913, upload-time = "2026-06-19T16:08:02.133Z" }, + { url = "https://files.pythonhosted.org/packages/05/16/617d8075fe65d3b27b5140da604531bb343f55eaa676ab88208d9c4e2ffa/pyobjc_framework_contactsui-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1cbdefbc77830205ca9e63360cad08562d7b97789a792e98ab2108c018f10768", size = 7926, upload-time = "2026-06-19T16:08:02.929Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2d/15210bf2f5b5b57b436231c7789239cdbfeca4ad82a0b2f41d1a55256a81/pyobjc_framework_contactsui-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:78452b5171a32d206a13d46ab7fdae60d87dd49802f531ced8646e6c71c25c57", size = 8073, upload-time = "2026-06-19T16:08:03.778Z" }, + { url = "https://files.pythonhosted.org/packages/ab/98/f0be6434e55627f843c253791d3fbeaae4badc03c07a2ad1cb06b4e698db/pyobjc_framework_contactsui-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:61d2a2df53a8bbdeadc552684a556c6ff7d4537c73333aa5629274eda2b99508", size = 7983, upload-time = "2026-06-19T16:08:04.666Z" }, + { url = "https://files.pythonhosted.org/packages/fb/eb/647e6261abb93b15515627f618f88c03b412c59bd1b1ed608906743567ab/pyobjc_framework_contactsui-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:81684620b41bedcef5423ea84337acd2e874c7ab7c2328b2f4f8d5df70daaffd", size = 8135, upload-time = "2026-06-19T16:08:05.586Z" }, + { url = "https://files.pythonhosted.org/packages/a4/e4/5e11e387a598fe670e0f0db1eb73d84f096bb2c5ce12f576477651649b36/pyobjc_framework_contactsui-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:3ecfb26a90b0b99231f433775ecda78bc461833e80212f8aa7f4f5d33dd1fe40", size = 7983, upload-time = "2026-06-19T16:08:06.916Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d9/fd4086be33ea326205847d2991e94e1e6b236183d69bd66cfacc3957d603/pyobjc_framework_contactsui-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:26acfebc03d264ff938da39ef33dbd1e09ab2c422245d146f14aafca78189894", size = 8127, upload-time = "2026-06-19T16:08:07.735Z" }, ] [[package]] name = "pyobjc-framework-coreaudio" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/84/d1/0b884c5564ab952ff5daa949128c64815300556019c1bba0cf2ca752a1a0/pyobjc_framework_coreaudio-12.1.tar.gz", hash = "sha256:a9e72925fcc1795430496ce0bffd4ddaa92c22460a10308a7283ade830089fe1", size = 75077, upload-time = "2025-11-14T10:13:22.345Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ad/df/f1d402bb7b437374f942bb19410a955a74291867c77a920d9c13887d9e48/pyobjc_framework_coreaudio-12.2.1.tar.gz", hash = "sha256:7dfbf1851523aed453af43a628e057d8950d6e020574aa497a2e4f559b6383c8", size = 78690, upload-time = "2026-06-19T16:20:10.586Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/15/4a635daa2c08133da5556d4fc7aee59de718031b79bb5cb24480e571f734/pyobjc_framework_coreaudio-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d676c85bb9dc51217d94adc3b5b60e7e5b59a81167446f06821b2687d92641d3", size = 35330, upload-time = "2025-11-14T09:43:29.016Z" }, - { url = "https://files.pythonhosted.org/packages/9e/25/491ff549fd9a40be4416793d335bff1911d3d1d1e1635e3b0defbd2cf585/pyobjc_framework_coreaudio-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a452de6b509fa4a20160c0410b72330ac871696cd80237883955a5b3a4de8f2a", size = 35327, upload-time = "2025-11-14T09:43:32.523Z" }, - { url = "https://files.pythonhosted.org/packages/a9/48/05b5192122e23140cf583eac99ccc5bf615591d6ff76483ba986c38ee750/pyobjc_framework_coreaudio-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a5ad6309779663f846ab36fe6c49647e470b7e08473c3e48b4f004017bdb68a4", size = 36908, upload-time = "2025-11-14T09:43:36.108Z" }, - { url = "https://files.pythonhosted.org/packages/3d/ce/45808618fefc760e2948c363e0a3402ff77690c8934609cd07b19bc5b15f/pyobjc_framework_coreaudio-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3d8ef424850c8ae2146f963afaec6c4f5bf0c2e412871e68fb6ecfb209b8376f", size = 36935, upload-time = "2025-11-14T09:43:39.414Z" }, - { url = "https://files.pythonhosted.org/packages/bf/f6/0d74d9464bfb4f39451abf745174ec0c4d5c5ebf1c2fcb7556263ae3f75a/pyobjc_framework_coreaudio-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6552624df39dbc68ff9328f244ba56f59234ecbde8455db1e617a71bc4f3dd3a", size = 38390, upload-time = "2025-11-14T09:43:43.194Z" }, - { url = "https://files.pythonhosted.org/packages/cf/f2/c5ca32d01c9d892bf189cfe9b17deaf996db3b4013f8a8ba9b0d22730d70/pyobjc_framework_coreaudio-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:78ea67483a5deb21625c189328152008d278fe1da4304da9fcc1babd12627038", size = 37012, upload-time = "2025-11-14T09:43:46.54Z" }, - { url = "https://files.pythonhosted.org/packages/00/be/c3d660cef1ef874f42057a74931a7a05f581f6a647f5209bef96b372db86/pyobjc_framework_coreaudio-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:8d81b0d0296ab4571a4ff302e5cdb52386e486eb8749e99b95b9141438558ca2", size = 38485, upload-time = "2025-11-14T09:43:49.883Z" }, + { url = "https://files.pythonhosted.org/packages/d8/ee/3c577d163ce3013e2800d28b452e358be23e0f45ac44e5b1f7c578b3364f/pyobjc_framework_coreaudio-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5081583ecc89aff5e1d6655677822f67033795dcbd3db6d8b2e0fa53a3d9085d", size = 35158, upload-time = "2026-06-19T16:08:09.046Z" }, + { url = "https://files.pythonhosted.org/packages/56/1f/853c498e18db8b6d9eb0cb2261bf8cda3948f2c925d1c820baaf96fdb54d/pyobjc_framework_coreaudio-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:214ce41d1ac3743377f607326d3ff70494dd6f75b2c575fe9c99d3890c545dee", size = 35149, upload-time = "2026-06-19T16:08:10.278Z" }, + { url = "https://files.pythonhosted.org/packages/ea/c1/1fc7a344ac15646fdf78c91af81028113c87375036e505082cf1f90bd657/pyobjc_framework_coreaudio-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:41a0d8c3b3957f35f17370071aeef94e176d07c9e2130d0aeeb4b260117acd52", size = 35415, upload-time = "2026-06-19T16:08:11.329Z" }, + { url = "https://files.pythonhosted.org/packages/39/e4/71b2e3bd03f0404c89b432273d272dc5427185fd9ed828036730bfc9d057/pyobjc_framework_coreaudio-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6fb46bc090edcaefeb70be2c179071e7a758e3375aebd3338b11773e371c3dce", size = 35445, upload-time = "2026-06-19T16:08:12.455Z" }, + { url = "https://files.pythonhosted.org/packages/67/5b/07dc0bf9f79d3b76effba3f0a754be6fda0947848939236820c8e70ac896/pyobjc_framework_coreaudio-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:d05e5fd58c1e5f48e7767b1f6182cf36ee9bff38568544d51f468b25ad90e334", size = 38205, upload-time = "2026-06-19T16:08:13.381Z" }, + { url = "https://files.pythonhosted.org/packages/ad/a4/6495dd34b1ed7a0e8f6d672577da62a92b166a0bc2ded8e55e552cbe5ad2/pyobjc_framework_coreaudio-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:efb79709371f29139f44adc36bd6c32d5a3a0ff3ffd0c5f3f372275cef893b18", size = 36697, upload-time = "2026-06-19T16:08:14.407Z" }, + { url = "https://files.pythonhosted.org/packages/cd/9e/9f0cba5b973a5c8041edb75cf0870f40324437721428b1baceb437553e3f/pyobjc_framework_coreaudio-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9c06b9d7ee3b2a76dd171de519f571d09bdebd85f40b5960d16f711395513cdd", size = 38300, upload-time = "2026-06-19T16:08:15.297Z" }, + { url = "https://files.pythonhosted.org/packages/a1/13/e8df283cdd73dec1b97033be04a89a05e17714c8e2229f2a2e5a44f377c7/pyobjc_framework_coreaudio-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:51be22e4fac73ba4b26ee34c457ac94f56218e4e9c1494f8f4e7dfe9dbb6a15f", size = 36723, upload-time = "2026-06-19T16:08:16.247Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b2/c76ddff169b45d7239468f44bf4af3ff431c014fbf0aa5dbbfcc099672f8/pyobjc_framework_coreaudio-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:18717e6e106556fd4ffd0bdf6a6dae796fc9100c6b373e664437604dd0b9e94d", size = 38333, upload-time = "2026-06-19T16:08:17.264Z" }, ] [[package]] name = "pyobjc-framework-coreaudiokit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-coreaudio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/41/1c/5c7e39b9361d4eec99b9115b593edd9825388acd594cb3b4519f8f1ac12c/pyobjc_framework_coreaudiokit-12.1.tar.gz", hash = "sha256:b83624f8de3068ab2ca279f786be0804da5cf904ff9979d96007b69ef4869e1e", size = 20137, upload-time = "2025-11-14T10:13:24.611Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/82/622a58a3aff47cfdad8cc3bf1c6a3a0c4d73d1cc3c71d050ac1bc0a62c6a/pyobjc_framework_coreaudiokit-12.2.1.tar.gz", hash = "sha256:61a5b796f8296ca5cb4779ec19391ad3a37f35c0c689a401d6e8d41cbe936f08", size = 20941, upload-time = "2026-06-19T16:20:11.407Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/fb/3ba9f67d04014e0d367cddd920aeaa9f2be997878eefb049015c16ad8889/pyobjc_framework_coreaudiokit-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7b7230cf4f34f2e35fc40928fa723c43193c359b21b690485001ca3616829b6c", size = 7253, upload-time = "2025-11-14T09:43:51.477Z" }, - { url = "https://files.pythonhosted.org/packages/c2/53/e4233fbe5b94b124f5612e1edc130a9280c4674a1d1bf42079ea14b816e1/pyobjc_framework_coreaudiokit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e1144c272f8d6429a34a6757700048f4631eb067c4b08d4768ddc28c371a7014", size = 7250, upload-time = "2025-11-14T09:43:53.208Z" }, - { url = "https://files.pythonhosted.org/packages/19/d7/f171c04c6496afeaad2ab658b0c810682c8407127edc94d4b3f3b90c2bb1/pyobjc_framework_coreaudiokit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:97d5dd857e73d5b597cfc980972b021314b760e2f5bdde7bbba0334fbf404722", size = 7273, upload-time = "2025-11-14T09:43:55.411Z" }, - { url = "https://files.pythonhosted.org/packages/81/9a/6cb91461b07c38b2db7918ee756f05fd704120b75ddc1a759e04af50351b/pyobjc_framework_coreaudiokit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dc1589cda7a4ae0560bf73e1a0623bb710de09ef030d585035f8a428a3e8d6a1", size = 7284, upload-time = "2025-11-14T09:43:57.109Z" }, - { url = "https://files.pythonhosted.org/packages/21/d8/1418fb222c6502ce2a99c415982895b510f6c48bdf60ca0dbed9897d96df/pyobjc_framework_coreaudiokit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6ec70b69d21925e02602cc22c5e0132daedc15ce65b7e3cc863fdb5f13cc23e3", size = 7446, upload-time = "2025-11-14T09:43:58.714Z" }, - { url = "https://files.pythonhosted.org/packages/92/65/36f017784df7ca5ad7741f1624c89410d62d0ebdeb437be32f7a1286a6df/pyobjc_framework_coreaudiokit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a2f9839a4bd05db2e7d12659af4cab32ec17dfee89fff83bbe9faee558e77a08", size = 7349, upload-time = "2025-11-14T09:44:00.625Z" }, - { url = "https://files.pythonhosted.org/packages/f1/fe/f012a1e3b92991819ae3319408cd77b2e7019be14d2b751d6ff613a8fe83/pyobjc_framework_coreaudiokit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0bf793729bf95bb2c667eba315ba4a6ab359f930efd1a5ea686392478abb687f", size = 7503, upload-time = "2025-11-14T09:44:02.166Z" }, + { url = "https://files.pythonhosted.org/packages/2a/30/b1aff3ee94d570579bfa6763288c807e4707f71033bcaf1e42f1668a3ddf/pyobjc_framework_coreaudiokit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88397f66edce76f03437678e6c6b18a52e0b03b06382c97444bd0ef09d3dd31a", size = 7275, upload-time = "2026-06-19T16:08:18.191Z" }, + { url = "https://files.pythonhosted.org/packages/e4/41/0121efdb19535aacd8fcb1c03bc7cc6f2e1865fabd77316d33e6422f09ad/pyobjc_framework_coreaudiokit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c36db7e1c6606cd8dc3d50a503f596774d554476729dd5870d05c52aa2de4da1", size = 7273, upload-time = "2026-06-19T16:08:19.111Z" }, + { url = "https://files.pythonhosted.org/packages/f7/8f/78a68b53ee2227dd22939f8d435b5dc6252228fbcf5bbf497fe0d6a6b3a6/pyobjc_framework_coreaudiokit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fbc5f8f3b46645b1051658be8b172c85ed700a649ba3c4bcf68a5b8d3c09ecee", size = 7300, upload-time = "2026-06-19T16:08:19.882Z" }, + { url = "https://files.pythonhosted.org/packages/44/46/5018ea6e71c3f29ef63241a28b20f67cec9236776d17a23e2d628171a0f6/pyobjc_framework_coreaudiokit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f6f4a3b5fac21170cce2f7585117c3fcce873ead84b4fe7e294dc823bff242dc", size = 7311, upload-time = "2026-06-19T16:08:20.701Z" }, + { url = "https://files.pythonhosted.org/packages/c3/fa/6ea54d77c2b557c568274ccf75306fff426b7e099a0cd0b7e2c9c28c6e78/pyobjc_framework_coreaudiokit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:dbc7d4ef4d7af452ba45c14e5324c11865e170b3b15f35d9431c97431b208f58", size = 7467, upload-time = "2026-06-19T16:08:21.767Z" }, + { url = "https://files.pythonhosted.org/packages/63/f3/000526a108d242477175cc9cecf71ca951fe6807db7cb2bd27389814892a/pyobjc_framework_coreaudiokit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:90610d5af81a071db7508df7a0dcc06ca2a95602f11dd7205f591bc4690e134f", size = 7373, upload-time = "2026-06-19T16:08:22.582Z" }, + { url = "https://files.pythonhosted.org/packages/8d/49/bb846c6d2f34d168c24fb7c6968699ec267de0fce6d381fbc4ea6e6b9100/pyobjc_framework_coreaudiokit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:f997becd046c41e2da1a4e9f67715a13344f8546364de973dede6bf47a85b798", size = 7530, upload-time = "2026-06-19T16:08:23.398Z" }, + { url = "https://files.pythonhosted.org/packages/c6/e2/84bbd3322eb54a8c85c301ea64be7888b83038f3a83716b17e775af64e68/pyobjc_framework_coreaudiokit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:78673312ebc806c4c635ca5906dab7c109d8346f058159cc6efa2be879244aab", size = 7372, upload-time = "2026-06-19T16:08:24.217Z" }, + { url = "https://files.pythonhosted.org/packages/98/2e/53817ad60d89043c890b0e8252c99e8d772e7ccc8a938217a8a741114558/pyobjc_framework_coreaudiokit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:173b58fce17e83aee72adeebeb5cc5a994184cabd1d5c25a6e4ba9902ae3e6b6", size = 7525, upload-time = "2026-06-19T16:08:24.99Z" }, ] [[package]] name = "pyobjc-framework-corebluetooth" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4b/25/d21d6cb3fd249c2c2aa96ee54279f40876a0c93e7161b3304bf21cbd0bfe/pyobjc_framework_corebluetooth-12.1.tar.gz", hash = "sha256:8060c1466d90bbb9100741a1091bb79975d9ba43911c9841599879fc45c2bbe0", size = 33157, upload-time = "2025-11-14T10:13:28.064Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/91/c76f3c5e8e80c7047e43c4c05b3e6fda9a7cefad5aae85487007674c966c/pyobjc_framework_corebluetooth-12.2.1.tar.gz", hash = "sha256:7dbb285295097205bebbcb11f55161e5faa02111108fb7b17536176e31971eb0", size = 37568, upload-time = "2026-06-19T16:20:12.191Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/1b/06914f4eb1bd8ce598fdd210e1a7411556286910fc8d8919ab7dbaebe629/pyobjc_framework_corebluetooth-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:937849f4d40a33afbcc56cbe90c8d1fbf30fb27a962575b9fb7e8e2c61d3c551", size = 13187, upload-time = "2025-11-14T09:44:04.098Z" }, - { url = "https://files.pythonhosted.org/packages/57/7a/26ae106beb97e9c4745065edb3ce3c2bdd91d81f5b52b8224f82ce9d5fb9/pyobjc_framework_corebluetooth-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:37e6456c8a076bd5a2bdd781d0324edd5e7397ef9ac9234a97433b522efb13cf", size = 13189, upload-time = "2025-11-14T09:44:06.229Z" }, - { url = "https://files.pythonhosted.org/packages/2a/56/01fef62a479cdd6ff9ee40b6e062a205408ff386ce5ba56d7e14a71fcf73/pyobjc_framework_corebluetooth-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe72c9732ee6c5c793b9543f08c1f5bdd98cd95dfc9d96efd5708ec9d6eeb213", size = 13209, upload-time = "2025-11-14T09:44:08.203Z" }, - { url = "https://files.pythonhosted.org/packages/e0/6c/831139ebf6a811aed36abfdfad846bc380dcdf4e6fb751a310ce719ddcfd/pyobjc_framework_corebluetooth-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5a894f695e6c672f0260327103a31ad8b98f8d4fb9516a0383db79a82a7e58dc", size = 13229, upload-time = "2025-11-14T09:44:10.463Z" }, - { url = "https://files.pythonhosted.org/packages/09/3c/3a6fe259a9e0745aa4612dee86b61b4fd7041c44b62642814e146b654463/pyobjc_framework_corebluetooth-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:1daf07a0047c3ed89fab84ad5f6769537306733b6a6e92e631581a0f419e3f32", size = 13409, upload-time = "2025-11-14T09:44:12.438Z" }, - { url = "https://files.pythonhosted.org/packages/2f/41/90640a4db62f0bf0611cf8a161129c798242116e2a6a44995668b017b106/pyobjc_framework_corebluetooth-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:15ba5207ca626dffe57ccb7c1beaf01f93930159564211cb97d744eaf0d812aa", size = 13222, upload-time = "2025-11-14T09:44:14.345Z" }, - { url = "https://files.pythonhosted.org/packages/86/99/8ed2f0ca02b9abe204966142bd8c4501cf6da94234cc320c4c0562c467e8/pyobjc_framework_corebluetooth-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e5385195bd365a49ce70e2fb29953681eefbe68a7b15ecc2493981d2fb4a02b1", size = 13408, upload-time = "2025-11-14T09:44:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/62/a8/1a7acdfeb474daa25704f38f20f26b5fa2111b768c6d1c1451417bf8e4a5/pyobjc_framework_corebluetooth-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:41dd9c9b4390e7316d189ba32d624ce933f7b25d351268b6e8cbea44f65e55f5", size = 13195, upload-time = "2026-06-19T16:08:25.759Z" }, + { url = "https://files.pythonhosted.org/packages/00/ad/de57d9060e4c5e9ca1a9bdd5b6bd1bdb73b198c0c53953cac445d9b3a84b/pyobjc_framework_corebluetooth-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f11df7052fa2d0524a9dadbc9c578fd3b8f3a350431edfa4ffa9e0a74b6faa32", size = 13195, upload-time = "2026-06-19T16:08:26.961Z" }, + { url = "https://files.pythonhosted.org/packages/c6/c4/7938016860850e28c001dc9b7c653352c43f7aebfffc6d3c5fd087281f22/pyobjc_framework_corebluetooth-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2f8d2ed65c0e98ba044b6a7fc2f9a290a5c579a28d33a57c642f8617ccbcca0f", size = 13217, upload-time = "2026-06-19T16:08:27.802Z" }, + { url = "https://files.pythonhosted.org/packages/ae/79/890a53ed45c1006eedcf60627b7d661c8696e5367723ceb25cc6a0216b30/pyobjc_framework_corebluetooth-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:30a26eef36c250fc14e73335641e24f764b32b7e42bae945a5d8a1c2347040b5", size = 13235, upload-time = "2026-06-19T16:08:28.727Z" }, + { url = "https://files.pythonhosted.org/packages/22/7a/40ffc3be8e31b1eb1f8f5eb2a58ef832287fb1ea6b3c452dc8b25b9e064b/pyobjc_framework_corebluetooth-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:756933b9ce6160a986c8877ab667659fa2d8e15aff28d0981b5284fbdd1ea735", size = 13416, upload-time = "2026-06-19T16:08:29.643Z" }, + { url = "https://files.pythonhosted.org/packages/30/ff/6f3b0bb3110ec82dbedaea47de151bd688980f5aadc634ef0cd236fdbd16/pyobjc_framework_corebluetooth-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:2a2e6d56f51e4ca3e3b9766ef34150a9a7ce5f0cf4f9ee879ec10923af58e97e", size = 13223, upload-time = "2026-06-19T16:08:30.672Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4c/4e12660569219e4a68186ae9709b85278d3ebaf8d2f8e1c826a7337f4f7a/pyobjc_framework_corebluetooth-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:50d7e4245dbdc8789dcc1f11fca2e633aa126a298b09db62f8216531fe107ee2", size = 13414, upload-time = "2026-06-19T16:08:31.679Z" }, + { url = "https://files.pythonhosted.org/packages/1b/4c/976ae9bcce3615af806e3c314ea9caa3faacf11ec44f00b1a149559c6cb3/pyobjc_framework_corebluetooth-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:c8d126c56b71c25218be186930a1b41739f83e931726a52e3298beeb170c5e5b", size = 13222, upload-time = "2026-06-19T16:08:32.481Z" }, + { url = "https://files.pythonhosted.org/packages/99/be/44bb648a6b5c8aec79138bf562dab9eef414016ee31f37066bf81d809ae9/pyobjc_framework_corebluetooth-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:81023518feb75e9b2b676b28198955c51ae00548cf23c73c524c7101263b68db", size = 13424, upload-time = "2026-06-19T16:08:33.336Z" }, ] [[package]] name = "pyobjc-framework-coredata" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3e/c5/8cd46cd4f1b7cf88bdeed3848f830ea9cdcc4e55cd0287a968a2838033fb/pyobjc_framework_coredata-12.1.tar.gz", hash = "sha256:1e47d3c5e51fdc87a90da62b97cae1bc49931a2bb064db1305827028e1fc0ffa", size = 124348, upload-time = "2025-11-14T10:13:36.435Z" } +sdist = { url = "https://files.pythonhosted.org/packages/21/cc/62113edb09c6f72922d800ad7dbd2b812e69f5933a245c59f2d33b214a47/pyobjc_framework_coredata-12.2.1.tar.gz", hash = "sha256:f357447b7955cfe5391dac4fe003b79ded307f4f00712dcfaec3d3ecfca30824", size = 143307, upload-time = "2026-06-19T16:20:13.096Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/66/d08c3d412a12f4032e432c770185bc9cd3521789d8f3eafa2c0c78f8ca4e/pyobjc_framework_coredata-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:547115df2391dafe9dbc0ae885a7d87e1c5f1710384effd8638857e5081c75ec", size = 16395, upload-time = "2025-11-14T09:44:18.709Z" }, - { url = "https://files.pythonhosted.org/packages/6b/a8/4c694c85365071baef36013a7460850dcf6ebfea0ba239e52d7293cdcb93/pyobjc_framework_coredata-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c861dc42b786243cbd96d9ea07d74023787d03637ef69a2f75a1191a2f16d9d6", size = 16395, upload-time = "2025-11-14T09:44:21.105Z" }, - { url = "https://files.pythonhosted.org/packages/a3/29/fe24dc81e0f154805534923a56fe572c3b296092f086cf5a239fccc2d46a/pyobjc_framework_coredata-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a3ee3581ca23ead0b152257e98622fe0bf7e7948f30a62a25a17cafe28fe015e", size = 16409, upload-time = "2025-11-14T09:44:23.582Z" }, - { url = "https://files.pythonhosted.org/packages/f8/12/a22773c3a590d4923c74990d6714c4463bd1e183daaa67d6b00c9f325b33/pyobjc_framework_coredata-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:79f68577a7e96c57559ec844a129a5edce6827cdfafe49bf31524a488d715a37", size = 16420, upload-time = "2025-11-14T09:44:26.179Z" }, - { url = "https://files.pythonhosted.org/packages/a6/32/9595f0c8727d6ac312d18d23fc4a327c34c6ab873d2b760bbc40cf063726/pyobjc_framework_coredata-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:279b39bdb2a9c5e4d0377c1e81263b7d137bf2be37e15d6b5b2403598596f0e3", size = 16576, upload-time = "2025-11-14T09:44:28.266Z" }, - { url = "https://files.pythonhosted.org/packages/66/2e/238dedc9499b4cccb963dccdfbbc420ace33a01fb9e1221a79c3044fecce/pyobjc_framework_coredata-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:07d19e7db06e1ad21708cf01fc8014d5f1b73efd373a99af6ff882c1bfb8497b", size = 16479, upload-time = "2025-11-14T09:44:30.814Z" }, - { url = "https://files.pythonhosted.org/packages/e1/55/a044857da51644bce6d1914156db5190443653ab9ce6806864728d06d017/pyobjc_framework_coredata-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ac49d45b372f768bd577a26b503dd04e553ffebd3aa96c653b1c88a3f2733552", size = 16636, upload-time = "2025-11-14T09:44:32.952Z" }, + { url = "https://files.pythonhosted.org/packages/41/ec/2c079dfee71fd8bb0aea2a47852a2e7835e3075fdaacd2f8643ea09fabd7/pyobjc_framework_coredata-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:429e386e10a463cf8b7e80cdf516d1c0f50a71697238cd34640e5625838dd3e9", size = 16540, upload-time = "2026-06-19T16:08:34.36Z" }, + { url = "https://files.pythonhosted.org/packages/7c/b9/ab7be45781781fc68faf523b7cfcdaf05c2b307fbc49140905eb31f3ecf2/pyobjc_framework_coredata-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:382594752f87fba5b6804619507d54ab4b6d2b54b520bfa291b06003e36c4646", size = 16542, upload-time = "2026-06-19T16:08:35.388Z" }, + { url = "https://files.pythonhosted.org/packages/32/17/87ef5c0edb220df910a4936ad23272f763065ee4817ad01107b19a8c78ff/pyobjc_framework_coredata-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:53093b9a500e82794b142bfe3c844414e46ccc2b2f9e1f5a998243646be59725", size = 16552, upload-time = "2026-06-19T16:08:36.178Z" }, + { url = "https://files.pythonhosted.org/packages/9a/90/6c0887e8cefd12b76e9c6104dd07a5251ff2d5fe47a27427b07754326d1e/pyobjc_framework_coredata-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a8353dbe4cb0cc6d5313d23e47ed6c00bfc5161c84777d1d3e21048522822feb", size = 16562, upload-time = "2026-06-19T16:08:37.125Z" }, + { url = "https://files.pythonhosted.org/packages/e7/70/60fa04f65640e76e2b49a2a2e297b54c0d980a2bd631c3e9b5c2d247e262/pyobjc_framework_coredata-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:7be973a5f2f5646a64d4c25175ec7ca2584f3ccd2c8bea4c2abc702ca61884c5", size = 16722, upload-time = "2026-06-19T16:08:38.044Z" }, + { url = "https://files.pythonhosted.org/packages/6a/f6/b58d1ee3a6c489e0a0429dcac32b283e6609cba5a8da3cc013cfb03b50db/pyobjc_framework_coredata-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e3f12941b0b83c66c3e7d9845bc14ed8d215c2975f55098deee56e22477b93e5", size = 16624, upload-time = "2026-06-19T16:08:39.068Z" }, + { url = "https://files.pythonhosted.org/packages/3e/6f/dcdf9ed284f7dc56f48667fb137d1080de371297dbc86103b99b5dda94db/pyobjc_framework_coredata-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:cb6c3b1f70301b2201e84b12adfac93c8f47c7678f35d56ae4f63100dc4e438a", size = 16782, upload-time = "2026-06-19T16:08:40.07Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8b/dafc41f03709426901f5981334ecbd9b315ea29616d64d8711502d8ed802/pyobjc_framework_coredata-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:698b5591c622b6ce451851fe393fe7e9a0a1148830659f500e577d06ea470781", size = 16621, upload-time = "2026-06-19T16:08:40.908Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/05ebf0eab33ccc7cc80b7b5e6da89a37efba99aaa0b2f78b5176906f7a9b/pyobjc_framework_coredata-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:dabebe6ac3cdda4563635c781b0ab5e65e4f04e76e636dade0996a58ea66b311", size = 16771, upload-time = "2026-06-19T16:08:42.185Z" }, ] [[package]] name = "pyobjc-framework-corehaptics" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3a/2f/74a3da79d9188b05dd4be4428a819ea6992d4dfaedf7d629027cf1f57bfc/pyobjc_framework_corehaptics-12.1.tar.gz", hash = "sha256:521dd2986c8a4266d583dd9ed9ae42053b11ae7d3aa89bf53fbee88307d8db10", size = 22164, upload-time = "2025-11-14T10:13:38.941Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/1b/76c27ae0f86126802c7f82f21c67868467795810750d9d192006471aa965/pyobjc_framework_corehaptics-12.2.1.tar.gz", hash = "sha256:73ce1afcb0174add11fd6f05cc67d8a371802ce8e94ba9d4f65c7cee0f392b0f", size = 24886, upload-time = "2026-06-19T16:20:14.011Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/f4/f469d6a9cac7c195f3d08fa65f94c32dd1dcf97a54b481be648fb3a7a5f3/pyobjc_framework_corehaptics-12.1-py2.py3-none-any.whl", hash = "sha256:a3b07d36ddf5c86a9cdaa411ab53d09553d26ea04fc7d4f82d21a84f0fc05fc0", size = 5382, upload-time = "2025-11-14T09:44:34.725Z" }, + { url = "https://files.pythonhosted.org/packages/44/0c/23e82eded054389a686377ebce9b6f82d4fed4c445b004bb8385c3188bb1/pyobjc_framework_corehaptics-12.2.1-py2.py3-none-any.whl", hash = "sha256:d19505e9e38136f50fc551ad0e1849afd1656dce068c82018ad669d01128f8c9", size = 5434, upload-time = "2026-06-19T16:08:43.061Z" }, ] [[package]] name = "pyobjc-framework-corelocation" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cc/79/b75885e0d75397dc2fe1ed9ca80be2b64c18b817f5fb924277cb1bf7b163/pyobjc_framework_corelocation-12.1.tar.gz", hash = "sha256:3674e9353f949d91dde6230ad68f6d5748a7f0424751e08a2c09d06050d66231", size = 53511, upload-time = "2025-11-14T10:13:43.384Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b8/93/41d2ae5cf15a27ee1b51e167b538e50aa752597002878556ed49b3615573/pyobjc_framework_corelocation-12.2.1.tar.gz", hash = "sha256:10b3c206049b70cbab0f98b37bcd91ad97de5ab57041b18a60ab702629009a31", size = 60318, upload-time = "2026-06-19T16:20:14.792Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/1c/e515e91de901715d33f6da49c5b6dd588262f5471265feda27bb5586acce/pyobjc_framework_corelocation-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:833678750636976833d905a1575896167841394b77936074774b8921c94653e5", size = 12701, upload-time = "2025-11-14T09:44:36.763Z" }, - { url = "https://files.pythonhosted.org/packages/34/ac/44b6cb414ce647da8328d0ed39f0a8b6eb54e72189ce9049678ce2cb04c3/pyobjc_framework_corelocation-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ffc96b9ba504b35fe3e0fcfb0153e68fdfca6fe71663d240829ceab2d7122588", size = 12700, upload-time = "2025-11-14T09:44:38.717Z" }, - { url = "https://files.pythonhosted.org/packages/71/57/1b670890fbf650f1a00afe5ee897ea3856a4a1417c2304c633ee2e978ed0/pyobjc_framework_corelocation-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8c35ad29a062fea7d417fd8997a9309660ba7963f2847c004e670efbe6bb5b00", size = 12721, upload-time = "2025-11-14T09:44:41.185Z" }, - { url = "https://files.pythonhosted.org/packages/9f/09/3da1947a5908d70461596eda5a0dc486ae807dc1c5a1ce2bf98567b474be/pyobjc_framework_corelocation-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:616eec0ccfcdcff7696bccf88c1aa39935387e595b22dd4c14842567aa0986a6", size = 12736, upload-time = "2025-11-14T09:44:42.977Z" }, - { url = "https://files.pythonhosted.org/packages/5d/9a/e5e11ec90500ce2c809a46113d3ebd70dd4b4ce450072db9a85f86e9a30f/pyobjc_framework_corelocation-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0a80ba8e8d9120eb80486235c483a0c734cb451265e5aa81bcf315f0e644eb00", size = 12867, upload-time = "2025-11-14T09:44:44.89Z" }, - { url = "https://files.pythonhosted.org/packages/38/ef/cd24f05a406c4f8478117f7bf54a9a7753b6485b3fc645a5d0530b1fa34b/pyobjc_framework_corelocation-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:3ed12521c457e484944fd91b1d19643d00596d3b0ea3455984c9e918a9c65138", size = 12720, upload-time = "2025-11-14T09:44:46.846Z" }, - { url = "https://files.pythonhosted.org/packages/72/f5/f08ea0a1eacc0e45260a4395412af2f501f93aa91c7efc0cadd39ee75717/pyobjc_framework_corelocation-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:43aa6d5c273c5efa0960dbb05ae7165948f12a889cb0fdcba2e0099d98f4c78d", size = 12862, upload-time = "2025-11-14T09:44:48.688Z" }, + { url = "https://files.pythonhosted.org/packages/86/9b/3b135777df07b5397518a233571e8f63224309b83b9c5f01fa8ba834eae8/pyobjc_framework_corelocation-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f8571fc9247dfbda6e485e3f68d78f8368b2e3197bc15f8eb6c188bf3d45f618", size = 12836, upload-time = "2026-06-19T16:08:43.956Z" }, + { url = "https://files.pythonhosted.org/packages/46/75/7a72b5c8cd470eeadc40b9bf05cbabb5049372de0a44ced4a04489d5a165/pyobjc_framework_corelocation-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cc3f7d0a9dd54b99faf7020205b16576466aff70c7babe2a905b8818a0c1d80d", size = 12836, upload-time = "2026-06-19T16:08:44.911Z" }, + { url = "https://files.pythonhosted.org/packages/b8/fc/4f6a89027995948270699c47a86583bfe586ac0c480cf8427fb708bf3a9c/pyobjc_framework_corelocation-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:683b6eb23db94ae6ca48e58089c4a9641fd90b1137e54c147ad56ed6db4bb570", size = 12857, upload-time = "2026-06-19T16:08:45.738Z" }, + { url = "https://files.pythonhosted.org/packages/e9/00/156e1d533f3167e638a8e649f02a422cd1f95270932fede56e6bcf46c878/pyobjc_framework_corelocation-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f336ab0f9289d1909bf86cdd798ac4308166f6e90eecf07452d21182b9a7cba", size = 12874, upload-time = "2026-06-19T16:08:46.645Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f9/7519b4f9d4feeebfb770ed563b756c5dd370d9f4fa302425f05829a7d2d6/pyobjc_framework_corelocation-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ca3b36828b4324c058ccc289b33cbdb7986bf879719d99a735c3abcd1ceb6ad1", size = 13004, upload-time = "2026-06-19T16:08:47.482Z" }, + { url = "https://files.pythonhosted.org/packages/ce/09/556f20c1178bd39ede1b18690211b36c13ad458692330474ef2e42b2202b/pyobjc_framework_corelocation-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:407336ce4e2c4d4d61b486cf64a4672a0048523b9412db8d3a1d1d5b3eaa53bf", size = 12852, upload-time = "2026-06-19T16:08:48.495Z" }, + { url = "https://files.pythonhosted.org/packages/57/19/bad61b941acf3c7e1a3aa623f9bd71dab9cfa7a52df40f79fed4370c2ddc/pyobjc_framework_corelocation-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:80423af235cfc73a719451e326631e743662d9c03821d227647d9fdb596a6401", size = 13004, upload-time = "2026-06-19T16:08:49.283Z" }, + { url = "https://files.pythonhosted.org/packages/d2/c3/e3b7c6982a053293995be2cb47bc6294f01db98892c3f155f08ec87f9922/pyobjc_framework_corelocation-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:8e1a2e5cdf5175b01cdda52a8d7dd5daec0afe08a5a0c7a359c2a2f93b66109f", size = 12839, upload-time = "2026-06-19T16:08:50.089Z" }, + { url = "https://files.pythonhosted.org/packages/d4/bb/a3bf2bc14ad5f0b6586de6526a9677cf505654ac1dace6b7aae3935ef72f/pyobjc_framework_corelocation-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:304db40687dd383fc787b795be9e8825f33c4fabf58b9a7d5582377eef173311", size = 12993, upload-time = "2026-06-19T16:08:50.891Z" }, ] [[package]] name = "pyobjc-framework-coremedia" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/7d/5ad600ff7aedfef8ba8f51b11d9aaacdf247b870bd14045d6e6f232e3df9/pyobjc_framework_coremedia-12.1.tar.gz", hash = "sha256:166c66a9c01e7a70103f3ca44c571431d124b9070612ef63a1511a4e6d9d84a7", size = 89566, upload-time = "2025-11-14T10:13:49.788Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/79/f501d730a9c320e0b2b3916e95f57e66dd6736d210a1aa5b63eb6c43e605/pyobjc_framework_coremedia-12.2.1.tar.gz", hash = "sha256:71b45f7cd52bd997d836c15a0e1016db90815a219dc87fd20435a6f08b87df7b", size = 98252, upload-time = "2026-06-19T16:20:15.75Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/35/9310566c24aad764b619057a5d583781f2e615388bcdeb28113b3a8b054f/pyobjc_framework_coremedia-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4fce8570db3eaa7b841456a7890b24546504d1059157dc33e700b14d9d3073d8", size = 29501, upload-time = "2025-11-14T09:44:51.605Z" }, - { url = "https://files.pythonhosted.org/packages/c8/bc/e66de468b3777d8fece69279cf6d2af51d2263e9a1ccad21b90c35c74b1b/pyobjc_framework_coremedia-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ee7b822c9bb674b5b0a70bfb133410acae354e9241b6983f075395f3562f3c46", size = 29503, upload-time = "2025-11-14T09:44:54.716Z" }, - { url = "https://files.pythonhosted.org/packages/c8/ae/f773cdc33c34a3f9ce6db829dbf72661b65c28ea9efaec8940364185b977/pyobjc_framework_coremedia-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:161a627f5c8cd30a5ebb935189f740e21e6cd94871a9afd463efdb5d51e255fa", size = 29396, upload-time = "2025-11-14T09:44:57.563Z" }, - { url = "https://files.pythonhosted.org/packages/a5/ea/aee26a475b4af8ed4152d3c50b1b8955241b8e95ae789aa9ee296953bc6a/pyobjc_framework_coremedia-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:98e885b7a092083fceaef0a7fc406a01ba7bcd3318fb927e59e055931c99cac8", size = 29414, upload-time = "2025-11-14T09:45:01.336Z" }, - { url = "https://files.pythonhosted.org/packages/db/9d/5ff10ee0ff539e125c96b8cff005457558766f942919814c968c3367cc32/pyobjc_framework_coremedia-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:d2b84149c1b3e65ec9050a3e5b617e6c0b4cdad2ab622c2d8c5747a20f013e16", size = 29477, upload-time = "2025-11-14T09:45:04.218Z" }, - { url = "https://files.pythonhosted.org/packages/08/e2/b890658face1290c8b6b6b53a1159c822bece248f883e42302548bef38da/pyobjc_framework_coremedia-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:737ec6e0b63414f42f7188030c85975d6d2124fbf6b15b52c99b6cc20250af4d", size = 29447, upload-time = "2025-11-14T09:45:07.17Z" }, - { url = "https://files.pythonhosted.org/packages/a4/9e/16981d0ee04b182481ce1e497b5e0326bad6d698fe0265bb7db72b1b26b5/pyobjc_framework_coremedia-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6a9419e0d143df16a1562520a13a389417386e2a53031530af6da60c34058ced", size = 29500, upload-time = "2025-11-14T09:45:10.506Z" }, + { url = "https://files.pythonhosted.org/packages/ed/2c/05fde2fb99ef4ae805485b517972ac5321f49fd037ba6c8ec8d6eb2ca56b/pyobjc_framework_coremedia-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:97a7b4bc2b53ad207648b4f187e6a7f00a45684db992803bef9aaf5931e0b572", size = 29518, upload-time = "2026-06-19T16:08:51.711Z" }, + { url = "https://files.pythonhosted.org/packages/5c/34/8b97b76f0643e01c98d5c246b1ed74884f4b3c43dfff2a36886212e20dfa/pyobjc_framework_coremedia-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:38e03a92a7fdf43162faadf354a8497ae764e3b3be26a8ffb107ed5d743cac76", size = 29513, upload-time = "2026-06-19T16:08:52.69Z" }, + { url = "https://files.pythonhosted.org/packages/af/17/6bf365530573a6b7719b5a47efe6c38ac8c42f6b556babe419f5de48a84c/pyobjc_framework_coremedia-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fd0e25bc704dd91882bc29d682880c42f712b39bdfe3d3168780d24a9d9eaf26", size = 29419, upload-time = "2026-06-19T16:08:53.52Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2a/9e5beae2961c9d22ce16258f39edae69996ee0167dd4cfe4e771454086c1/pyobjc_framework_coremedia-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:45878f686ce8ea1735ce382b34ef3a5852cafe0ae2a27a49c08701f4c3ab830b", size = 29431, upload-time = "2026-06-19T16:08:54.41Z" }, + { url = "https://files.pythonhosted.org/packages/dc/51/00b7f012e55475502296cc8ef276b94ad7d4d10d97ce2e88bf9d87f9b664/pyobjc_framework_coremedia-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9f2b30ef288c9f2fa1599a1cc5868f8cd949c7138a7f15244c7a6884afcef273", size = 29491, upload-time = "2026-06-19T16:08:55.297Z" }, + { url = "https://files.pythonhosted.org/packages/16/dc/7083e6781fcd843d210ec6d247d28c6e1032ea1c5655b9e1182d0a85f331/pyobjc_framework_coremedia-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:84e9fb3f7b6420fc3865a5812604c63546b5777f4999e0e0e830a9a0816e8743", size = 29468, upload-time = "2026-06-19T16:08:56.129Z" }, + { url = "https://files.pythonhosted.org/packages/11/74/edb3ec87d2e5f962c75af990207e4df39b290f11b4f4c3cc446ee141e4e7/pyobjc_framework_coremedia-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:541d64d2fc7576054bdc3508a94f37671e81e5ca1582e16ebec2c0e05dd75412", size = 29519, upload-time = "2026-06-19T16:08:56.956Z" }, + { url = "https://files.pythonhosted.org/packages/28/0f/9176b6a7666e46273c0392c8f77bc812e006dc74a1c21263c4bdf385012f/pyobjc_framework_coremedia-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:2a355770b66b4caf99560c11af44dfeca53066ece75807fc6a391a16369ccc72", size = 29502, upload-time = "2026-06-19T16:08:57.918Z" }, + { url = "https://files.pythonhosted.org/packages/a5/27/13e7d8d16528734690f83abd901cbc53dd6f7c1adbcb3a902166735c6c21/pyobjc_framework_coremedia-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:5536453ba631227810a1141fb1761149244bd6d0e7cd4b12477be3b164f5bb11", size = 29555, upload-time = "2026-06-19T16:08:58.792Z" }, ] [[package]] name = "pyobjc-framework-coremediaio" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/08/8e/23baee53ccd6c011c965cff62eb55638b4088c3df27d2bf05004105d6190/pyobjc_framework_coremediaio-12.1.tar.gz", hash = "sha256:880b313b28f00b27775d630174d09e0b53d1cdbadb74216618c9dd5b3eb6806a", size = 51100, upload-time = "2025-11-14T10:13:54.277Z" } +sdist = { url = "https://files.pythonhosted.org/packages/08/8b/60e73d8049e9c123ff237694acea8752e9e8143864bfea4bab0bebb55db4/pyobjc_framework_coremediaio-12.2.1.tar.gz", hash = "sha256:edfd070544857b8e1d2ae55ed7c7eac9f513b4cd7c03ee79615c7d524e362d62", size = 56604, upload-time = "2026-06-19T16:20:16.744Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/33/9ab6ac724ae14b464fa183cc92f15a426f0aed0ecc296836bf114e4fc4e7/pyobjc_framework_coremediaio-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1a471e82fddeaa8a172d96dd897c3489f0a0ad8d5e5d2b46ae690e273c254cd0", size = 17219, upload-time = "2025-11-14T09:45:12.656Z" }, - { url = "https://files.pythonhosted.org/packages/46/6c/88514f8938719f74aa13abb9fd5492499f1834391133809b4e125c3e7150/pyobjc_framework_coremediaio-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3da79c5b9785c5ccc1f5982de61d4d0f1ba29717909eb6720734076ccdc0633c", size = 17218, upload-time = "2025-11-14T09:45:15.294Z" }, - { url = "https://files.pythonhosted.org/packages/d4/0c/9425c53c9a8c26e468e065ba12ef076bab20197ff7c82052a6dddd46d42b/pyobjc_framework_coremediaio-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1108f8a278928fbca465f95123ea4a56456bd6571c1dc8b91793e6c61d624517", size = 17277, upload-time = "2025-11-14T09:45:17.457Z" }, - { url = "https://files.pythonhosted.org/packages/6d/d1/0267ec27841ee96458e6b669ce5b0c67d040ef3d5de90fa4e945ff989c48/pyobjc_framework_coremediaio-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:85ae768294ec307d5b502c075aeae1c53a731afc2f7f0307c9bef785775e26a6", size = 17249, upload-time = "2025-11-14T09:45:20.42Z" }, - { url = "https://files.pythonhosted.org/packages/ca/4e/bd0114aa052aaffc250b0c00567b42df8c7cb35517488c3238bcc964d016/pyobjc_framework_coremediaio-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6136a600a1435b9e798427984088a7bd5e68778e1bcf48a23a0eb9bc946a06f0", size = 17573, upload-time = "2025-11-14T09:45:22.572Z" }, - { url = "https://files.pythonhosted.org/packages/41/fd/cdf26be5b15ee2f2a73c320a62393e03ab15966ee8262540f918f0c7b181/pyobjc_framework_coremediaio-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a5ca5763f185f48fedafec82f794dca53c55d2e52058d1b11baa43dd4ab0cd16", size = 17266, upload-time = "2025-11-14T09:45:24.719Z" }, - { url = "https://files.pythonhosted.org/packages/18/75/be0bfb86497f98915c7d015e3c21d199a1be8780ed08c171832b27593eac/pyobjc_framework_coremediaio-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:8aaeb44fdf9382dda30ff5f53ba6e291c1b514b7ab651f7b31d7fb4c27bfd309", size = 17561, upload-time = "2025-11-14T09:45:26.897Z" }, + { url = "https://files.pythonhosted.org/packages/c5/6e/b40809d0121ec4e0e34cb990559584ba8fb2d333e552d0d1ba8c30116d01/pyobjc_framework_coremediaio-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:59eb9122717d0b390e4c0084d76560559f740133a150df8fdeac0161fb60e40e", size = 17305, upload-time = "2026-06-19T16:08:59.654Z" }, + { url = "https://files.pythonhosted.org/packages/79/9e/7bd5ca10c31a987b04c18a5b40dd7cb31fa7f13d221b4d209646d2b34c52/pyobjc_framework_coremediaio-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7525dff27f84f7cc007d2077787835cdf094ed925235e4f179bd6558a889b008", size = 17305, upload-time = "2026-06-19T16:09:00.583Z" }, + { url = "https://files.pythonhosted.org/packages/2f/f2/95c8d8ef684b377d70b65ccea9cc8bb583eff79690d77a390fd52f190120/pyobjc_framework_coremediaio-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c60ecc49fa18a53c5bcf7f844554d5e24883d2b7d5537923dfe4e8512069a6a0", size = 17361, upload-time = "2026-06-19T16:09:01.421Z" }, + { url = "https://files.pythonhosted.org/packages/0c/85/ae2715fe1d788e166ef04d7080f09de5cf3a63fa83253bfde42027ccf089/pyobjc_framework_coremediaio-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:30cfc0e30b46d67668114d7d2bf0a66196928ad84db77d34a1615d3f1b661c46", size = 17324, upload-time = "2026-06-19T16:09:02.234Z" }, + { url = "https://files.pythonhosted.org/packages/1d/99/96f68d7d82978a568ffed99a8b42361343499af4e227a4f896351418462f/pyobjc_framework_coremediaio-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:1dc37de0633e2daff4ae14640cec68e1b2c2d3b11f866f36961704abdfdabc38", size = 17646, upload-time = "2026-06-19T16:09:03.158Z" }, + { url = "https://files.pythonhosted.org/packages/e4/63/9504851dac16bfd7dccd4ddd0e1aa74d6185861c405213f26fa30df45ac0/pyobjc_framework_coremediaio-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:599dfff52ef5a7886753fef44b1226d939b1f44c02d7a9790fe40e1f792b9b81", size = 17347, upload-time = "2026-06-19T16:09:04.037Z" }, + { url = "https://files.pythonhosted.org/packages/bf/4e/acf08847ee0fec18ccc944682cba5dadf9c0c9f32e9abdea74dcf725cecd/pyobjc_framework_coremediaio-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ae6485b88b13953a6485dd3b9afe6a99b538e3fab3c72c590a943c5173348268", size = 17644, upload-time = "2026-06-19T16:09:04.943Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d4/0c08222049c302d52e65acc89c28118581609bd8a307b02619a862d4ecde/pyobjc_framework_coremediaio-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:0cb44d84412d737f103cb06b5b400e376ba8869da0d31326e5d040861831342e", size = 17365, upload-time = "2026-06-19T16:09:05.792Z" }, + { url = "https://files.pythonhosted.org/packages/07/c0/6e6de289f2126978f462b506faf5df6f13329ad71f9a4d123da34fd06469/pyobjc_framework_coremediaio-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:cb6043694fddba0889861e8321cba422fd3d90f0e95ad716826e67246d86bb96", size = 17649, upload-time = "2026-06-19T16:09:06.638Z" }, ] [[package]] name = "pyobjc-framework-coremidi" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/75/96/2d583060a71a73c8a7e6d92f2a02675621b63c1f489f2639e020fae34792/pyobjc_framework_coremidi-12.1.tar.gz", hash = "sha256:3c6f1fd03997c3b0f20ab8545126b1ce5f0cddcc1587dffacad876c161da8c54", size = 55587, upload-time = "2025-11-14T10:13:58.903Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d2/77/c51599da17f742fdcaaa381a26daadc22c79739dcf7705f8faf29ca1692a/pyobjc_framework_coremidi-12.2.1.tar.gz", hash = "sha256:d9744001102f935646997136c3d7d0562088bafa1837e5ccc439b5c8ee9e032e", size = 63469, upload-time = "2026-06-19T16:20:17.577Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/67/77/36f4a45832c17bbc676dfec19215ae741c2f3a5083134159e39834993aa6/pyobjc_framework_coremidi-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bcbe70bdd5f79999d028aee7a8b34e72ff955547c7d277357c4d67afd8a23024", size = 24284, upload-time = "2025-11-14T09:45:29.528Z" }, - { url = "https://files.pythonhosted.org/packages/76/d5/49b8720ec86f64e3dc3c804bd7e16fabb2a234a9a8b1b6753332ed343b4e/pyobjc_framework_coremidi-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:af3cdf195e8d5e30d1203889cc4107bebc6eb901aaa81bf3faf15e9ffaca0735", size = 24282, upload-time = "2025-11-14T09:45:32.288Z" }, - { url = "https://files.pythonhosted.org/packages/e3/2d/99520f6f1685e4cad816e55cbf6d85f8ce6ea908107950e2d37dc17219d8/pyobjc_framework_coremidi-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e84ffc1de59691c04201b0872e184fe55b5589f3a14876bd14460f3b5f3cd109", size = 24317, upload-time = "2025-11-14T09:45:34.92Z" }, - { url = "https://files.pythonhosted.org/packages/a9/2a/093ec8366d5f9e6c45e750310121ea572b8696518c51c4bbcf1623c01cf1/pyobjc_framework_coremidi-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:69720f38cfeea4299f31cb3e15d07e5d43e55127605f95e001794c7850c1c637", size = 24333, upload-time = "2025-11-14T09:45:37.577Z" }, - { url = "https://files.pythonhosted.org/packages/0e/cf/f03a0b44d1cfcfa9837cdfd6385c1e7d1e42301076d376329a44b6cbec03/pyobjc_framework_coremidi-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:06e5bce0a28bac21f09bcfedda46d93b2152c138764380314d99f2370a8c00f2", size = 24493, upload-time = "2025-11-14T09:45:40.591Z" }, - { url = "https://files.pythonhosted.org/packages/29/4d/7d8d6ee42a2c6ebc89fb78fa6a2924de255f76ba7907656c26cc5847fc92/pyobjc_framework_coremidi-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b49442cf533923952f56049be407edbe2ab2ece04ae1c94ca1e28d500f9f5754", size = 24371, upload-time = "2025-11-14T09:45:43.514Z" }, - { url = "https://files.pythonhosted.org/packages/6c/e5/56239a9e05fe62ad7cf00844c9a89db249281dc6b72238dfdcaa783896b0/pyobjc_framework_coremidi-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:194bc4da148ace8b71117c227562cad39a2708d296f569839f56d83e8801b25b", size = 24536, upload-time = "2025-11-14T09:45:46.504Z" }, + { url = "https://files.pythonhosted.org/packages/f8/33/3d8e6fc760f887fe5222ee5ec63f7e34d8c0610158cf03f55a8d64759ed6/pyobjc_framework_coremidi-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:179be5fc429e9f64fb1cf9be9406bb764d6852afcdce777dadbca10fd8488451", size = 24491, upload-time = "2026-06-19T16:09:07.471Z" }, + { url = "https://files.pythonhosted.org/packages/0d/5c/0fd7111ed45d665e9d82d8e03462c2c4e26115a16b1834c3045e1ebaba3e/pyobjc_framework_coremidi-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f8c42d469332e3e1811f69fb0dfbcc9ff5d98e3ee08348ccbcec90bd1bcd8288", size = 24491, upload-time = "2026-06-19T16:09:08.474Z" }, + { url = "https://files.pythonhosted.org/packages/eb/02/b6d415a946c0b48d555aa96b2e8b11f45965f1f21524d91a40821e60df14/pyobjc_framework_coremidi-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab57adc4104135ae0373781b055ceabd9ac45db48e171502fcce48317b5c8d51", size = 24581, upload-time = "2026-06-19T16:09:09.451Z" }, + { url = "https://files.pythonhosted.org/packages/ec/f9/45acec2ddfaed67da0d1a9fedc954162b899ab4f8612593bbdfdc223345f/pyobjc_framework_coremidi-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b6596d9062221097164f8adb445530aca398f6abee0111178cefe37474ab8d2c", size = 24605, upload-time = "2026-06-19T16:09:10.507Z" }, + { url = "https://files.pythonhosted.org/packages/ed/05/557f0e27db08880e6f403b192dd5e99c6cc68717042b4f28208c6f80901e/pyobjc_framework_coremidi-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:25ad1c67e2e50b67d0569f832ae8531c2a9261a6740a1b88d311fda5211f6130", size = 24757, upload-time = "2026-06-19T16:09:11.436Z" }, + { url = "https://files.pythonhosted.org/packages/5c/17/3dabcfe3db51d1ad6b91f8b4b0a5dceaa38bea61d5434c75763c28aad936/pyobjc_framework_coremidi-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:05b0da253ee7ebd2bbcc40f2893cd6f78ac50c5da07d256faecc7ef0746fbb7f", size = 24646, upload-time = "2026-06-19T16:09:12.226Z" }, + { url = "https://files.pythonhosted.org/packages/7d/71/8624f3598d7539da94fd00a510d6c4273ca6c6a88c613d66fbae2f68bb96/pyobjc_framework_coremidi-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:7e76daa635b57659efc30ebb9cffa6e86beacda76a690120183961fcc5cdfada", size = 24810, upload-time = "2026-06-19T16:09:13.648Z" }, + { url = "https://files.pythonhosted.org/packages/69/26/e3bfb5666ae2c69d5bfdaa7115299f6a0004ac900d020ffb2bb52192c29a/pyobjc_framework_coremidi-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:98fdb11eff3ce6ebc5841b91fadbfcfb5d41dd49bc0f1b4b0f55918e5f6a9959", size = 24638, upload-time = "2026-06-19T16:09:14.704Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b7/887887cdff0e27274bfa1d7b7e99ffe4233ea85cacb3a7f5f00bc709512f/pyobjc_framework_coremidi-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:7cc1e3ac498ddd3cb2424ae003fdb06911c9aaea2e5f4544b14497cb512812f9", size = 24799, upload-time = "2026-06-19T16:09:15.517Z" }, ] [[package]] name = "pyobjc-framework-coreml" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/2d/baa9ea02cbb1c200683cb7273b69b4bee5070e86f2060b77e6a27c2a9d7e/pyobjc_framework_coreml-12.1.tar.gz", hash = "sha256:0d1a4216891a18775c9e0170d908714c18e4f53f9dc79fb0f5263b2aa81609ba", size = 40465, upload-time = "2025-11-14T10:14:02.265Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/1e/7d2db3e4468eb04cc92264be83113d86eea4f96302742437de695a445d6d/pyobjc_framework_coreml-12.2.1.tar.gz", hash = "sha256:ef3c2b6a160891b44173235603d10174929656b9c206d6f2f443fe2aa903c2cb", size = 49272, upload-time = "2026-06-19T16:20:18.459Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/f6/e8afa7143d541f6f0b9ac4b3820098a1b872bceba9210ae1bf4b5b4d445d/pyobjc_framework_coreml-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:df4e9b4f97063148cc481f72e2fbe3cc53b9464d722752aa658d7c0aec9f02fd", size = 11334, upload-time = "2025-11-14T09:45:48.42Z" }, - { url = "https://files.pythonhosted.org/packages/34/0f/f55369da4a33cfe1db38a3512aac4487602783d3a1d572d2c8c4ccce6abc/pyobjc_framework_coreml-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:16dafcfb123f022e62f47a590a7eccf7d0cb5957a77fd5f062b5ee751cb5a423", size = 11331, upload-time = "2025-11-14T09:45:50.445Z" }, - { url = "https://files.pythonhosted.org/packages/bb/39/4defef0deb25c5d7e3b7826d301e71ac5b54ef901b7dac4db1adc00f172d/pyobjc_framework_coreml-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:10dc8e8db53d7631ebc712cad146e3a9a9a443f4e1a037e844149a24c3c42669", size = 11356, upload-time = "2025-11-14T09:45:52.271Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3f/3749964aa3583f8c30d9996f0d15541120b78d307bb3070f5e47154ef38d/pyobjc_framework_coreml-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:48fa3bb4a03fa23e0e36c93936dca2969598e4102f4b441e1663f535fc99cd31", size = 11371, upload-time = "2025-11-14T09:45:54.105Z" }, - { url = "https://files.pythonhosted.org/packages/9c/c8/cf20ea91ae33f05f3b92dec648c6f44a65f86d1a64c1d6375c95b85ccb7c/pyobjc_framework_coreml-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:71de5b37e6a017e3ed16645c5d6533138f24708da5b56c35c818ae49d0253ee1", size = 11600, upload-time = "2025-11-14T09:45:55.976Z" }, - { url = "https://files.pythonhosted.org/packages/bc/5c/510ae8e3663238d32e653ed6a09ac65611dd045a7241f12633c1ab48bb9b/pyobjc_framework_coreml-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a04a96e512ecf6999aa9e1f60ad5635cb9d1cd839be470341d8d1541797baef6", size = 11418, upload-time = "2025-11-14T09:45:57.75Z" }, - { url = "https://files.pythonhosted.org/packages/d3/1a/b7367819381b07c440fa5797d2b0487e31f09aa72079a693ceab6875fa0a/pyobjc_framework_coreml-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:7762b3dd2de01565b7cf3049ce1e4c27341ba179d97016b0b7607448e1c39865", size = 11593, upload-time = "2025-11-14T09:45:59.623Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5b/caf7ddf83f1faf049a519fa5b515263953e08474543cde2f0fa8b084b05b/pyobjc_framework_coreml-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d04c58b18ce9a136ea9184f604658d50b8753ad4634638ac50e360298a3eb81", size = 11947, upload-time = "2026-06-19T16:09:16.366Z" }, + { url = "https://files.pythonhosted.org/packages/d5/93/1c5ca6b3cdb32d37b1df64d18d6e38b73ad7cf30b55ff51083093e5c8388/pyobjc_framework_coreml-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:711be8528b0cbbc49b5bd4669309f0ad0de6ed76499ffb706b0dd2f1b294293f", size = 11945, upload-time = "2026-06-19T16:09:17.464Z" }, + { url = "https://files.pythonhosted.org/packages/75/d9/d639ecf7f5730482c0be4a9a0e340a06d48fecad0976ca708cdfb4b79429/pyobjc_framework_coreml-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ca06f106c8d0e2e818212e6cfe9270bd333983ffd086be7b474fc9df34cbc5cc", size = 11973, upload-time = "2026-06-19T16:09:18.357Z" }, + { url = "https://files.pythonhosted.org/packages/02/a1/7f361206e202e84cf5695a8d1dc25810e8f58c4b9d043d10c0030a04dafe/pyobjc_framework_coreml-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4c9299321ced92439c203342183fdaa47aa8f125b6ef831c55bcc91a69e196e9", size = 11986, upload-time = "2026-06-19T16:09:19.211Z" }, + { url = "https://files.pythonhosted.org/packages/40/c4/763f7e8e8c63f4be106046dbca0cce3f33535c07dc5b079734951686dbd7/pyobjc_framework_coreml-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:1ff65a16661abcc7c2dfe90e87b821d596bc17d1fed44c3a7ba36bd60b002a99", size = 12213, upload-time = "2026-06-19T16:09:19.991Z" }, + { url = "https://files.pythonhosted.org/packages/09/77/957af9b9afcb8de21cbc3e0b58f29c28bbfa6b560643e1de0fa259a39092/pyobjc_framework_coreml-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:554b423411890d26a03628bb43f848d45656980b4cd43e8f91f84b52347f0b66", size = 12028, upload-time = "2026-06-19T16:09:20.761Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4c/10bf963e3dabd5354e64697b7ae847586169c37638da645f3af33165231a/pyobjc_framework_coreml-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6e2d71048e80c27c7c0fdd01b9ec5d309c873cb2c4da64578f13d842298a9186", size = 12207, upload-time = "2026-06-19T16:09:21.612Z" }, + { url = "https://files.pythonhosted.org/packages/21/ce/2c00c20db21112955aecc3b673da804734201a62bd9ba53e4d47b90acb83/pyobjc_framework_coreml-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:52dbdd00da500253ed8b21193ee0a4ba5c45e155fbe0d64448ba2c6666552f36", size = 12029, upload-time = "2026-06-19T16:09:22.382Z" }, + { url = "https://files.pythonhosted.org/packages/d7/53/d0401ecbbab4729b0b5a9a4eb8dd1a9bd40455ddacbae8dbc2aedb332286/pyobjc_framework_coreml-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:d1cda08f258e30f5c92925d377a35bf5cd4a244911a4fb2196505431d2462ed8", size = 12198, upload-time = "2026-06-19T16:09:23.3Z" }, ] [[package]] name = "pyobjc-framework-coremotion" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2c/eb/abef7d405670cf9c844befc2330a46ee59f6ff7bac6f199bf249561a2ca6/pyobjc_framework_coremotion-12.1.tar.gz", hash = "sha256:8e1b094d34084cc8cf07bedc0630b4ee7f32b0215011f79c9e3cd09d205a27c7", size = 33851, upload-time = "2025-11-14T10:14:05.619Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/d5/15d318ab0d12681ff5aa5c6799287480dff561e2b3a79d2befe1811b0453/pyobjc_framework_coremotion-12.2.1.tar.gz", hash = "sha256:21fd319d7313b9b03f062239f1c09e324969d5da0fe74842c92a2724381bf78e", size = 38049, upload-time = "2026-06-19T16:20:19.483Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/bc/48ca9905725779de71543522d96e2e265f486df3fd5eecfabfee775e554c/pyobjc_framework_coremotion-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0da3c3e82744cf555537d65ad796e9ad2687d26aeb458078c74896496538eace", size = 10384, upload-time = "2025-11-14T09:46:01.584Z" }, - { url = "https://files.pythonhosted.org/packages/77/fd/0d24796779e4d8187abbce5d06cfd7614496d57a68081c5ff1e978b398f9/pyobjc_framework_coremotion-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed8cb67927985d97b1dd23ab6a4a1b716fc7c409c35349816108781efdcbb5b6", size = 10382, upload-time = "2025-11-14T09:46:03.438Z" }, - { url = "https://files.pythonhosted.org/packages/bc/75/89fa4aab818aeca21ac0a60b7ceb89a9e685df0ddd3828d36a6f84a0cff0/pyobjc_framework_coremotion-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a77908ab83c422030f913a2a761d196359ab47f6d1e7c76f21de2c6c05ea2f5f", size = 10406, upload-time = "2025-11-14T09:46:05.076Z" }, - { url = "https://files.pythonhosted.org/packages/4d/dd/9a4cc56c55f7ffece2e100664503cb27b4f4265d57656d050a3af1c71d94/pyobjc_framework_coremotion-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b7b0d47b5889ca0b6e3a687bd1f83a13d3bb59c07a1c4c37dcca380ede5d6e81", size = 10423, upload-time = "2025-11-14T09:46:07.051Z" }, - { url = "https://files.pythonhosted.org/packages/0d/4d/660b47e9e0bc10ae87f85bede39e3f922b8382e0f6ac273058183d0bdc2f/pyobjc_framework_coremotion-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:531ea82945266d78e23d1f35de0cae2391e18677ed54120b90a4b9dd19f32596", size = 10570, upload-time = "2025-11-14T09:46:09.047Z" }, - { url = "https://files.pythonhosted.org/packages/21/b0/a1809fc3eea18db15d20bd2225f4d5e1cfc74f38b252e0cb1e3f2563bcfa/pyobjc_framework_coremotion-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e7ce95dfa7e33b5762e0a800d76ef9c6a34b827c700d7e80c3740b7cd05168a5", size = 10484, upload-time = "2025-11-14T09:46:10.751Z" }, - { url = "https://files.pythonhosted.org/packages/1c/c4/167729d032e27985d1a6ba5e60c8045c43b9392624e8c605a24f2e22cf14/pyobjc_framework_coremotion-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d0aedcf8157c1428c7d2df8edae159b9de226d4df719c5bac8a96b648950b63e", size = 10629, upload-time = "2025-11-14T09:46:12.782Z" }, + { url = "https://files.pythonhosted.org/packages/28/cf/ca8382e808d303d605798cb6491bb53afb4a1fb5c6bc6894f0bf9a7a232c/pyobjc_framework_coremotion-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0cb2451d4770078955f3eb17fcefa075c1290ff0599557e4f9e0cbb2b2e3bceb", size = 10433, upload-time = "2026-06-19T16:09:24.225Z" }, + { url = "https://files.pythonhosted.org/packages/54/20/58afe54cdecca3715f3cb1cba9d340c9ff24d24b46b032bee9c21112fef8/pyobjc_framework_coremotion-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:71a33f6e33b47c132178bd7bcf8fdc0ff2e7dbc66bb0db01ec2bcc322910eb66", size = 10439, upload-time = "2026-06-19T16:09:25.249Z" }, + { url = "https://files.pythonhosted.org/packages/58/28/a83a7ca091022d8b3df7ba1af2ad98b683f9db5c9e393e200c308a30f87e/pyobjc_framework_coremotion-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:05388142cc892b95e40fb626748a4af0816b285bf33f1fb3b8a856b964581433", size = 10456, upload-time = "2026-06-19T16:09:26.059Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/3f8e52e418be615aeb170541f873af739e756aab7d3610d707bfceb48698/pyobjc_framework_coremotion-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d18a90639934e0d4379cc6e632095cda1287c03054be73d79cb3eb680909445a", size = 10470, upload-time = "2026-06-19T16:09:26.853Z" }, + { url = "https://files.pythonhosted.org/packages/b6/0b/6c69a4eae1e6ef0de4afae1cbef99e45348b1dbc067648872da9a6903b44/pyobjc_framework_coremotion-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:848aa8c0d0af9beb909f28f7aa6555b62646ff34a73873f8820d330c3c3e5578", size = 10616, upload-time = "2026-06-19T16:09:27.622Z" }, + { url = "https://files.pythonhosted.org/packages/0f/10/64d30e500aa92ed5cf97f63ce742615eea312dd1d790959c5f609a4492a8/pyobjc_framework_coremotion-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0caa15ddd0a25ed2cff27b28918ec690e1031ad62db8af5c4b0ec1957448aa10", size = 10534, upload-time = "2026-06-19T16:09:28.432Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b3/73b5e6bd8319d506a8effe250349c346f7e429bbd55d44f84fbcdcf05959/pyobjc_framework_coremotion-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:7596619951228890658833c6a7bbd50b7287b501feae31d774cf7e699e9f8f9e", size = 10687, upload-time = "2026-06-19T16:09:29.443Z" }, + { url = "https://files.pythonhosted.org/packages/b4/d2/f84d404430b905c6630db22f6cc768940e602db7ac7f4297a429546290a0/pyobjc_framework_coremotion-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:6ccbc65ed88f23f0824dac41a321558dedd8ddacb3b6c967d19801c5418a7811", size = 10523, upload-time = "2026-06-19T16:09:30.273Z" }, + { url = "https://files.pythonhosted.org/packages/ab/b1/05c42f264e2d01d483ff80b936f99ebaaf195ae081b6369a597f2db82a74/pyobjc_framework_coremotion-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:0acd45e02a10a8e7f3fc6b2829740c9c18fc4c2adddf263e117a97c46105222a", size = 10684, upload-time = "2026-06-19T16:09:31.189Z" }, ] [[package]] name = "pyobjc-framework-coreservices" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-fsevents" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/b3/52338a3ff41713f7d7bccaf63bef4ba4a8f2ce0c7eaff39a3629d022a79a/pyobjc_framework_coreservices-12.1.tar.gz", hash = "sha256:fc6a9f18fc6da64c166fe95f2defeb7ac8a9836b3b03bb6a891d36035260dbaa", size = 366150, upload-time = "2025-11-14T10:14:28.133Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/dd/d87ebbb99b1c277a519e1b7e0fb5efaf2e68833322d9c1eca5ecd79ed8b9/pyobjc_framework_coreservices-12.2.1.tar.gz", hash = "sha256:b4f052acd7346afa6f5441d32a19faaf080c3441cfaafad40c9b9a485b664554", size = 399935, upload-time = "2026-06-19T16:20:20.469Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/11/b0/a4e97059aba12d0940cfac08f440c061e1b9e9df8da7a38d5aafdb6fd7b5/pyobjc_framework_coreservices-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:38856a89ccab766a03270c415a6bf5ea0f87134134fe4c122af9894d50162d77", size = 30195, upload-time = "2025-11-14T09:46:16.108Z" }, - { url = "https://files.pythonhosted.org/packages/55/56/c905deb5ab6f7f758faac3f2cbc6f62fde89f8364837b626801bba0975c3/pyobjc_framework_coreservices-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b6ef07bcf99e941395491f1efcf46e99e5fb83eb6bfa12ae5371135d83f731e1", size = 30196, upload-time = "2025-11-14T09:46:19.356Z" }, - { url = "https://files.pythonhosted.org/packages/61/6c/33984caaf497fc5a6f86350d7ca4fac8abeb2bc33203edc96955a21e8c05/pyobjc_framework_coreservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8751dc2edcb7cfa248bf8a274c4d6493e8d53ef28a843827a4fc9a0a8b04b8be", size = 30206, upload-time = "2025-11-14T09:46:22.732Z" }, - { url = "https://files.pythonhosted.org/packages/a7/6f/4a6eb2f2bbdbf66a1b35f272d8504ce6f098947f9343df474f0d15a2b507/pyobjc_framework_coreservices-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:96574fb24d2b8b507901ef7be7fcb70b7f49e110bd050a411b90874cc18c7c7b", size = 30226, upload-time = "2025-11-14T09:46:25.565Z" }, - { url = "https://files.pythonhosted.org/packages/60/6e/78a831834dc7f84a2d61efb47d212239f3ae3d16aa5512f1265a8f6c0162/pyobjc_framework_coreservices-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:227fb4144a87c6c97a5f737fb0c666293b33e54f0ffb500f2c420da6c110ba2d", size = 30229, upload-time = "2025-11-14T09:46:28.51Z" }, - { url = "https://files.pythonhosted.org/packages/d8/b6/c4100905d92f1187f74701ab520da95a235c09e94a71e5872462660ac022/pyobjc_framework_coreservices-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c650e1083fb313b9c8df4be8d582c266aa1b99c75ed5d7e45e3a91a7b8a128b2", size = 30255, upload-time = "2025-11-14T09:46:31.492Z" }, - { url = "https://files.pythonhosted.org/packages/d2/79/df730603028dbd34aa61dbe0396cc23715520195726686bb5e5832429f56/pyobjc_framework_coreservices-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:dff0cb6ccbd39ea45b01a50955d757172567de5c164f6e8e241bf4e7639b0946", size = 30269, upload-time = "2025-11-14T09:46:34.469Z" }, + { url = "https://files.pythonhosted.org/packages/1f/99/fc99199f7579d32f1b262f1969b84d853f87b64e6bc5763dc62a9df84772/pyobjc_framework_coreservices-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:af82be82d59b4f88eecd7e35fba0b54336e902ee1484aa90f49e66e6dacc4b84", size = 30327, upload-time = "2026-06-19T16:09:32.017Z" }, + { url = "https://files.pythonhosted.org/packages/db/d8/0020ace0ab37bc81f865eaa6f5b2f7a27f5389a6179d6e231e647dd17f37/pyobjc_framework_coreservices-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ceec41060d4027c935069f109f85de5e5025cf9b78e5f141b9c29f797396a144", size = 30333, upload-time = "2026-06-19T16:09:33.075Z" }, + { url = "https://files.pythonhosted.org/packages/5c/46/21129f921528551b8aec853ebe9f21edad01bd7f4501743d0601b7ac7ccb/pyobjc_framework_coreservices-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cf31f1d6e477414b7c5bd831df5864744a558eec5b055155fe6929ef1070d371", size = 30342, upload-time = "2026-06-19T16:09:33.961Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ad/aa9aea470acd79e69cac8dc561844236db307d6e93ad0e0140beed646f4d/pyobjc_framework_coreservices-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b911d08cf4e9fab5fd0992dc525bec02cc925102a42b2bdd4b77dd6a7b8ce70d", size = 30359, upload-time = "2026-06-19T16:09:34.884Z" }, + { url = "https://files.pythonhosted.org/packages/fd/0b/67db0cd511e487a847e6ddf1a9e78baf5db210b848e48f782ad15e302b1f/pyobjc_framework_coreservices-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:aad43d280da1ba8f7a815de1935957f3f61b80eda15c5cac35cdf6a1b20f69ee", size = 30369, upload-time = "2026-06-19T16:09:35.701Z" }, + { url = "https://files.pythonhosted.org/packages/18/ff/197681804de3504b66971f99d140e3284a028b3393e68130df928410804d/pyobjc_framework_coreservices-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:cbb369619aefb556f0a0d06fd667ebb2ac4235fb2bb453454dda1571801e259c", size = 30382, upload-time = "2026-06-19T16:09:36.718Z" }, + { url = "https://files.pythonhosted.org/packages/91/36/b07713ead8fcafd7279d6893822374f99690982dc47f23fe3d3b3d5628ed/pyobjc_framework_coreservices-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:38fdc91789137af9e22a7bcea0ecdcb9d1d35ebb1c66c58af91973bf9727dcfc", size = 30402, upload-time = "2026-06-19T16:09:37.641Z" }, + { url = "https://files.pythonhosted.org/packages/f2/bd/c0b88cba4b0cda2390d299cbfe3352b81e7861b46a0a5cb4471081fb1796/pyobjc_framework_coreservices-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:628aa9ed4c1da2bc2d155b29609de2ac6197bbe41d7f8eb0a594fb2d5d6570dd", size = 30412, upload-time = "2026-06-19T16:09:38.518Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ce/222b2ec4cfc803e47fe170d04f89d50a397020ca7436582a2d880779174e/pyobjc_framework_coreservices-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:d87eb0f15224925d860c8332cf579b998db850cf0d4f7a611872915bf9271bfc", size = 30423, upload-time = "2026-06-19T16:09:39.593Z" }, ] [[package]] name = "pyobjc-framework-corespotlight" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/99/d0/88ca73b0cf23847af463334989dd8f98e44f801b811e7e1d8a5627ec20b4/pyobjc_framework_corespotlight-12.1.tar.gz", hash = "sha256:57add47380cd0bbb9793f50a4a4b435a90d4ebd2a33698e058cb353ddfb0d068", size = 38002, upload-time = "2025-11-14T10:14:31.948Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/89479d419712deef7e245a8284edfebc418297a17e3066a990ae88c3bf3d/pyobjc_framework_corespotlight-12.2.1.tar.gz", hash = "sha256:85d6080ff2f3a02593650eeb799d667be66383e8cd947abfec5e8ef8fd10d18b", size = 45685, upload-time = "2026-06-19T16:20:21.45Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/e1/d9efae11e4fde82c38a747b48c72b00cfeefe1f7f2aa7bc7b60ffdeac6be/pyobjc_framework_corespotlight-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9299ebabe2031433c384aec52074c52f6258435152d3bdbbed0ed68e37ad1f45", size = 9977, upload-time = "2025-11-14T09:46:39.893Z" }, - { url = "https://files.pythonhosted.org/packages/d4/37/1e7bacb9307a8df52234923e054b7303783e7a48a4637d44ce390b015921/pyobjc_framework_corespotlight-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:404a1e362fe19f0dff477edc1665d8ad90aada928246802da777399f7c06b22e", size = 9976, upload-time = "2025-11-14T09:46:45.221Z" }, - { url = "https://files.pythonhosted.org/packages/f6/3b/d3031eddff8029859de6d92b1f741625b1c233748889141a6a5a89b96f0e/pyobjc_framework_corespotlight-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bfcea64ab3250e2886d202b8731be3817b5ac0c8c9f43e77d0d5a0b6602e71a7", size = 9996, upload-time = "2025-11-14T09:46:47.157Z" }, - { url = "https://files.pythonhosted.org/packages/7b/ed/419ae27bdd17701404301ede1969daadeef6ef6dd8b4a8110a90a1d77df1/pyobjc_framework_corespotlight-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:37003bfea415ff21859d44403c3a13ac55f90b6dca92c69b81b61d96cee0c7be", size = 10012, upload-time = "2025-11-14T09:46:48.826Z" }, - { url = "https://files.pythonhosted.org/packages/a8/84/ebe1acb365958604465f83710772c1a08854f472896e607f7eedb5944e1b/pyobjc_framework_corespotlight-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ede26027cfa577e6748b7dd0615e8a1bb379e48ad2324489b2c8d242cdf6fce8", size = 10152, upload-time = "2025-11-14T09:46:51.025Z" }, - { url = "https://files.pythonhosted.org/packages/21/cf/11cafe42bc7209bd96d71323beb60d6d1cdb069eb651f120323b3ef9c8d4/pyobjc_framework_corespotlight-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:986ac40755e15aa3a562aac687b22c882de2b4b0fa58fbd419cc3487a0df1507", size = 10069, upload-time = "2025-11-14T09:46:53Z" }, - { url = "https://files.pythonhosted.org/packages/10/95/a64f847413834ced69c29d63b60aeb084174d81d57f748475be03fbfcdc2/pyobjc_framework_corespotlight-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0041b9a10d7f6c4a8d05f2ed281194a3d8bc5b2d0ceca4f4a9d9a8ce064fd68e", size = 10215, upload-time = "2025-11-14T09:46:54.703Z" }, + { url = "https://files.pythonhosted.org/packages/3c/19/7d0e3d3b7df6afb1f6db3801f759e97b7be1f2cc30ba51364482187055ef/pyobjc_framework_corespotlight-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e6109b3997a32ba271df61135f931ef2d791c54bdfbdb25c6e05c8ed5f6bf2e5", size = 10010, upload-time = "2026-06-19T16:09:40.509Z" }, + { url = "https://files.pythonhosted.org/packages/26/2d/b9806f21d2bf98429bd7a4cb49f68352459d3333fe58fa30a24bf2a00f09/pyobjc_framework_corespotlight-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d5f9202b197d3863ac163a7b5eb3def8cea1bd58a7b7c930d53824300044be0e", size = 10004, upload-time = "2026-06-19T16:09:41.432Z" }, + { url = "https://files.pythonhosted.org/packages/1f/20/d6d9d96b4dba80f4030f6a94a322ef8dc384cf26e7a64f63b44d046f3b3e/pyobjc_framework_corespotlight-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:09b40a5982ca77c4b7119cb69765de4f7f565721bd3bfc724cfa9703abf15dc3", size = 10030, upload-time = "2026-06-19T16:09:42.158Z" }, + { url = "https://files.pythonhosted.org/packages/1f/0d/8390925cdd196a209ec2e11b0924060d6a0b12256084bf193107792e45ee/pyobjc_framework_corespotlight-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:76b6315e724f81d62af09673b78ae4e3370b58b8ed639a1a9d2dcb2621a044b2", size = 10043, upload-time = "2026-06-19T16:09:43.011Z" }, + { url = "https://files.pythonhosted.org/packages/11/2a/3b85888fefe9dd61c6c3ea264950b80ac17ff9a740cfffa4bf796e3174b1/pyobjc_framework_corespotlight-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:3591127a7958299406f592027de570d5c8b6eb40a7a0667b912562a42007e67a", size = 10187, upload-time = "2026-06-19T16:09:43.829Z" }, + { url = "https://files.pythonhosted.org/packages/0f/fb/783c1776f81a491065e42622c7d2a80982ba8e852c9bfc804e51e57b7c44/pyobjc_framework_corespotlight-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:154fe491f217eb0e107ba37403b1856263f288ed801b10ede193df427cf15c57", size = 10102, upload-time = "2026-06-19T16:09:45.324Z" }, + { url = "https://files.pythonhosted.org/packages/cf/0e/dd272a9d0b6202cc039584e2e50a887237a8a06a7575257e426cad517e96/pyobjc_framework_corespotlight-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0da2cb26b9449cfe7f629c4f241319b2eaf083643e63125bda38135943132b4d", size = 10245, upload-time = "2026-06-19T16:09:46.149Z" }, + { url = "https://files.pythonhosted.org/packages/d7/7e/a51ca1c2499e1905cd2f094e8d3fa295bf64e638bd15c8c0b782e166dde8/pyobjc_framework_corespotlight-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:53e8ab09ee1e4a2c0e20daf43a782ca381379ec5ee30224c561c62a3acd59766", size = 10098, upload-time = "2026-06-19T16:09:47.019Z" }, + { url = "https://files.pythonhosted.org/packages/ad/94/43a3f32026ddf215ff798bb5cbb5f4f251ba8f9b31759a07f10009aa51a9/pyobjc_framework_corespotlight-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:98ba3d15bcb028bd44425f1910c29c92245b69218cd340e8e35eaef535d609a9", size = 10240, upload-time = "2026-06-19T16:09:47.969Z" }, ] [[package]] name = "pyobjc-framework-coretext" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-quartz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/29/da/682c9c92a39f713bd3c56e7375fa8f1b10ad558ecb075258ab6f1cdd4a6d/pyobjc_framework_coretext-12.1.tar.gz", hash = "sha256:e0adb717738fae395dc645c9e8a10bb5f6a4277e73cba8fa2a57f3b518e71da5", size = 90124, upload-time = "2025-11-14T10:14:38.596Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/9c/4c7f452059dc1d3845b8e627b9113c247a997b9b07518e848c2ab7ff3149/pyobjc_framework_coretext-12.2.1.tar.gz", hash = "sha256:af740e784d7c592c34025ec7165f4f6c1a69b5a2d9075f06e41e4f77c212aed2", size = 97349, upload-time = "2026-06-19T16:20:22.508Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/1c/ddecc72a672d681476c668bcedcfb8ade16383c028eac566ac7458fb91ef/pyobjc_framework_coretext-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1c8315dcef6699c2953461d97117fe81402f7c29cff36d2950dacce028a362fd", size = 29987, upload-time = "2025-11-14T09:46:58.028Z" }, - { url = "https://files.pythonhosted.org/packages/f0/81/7b8efc41e743adfa2d74b92dec263c91bcebfb188d2a8f5eea1886a195ff/pyobjc_framework_coretext-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4f6742ba5b0bb7629c345e99eff928fbfd9e9d3d667421ac1a2a43bdb7ba9833", size = 29990, upload-time = "2025-11-14T09:47:01.206Z" }, - { url = "https://files.pythonhosted.org/packages/cd/0f/ddf45bf0e3ba4fbdc7772de4728fd97ffc34a0b5a15e1ab1115b202fe4ae/pyobjc_framework_coretext-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d246fa654bdbf43bae3969887d58f0b336c29b795ad55a54eb76397d0e62b93c", size = 30108, upload-time = "2025-11-14T09:47:04.228Z" }, - { url = "https://files.pythonhosted.org/packages/20/a2/a3974e3e807c68e23a9d7db66fc38ac54f7ecd2b7a9237042006699a76e1/pyobjc_framework_coretext-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7cbb2c28580e6704ce10b9a991ccd9563a22b3a75f67c36cf612544bd8b21b5f", size = 30110, upload-time = "2025-11-14T09:47:07.518Z" }, - { url = "https://files.pythonhosted.org/packages/0f/5d/85e059349e9cfbd57269a1f11f56747b3ff5799a3bcbd95485f363c623d8/pyobjc_framework_coretext-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:14100d1e39efb30f57869671fb6fce8d668f80c82e25e7930fb364866e5c0dab", size = 30697, upload-time = "2025-11-14T09:47:10.932Z" }, - { url = "https://files.pythonhosted.org/packages/ef/c3/adf9d306e9ead108167ab7a974ab7d171dbacf31c72fad63e12585f58023/pyobjc_framework_coretext-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:782a1a9617ea267c05226e9cd81a8dec529969a607fe1e037541ee1feb9524e9", size = 30095, upload-time = "2025-11-14T09:47:13.893Z" }, - { url = "https://files.pythonhosted.org/packages/bd/ca/6321295f47a47b0fca7de7e751ddc0ddc360413f4e506335fe9b0f0fb085/pyobjc_framework_coretext-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:7afe379c5a870fa3e66e6f65231c3c1732d9ccd2cd2a4904b2cd5178c9e3c562", size = 30702, upload-time = "2025-11-14T09:47:17.292Z" }, + { url = "https://files.pythonhosted.org/packages/d0/5c/8ea3ea3715147c4ccdb7fbfd36635069fa1dd637812052f9453a18d7cec5/pyobjc_framework_coretext-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ae7eb1943fb8f1acf9fefdd76fe74751d05beaf4dff1c989cf9237b117a66d6e", size = 30021, upload-time = "2026-06-19T16:09:48.947Z" }, + { url = "https://files.pythonhosted.org/packages/13/53/c262cf5052c648c48b3f7562fcce188fb78ff94e44cf1c48fdfc62fdfcce/pyobjc_framework_coretext-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:02a448675d28005fdb88fb3c585572b02871e9e5d4d08f88334ec937ea5de6e7", size = 30022, upload-time = "2026-06-19T16:09:50.37Z" }, + { url = "https://files.pythonhosted.org/packages/c5/11/c1298c2ec3b0cd19a457a1fd0da47898f894a13df5516f80dc04d1a7a4d9/pyobjc_framework_coretext-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac2ead13dfa4379a1566129d0e8a8ea778a2bcac9ac360a583360fd4f1ba39c6", size = 30123, upload-time = "2026-06-19T16:09:51.183Z" }, + { url = "https://files.pythonhosted.org/packages/05/8c/154e8f34923b24aade64a20eca2b759f8f67e109654308103080751f246f/pyobjc_framework_coretext-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5c5a3c6e2d905a17efb15572dad97ce582feeab5c3b92537015445e0e0bb46de", size = 30116, upload-time = "2026-06-19T16:09:52.219Z" }, + { url = "https://files.pythonhosted.org/packages/01/61/f53458c8f7fe74008e342946eca1fa82b777b284d4e13d8bd2e3e5724cab/pyobjc_framework_coretext-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5c979058c77df8cd3dac5fd7db4c484f9886fbe09e2687bfaf269a856f631f78", size = 30659, upload-time = "2026-06-19T16:09:53.034Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d8/d1178bb1ba3bb7a0d7a55db460aa89f2a8b232ed7eaf76cb402923cacf2d/pyobjc_framework_coretext-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:d0b3b0467a23dbc2a39d0839e7100cc98b429fb7d52a471bd65477f46bb4c9e5", size = 30100, upload-time = "2026-06-19T16:09:53.902Z" }, + { url = "https://files.pythonhosted.org/packages/12/c3/780d739909e6d8ddd9e8786fd19e8ea10ccfcc7275df987e349af0fb33b1/pyobjc_framework_coretext-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:3d4a92fa657180cd0e900b98535da4f0f4d8c76a7077730a507e50d52d2853e3", size = 30642, upload-time = "2026-06-19T16:09:54.736Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e9/867197c6cf2396b33e95d6175d7fdc6f789314859fb107560ae9b19c7b14/pyobjc_framework_coretext-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:7a17034fd0a08cf58323b7234a385ce0ec355509d414df69e3f8f88df26de1fc", size = 30098, upload-time = "2026-06-19T16:09:55.679Z" }, + { url = "https://files.pythonhosted.org/packages/10/a3/f4e6d1a38cd4db8a1275eddb287f3cdc2c01c48f80b30e89cc58cfd92156/pyobjc_framework_coretext-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:28980144af75598654f6997b2bbb427885f4f5df90aa4d840f452ba5778ab155", size = 30669, upload-time = "2026-06-19T16:09:56.521Z" }, ] [[package]] name = "pyobjc-framework-corewlan" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/88/71/739a5d023566b506b3fd3d2412983faa95a8c16226c0dcd0f67a9294a342/pyobjc_framework_corewlan-12.1.tar.gz", hash = "sha256:a9d82ec71ef61f37e1d611caf51a4203f3dbd8caf827e98128a1afaa0fd2feb5", size = 32417, upload-time = "2025-11-14T10:14:41.921Z" } +sdist = { url = "https://files.pythonhosted.org/packages/59/4c/1ff3c5042ee2e8344b47978458af62a81ccc49894039fcfdf81d3ced4ee9/pyobjc_framework_corewlan-12.2.1.tar.gz", hash = "sha256:9a7ae402a55710392570a736a2bbe15f372325f941fb7074e682f75e4866c3fe", size = 35515, upload-time = "2026-06-19T16:20:23.401Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/cb/97f07239a9e2dacc8b8db56be765527361963fb2582f531a28a0706c0e84/pyobjc_framework_corewlan-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:440db62f4ecc0c3b636fa06928d51f147b58c4cb000c0ba2dfc820ad484c2358", size = 9936, upload-time = "2025-11-14T09:47:18.994Z" }, - { url = "https://files.pythonhosted.org/packages/f5/74/4d8a52b930a276f6f9b4f3b1e07cd518cb6d923cb512e39c935e3adb0b86/pyobjc_framework_corewlan-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3e3f2614eb37dfd6860d6a0683877c2f3b909758ef78b68e5f6b7ea9c858cc51", size = 9931, upload-time = "2025-11-14T09:47:20.849Z" }, - { url = "https://files.pythonhosted.org/packages/4e/31/3e9cf2c0ac3c979062958eae7a275b602515c9c76fd30680e1ee0fea82ae/pyobjc_framework_corewlan-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5cba04c0550fc777767cd3a5471e4ed837406ab182d7d5c273bc5ce6ea237bfe", size = 9958, upload-time = "2025-11-14T09:47:22.474Z" }, - { url = "https://files.pythonhosted.org/packages/c0/a4/b691e4d1730c16f8ea2f883712054961a3e45f40e1471c0edfc30f061c07/pyobjc_framework_corewlan-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aac949646953effdd36d2d21bc0ab645e58bb25deafe86c6e600b3cdcfc2228b", size = 9968, upload-time = "2025-11-14T09:47:24.454Z" }, - { url = "https://files.pythonhosted.org/packages/88/2e/dbba1674e1629839f479c9d14b90c37ed3b5f76d3b6b3ad56af48951c45b/pyobjc_framework_corewlan-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:dae63c36affcc933c9161980e4fe7333e0c59c968174a00a75cb5f6e4ede10c6", size = 10115, upload-time = "2025-11-14T09:47:26.152Z" }, - { url = "https://files.pythonhosted.org/packages/e8/e2/e89ea1ee92de17ec53087868d0466f6fd8174488b613a46528a3642aa41d/pyobjc_framework_corewlan-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:336536ecfd503118f79c8337cc983bbf0768e3ba4ac142e0cf8db1408c644965", size = 10010, upload-time = "2025-11-14T09:47:27.827Z" }, - { url = "https://files.pythonhosted.org/packages/15/df/e695f432dbfcd0fbfa416db21471091e94e921094a795b87cb9ebea423e5/pyobjc_framework_corewlan-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:fe6373e83e12be6854f7c1f054e2f68b41847fd739aa578d3c5478bd3fd4014f", size = 10162, upload-time = "2025-11-14T09:47:29.82Z" }, + { url = "https://files.pythonhosted.org/packages/b8/92/0d582fb48e9bf722fbfe071ad98851f23520f1ff4db838d01062c16dd8b6/pyobjc_framework_corewlan-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:61a0cf8f5c260603a4dfa2937e3a2e53a4cf6a9e15694be780ea8cc4445b8d53", size = 9993, upload-time = "2026-06-19T16:09:57.357Z" }, + { url = "https://files.pythonhosted.org/packages/a2/66/7cc21bf2488373081ee8cfbeeaf7b371946bbbd8b6eb290d4e50d2f13e31/pyobjc_framework_corewlan-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2bd1d9aca137990eb8120725962091eb03e5604c8d3431915127826790356ec3", size = 9996, upload-time = "2026-06-19T16:09:58.296Z" }, + { url = "https://files.pythonhosted.org/packages/83/ff/7f8b6da97b41155cab6bbf3a6d611f9c0ad172fd790bc912cb76146a4557/pyobjc_framework_corewlan-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a7a980cd8eb5f879923afbe92bf088e585968b3cd3448b9f4a16d5158b59e6ec", size = 10014, upload-time = "2026-06-19T16:09:59.047Z" }, + { url = "https://files.pythonhosted.org/packages/2f/ad/1624d1d8d238cd7aa34b29154cd3d2ab7176c25aa84cbec9b85256de339e/pyobjc_framework_corewlan-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6c9597a5d8b9c7c9b01c28fc6fc6ccc4ec7ea2de83296d5f3f950fbd9c39b448", size = 10026, upload-time = "2026-06-19T16:09:59.849Z" }, + { url = "https://files.pythonhosted.org/packages/ac/a9/f595af68e8975cef88088d963837eb37fb70aa8d1d42f3e42a62cf6c5e59/pyobjc_framework_corewlan-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:8d91fd2396ddf71c8df01e36fc9b49a87a63de77220b39eb858f955cc2ebd593", size = 10174, upload-time = "2026-06-19T16:10:00.679Z" }, + { url = "https://files.pythonhosted.org/packages/b7/6b/35dbc6e121fcda3d86db0722136cd48a93acf58d1b4c8058879f9f300b68/pyobjc_framework_corewlan-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e4ac75fcb3a8533c6e65d21871aef8ac52db4e322d038bfc38183667bae6b8ec", size = 10066, upload-time = "2026-06-19T16:10:01.622Z" }, + { url = "https://files.pythonhosted.org/packages/2e/45/8688ff8141220b64242e03f0619512593d2c3858e7bd852fbad6b14ba3ec/pyobjc_framework_corewlan-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:7be48cb7492740d33c3cdfd3e42e527ec2d8feb9ff67f082471e4b2db636e140", size = 10218, upload-time = "2026-06-19T16:10:02.404Z" }, + { url = "https://files.pythonhosted.org/packages/62/4e/6d8b1530e5735482dc0e9f0cdf0d9371160313ca9f25a16cecf79d885ddc/pyobjc_framework_corewlan-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:ea678d0a91bc5db8a3b80cebf016631883e7e8b0b2ac8b9cdddc97adc7031fa5", size = 10063, upload-time = "2026-06-19T16:10:03.172Z" }, + { url = "https://files.pythonhosted.org/packages/be/5e/de7068bdfde55839959479ec69b2188867b71979469a894c66a309fededc/pyobjc_framework_corewlan-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:85016f24b0a0e4ea538f74203e22371aecde961d8f849883096576245afb94c4", size = 10216, upload-time = "2026-06-19T16:10:04.575Z" }, ] [[package]] name = "pyobjc-framework-cryptotokenkit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6b/7c/d03ff4f74054578577296f33bc669fce16c7827eb1a553bb372b5aab30ca/pyobjc_framework_cryptotokenkit-12.1.tar.gz", hash = "sha256:c95116b4b7a41bf5b54aff823a4ef6f4d9da4d0441996d6d2c115026a42d82f5", size = 32716, upload-time = "2025-11-14T10:14:45.024Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/0f/697f89ccc2b4f6186b126d29d33def5d699ded72746c6c963aae2f8f4107/pyobjc_framework_cryptotokenkit-12.2.1.tar.gz", hash = "sha256:f5ad2a333ff4ba77d2ba901257836b13c1c93e62342dc092fe57dd67a97ceee7", size = 38273, upload-time = "2026-06-19T16:20:24.319Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b2/013410837cebf67e40b470cd8ffc524bd85f0ff72b62021ddf7b6e32f2b2/pyobjc_framework_cryptotokenkit-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cfdefb1d9b1bf2223055378ee84ad40b771b1a0ba02258fbf06be54d6b30a689", size = 12597, upload-time = "2025-11-14T09:47:31.743Z" }, - { url = "https://files.pythonhosted.org/packages/2c/90/1623b60d6189db08f642777374fd32287b06932c51dfeb1e9ed5bbf67f35/pyobjc_framework_cryptotokenkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d84b75569054fa0886e3e341c00d7179d5fe287e6d1509630dd698ee60ec5af1", size = 12598, upload-time = "2025-11-14T09:47:33.798Z" }, - { url = "https://files.pythonhosted.org/packages/0f/c7/aecba253cf21303b2c9f3ce03fc0e987523609d7839ea8e0a688ae816c96/pyobjc_framework_cryptotokenkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ef51a86c1d0125fabdfad0b3efa51098fb03660d8dad2787d82e8b71c9f189de", size = 12633, upload-time = "2025-11-14T09:47:35.707Z" }, - { url = "https://files.pythonhosted.org/packages/15/8d/3e24abc92a8ee8ee11386d4d9dfb2d6961d10814474053a8ebccfaff0d97/pyobjc_framework_cryptotokenkit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e65a8e4558e6cf1e46a9b4a52fcbf7b2ddd17958d675e9047d8a9f131d0a4d33", size = 12650, upload-time = "2025-11-14T09:47:37.633Z" }, - { url = "https://files.pythonhosted.org/packages/e9/eb/418afc27429922e73a05bd22198c71e1f6b3badebd73cad208eb9e922f64/pyobjc_framework_cryptotokenkit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:cc9aa75e418376e92b1540d1edfa0c8097a027a1a241717983d0223cdad8e9ca", size = 12834, upload-time = "2025-11-14T09:47:40.27Z" }, - { url = "https://files.pythonhosted.org/packages/6d/cc/32c8e34c6c54e487b993eaabe70d997096fcc1d82176207f967858f2987b/pyobjc_framework_cryptotokenkit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:94fa4b3903a1a39fe1d5874a5ae5b67471f488925c485a7e9c3575fbf9eba43e", size = 12632, upload-time = "2025-11-14T09:47:42.195Z" }, - { url = "https://files.pythonhosted.org/packages/a9/7e/57c569f4f71dfcb65b049fbb0aace19da0ed756eef7f440950098f8de498/pyobjc_framework_cryptotokenkit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:05d40859a40ba4ed3dd8befabefc02aa224336c660b2f33ebf14d5397a30ffb3", size = 12839, upload-time = "2025-11-14T09:47:44.133Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9d/b7ccac8432b692d4469e98bbee0f8174e364a341940abe352ca7b81e487e/pyobjc_framework_cryptotokenkit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c49396022718616bdbf7939186095db3073ed8559eb84c52c66f21e332191265", size = 12688, upload-time = "2026-06-19T16:10:05.596Z" }, + { url = "https://files.pythonhosted.org/packages/2c/a0/4bb5e91f1ae45fa02883769bcdc54546729ec8931d2392a4db0e02273d20/pyobjc_framework_cryptotokenkit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60ab58babe44175a17aca22f49a2a4d18c24ec0987b1cbf68b5cd1a627e26830", size = 12689, upload-time = "2026-06-19T16:10:06.724Z" }, + { url = "https://files.pythonhosted.org/packages/88/70/cbae3352cd1e68373e65b3cd110aadf3839d2b64c75e639f37b6b954b0c7/pyobjc_framework_cryptotokenkit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bd2084fc176f32f5900a17da4d3b05c9563e43521c5df3c8563caef1ebb9f194", size = 12727, upload-time = "2026-06-19T16:10:07.62Z" }, + { url = "https://files.pythonhosted.org/packages/71/fe/211725952ed459efd0e29e40de11277e1102fd9280f6cf15270de939234f/pyobjc_framework_cryptotokenkit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ad53981b7f6d0f8114579c7b93b46fd8ead341663df9ccd845f79633f66b4c79", size = 12740, upload-time = "2026-06-19T16:10:08.466Z" }, + { url = "https://files.pythonhosted.org/packages/2a/03/a30266392021c8f0f4b8ba64ee3839e051448397d413bcf8ec4e3ba314a2/pyobjc_framework_cryptotokenkit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:7177828df64d96737d523a5b2ec28d5d5b171dcc1045c486b1e460234519244f", size = 12926, upload-time = "2026-06-19T16:10:09.798Z" }, + { url = "https://files.pythonhosted.org/packages/3e/79/47e48de94096025f06650286c2ce77c8d360d07075dfc0b10e9b5f0eec76/pyobjc_framework_cryptotokenkit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:91fe788acab20a8066f05062410193952cb249d0d0acbbe675f08caf6f4993f7", size = 12718, upload-time = "2026-06-19T16:10:10.79Z" }, + { url = "https://files.pythonhosted.org/packages/c0/31/41c61fc4f085e94084d19b831057eaec59a14513c02ec313cd0a75fe8b42/pyobjc_framework_cryptotokenkit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:b5db6a890938f049c977d6a62f4db167c80a3146f753e98e67796ce6a49c5809", size = 12920, upload-time = "2026-06-19T16:10:11.844Z" }, + { url = "https://files.pythonhosted.org/packages/d6/c1/7f04f571f3ba8e240e13155179d021b0c38432189ef9b07e26d751951244/pyobjc_framework_cryptotokenkit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:2e4306ffe83327219910d72e3c2524d4a6e4a02d64f39bf7b31a3613903b5e70", size = 12705, upload-time = "2026-06-19T16:10:12.662Z" }, + { url = "https://files.pythonhosted.org/packages/00/87/a2e5f0cc0407e13ae2468eb8b555ccc6e2cd2643e917300399f620899c4a/pyobjc_framework_cryptotokenkit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:8914647c0556f61a0330b8becf5e42a5c23107f8fbf6acb10f51c0d0b68f3e6f", size = 12916, upload-time = "2026-06-19T16:10:13.599Z" }, ] [[package]] name = "pyobjc-framework-datadetection" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/db/97/9b03832695ec4d3008e6150ddfdc581b0fda559d9709a98b62815581259a/pyobjc_framework_datadetection-12.1.tar.gz", hash = "sha256:95539e46d3bc970ce890aa4a97515db10b2690597c5dd362996794572e5d5de0", size = 12323, upload-time = "2025-11-14T10:14:46.769Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/f2/8c2ab85d3dc8022450af30d58b225bfd3e01693f50d383c80e7a905bcd81/pyobjc_framework_datadetection-12.2.1.tar.gz", hash = "sha256:b1059f9bcfab5a96606dfdde663f41dd8c23a33f8bc8c00371d68796476981cc", size = 12679, upload-time = "2026-06-19T16:20:25.331Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/1c/5d2f941501e84da8fef8ef3fd378b5c083f063f083f97dd3e8a07f0404b3/pyobjc_framework_datadetection-12.1-py2.py3-none-any.whl", hash = "sha256:4dc8e1d386d655b44b2681a4a2341fb2fc9addbf3dda14cb1553cd22be6a5387", size = 3497, upload-time = "2025-11-14T09:47:45.826Z" }, + { url = "https://files.pythonhosted.org/packages/59/fe/4eafd52ca2dedd34b564d7f4cc41da8ef8c071b3fb30e9e74faca1c77a3d/pyobjc_framework_datadetection-12.2.1-py2.py3-none-any.whl", hash = "sha256:c18b746a420e33d13689cbe64635249c492d3b58044089c5c8d6366b7bb46ed4", size = 3547, upload-time = "2026-06-19T16:10:14.839Z" }, ] [[package]] name = "pyobjc-framework-devicecheck" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/af/c676107c40d51f55d0a42043865d7246db821d01241b518ea1d3b3ef1394/pyobjc_framework_devicecheck-12.1.tar.gz", hash = "sha256:567e85fc1f567b3fe64ac1cdc323d989509331f64ee54fbcbde2001aec5adbdb", size = 12885, upload-time = "2025-11-14T10:14:48.804Z" } +sdist = { url = "https://files.pythonhosted.org/packages/63/c6/9784a80bf3bbd45b4982d9349280938bf70a0b7e15bccc8302ebf324c379/pyobjc_framework_devicecheck-12.2.1.tar.gz", hash = "sha256:f67a745b89cd2f3e307cabee018a509aff0561e3f747eb4dbfe84498f7f2ca90", size = 13321, upload-time = "2026-06-19T16:20:26.05Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/d8/1f1b13fa4775b6474c9ad0f4b823953eaeb6c11bd6f03fa8479429b36577/pyobjc_framework_devicecheck-12.1-py2.py3-none-any.whl", hash = "sha256:ffd58148bdef4a1ee8548b243861b7d97a686e73808ca0efac5bef3c430e4a15", size = 3684, upload-time = "2025-11-14T09:47:47.25Z" }, + { url = "https://files.pythonhosted.org/packages/bb/d5/79ee42be90a01fb0c6ae80b96bdc28160ca0fb415595438f503efe2502c0/pyobjc_framework_devicecheck-12.2.1-py2.py3-none-any.whl", hash = "sha256:085c91137b1583bcd62787a2788adc7a51b24d8c61fb7848fb607ce3bc49553f", size = 3708, upload-time = "2026-06-19T16:10:15.92Z" }, ] [[package]] name = "pyobjc-framework-devicediscoveryextension" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/91/b0/e6e2ed6a7f4b689746818000a003ff7ab9c10945df66398ae8d323ae9579/pyobjc_framework_devicediscoveryextension-12.1.tar.gz", hash = "sha256:60e12445fad97ff1f83472255c943685a8f3a9d95b3126d887cfe769b7261044", size = 14718, upload-time = "2025-11-14T10:14:50.723Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/af/e5794d11e3e485c194f26563d04bcd4d729335ac221547e6d93109dd070c/pyobjc_framework_devicediscoveryextension-12.2.1.tar.gz", hash = "sha256:ccec236e790304c0bb880035b7f14738346c90d18cc4cb77b35169aa1035051e", size = 15767, upload-time = "2026-06-19T16:20:26.869Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/0c/005fe8db1e19135f493a3de8c8d38031e1ad2d626de4ef89f282acf4aff7/pyobjc_framework_devicediscoveryextension-12.1-py2.py3-none-any.whl", hash = "sha256:d6d6b606d27d4d88efc0bed4727c375e749149b360290c3ad2afc52337739a1b", size = 4321, upload-time = "2025-11-14T09:47:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/5f/00/cf5ff4cb4a2c35b7e42e386dfaf7d7692410126c0643db29afa3b028c0a6/pyobjc_framework_devicediscoveryextension-12.2.1-py2.py3-none-any.whl", hash = "sha256:6e4dcee2810968bf1b076d3718ce037f65d770533534806866ad8391f9806bfb", size = 4346, upload-time = "2026-06-19T16:10:17.071Z" }, ] [[package]] name = "pyobjc-framework-dictionaryservices" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-coreservices" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7a/c0/daf03cdaf6d4e04e0cf164db358378c07facd21e4e3f8622505d72573e2c/pyobjc_framework_dictionaryservices-12.1.tar.gz", hash = "sha256:354158f3c55d66681fa903c7b3cb05a435b717fa78d0cef44d258d61156454a7", size = 10573, upload-time = "2025-11-14T10:14:53.961Z" } +sdist = { url = "https://files.pythonhosted.org/packages/95/2d/0c9cc7065a9d2d387be065ad3721b77876627541beee224bf86639c65f61/pyobjc_framework_dictionaryservices-12.2.1.tar.gz", hash = "sha256:631560760d58fe89af8332adee6dcaee35867cf13f4607f7b5c36e85fa4c1db9", size = 10712, upload-time = "2026-06-19T16:20:27.562Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/13/ab308e934146cfd54691ddad87e572cd1edb6659d795903c4c75904e2d7d/pyobjc_framework_dictionaryservices-12.1-py2.py3-none-any.whl", hash = "sha256:578854eec17fa473ac17ab30050a7bbb2ab69f17c5c49b673695254c3e88ad4b", size = 3930, upload-time = "2025-11-14T09:47:50.782Z" }, + { url = "https://files.pythonhosted.org/packages/6b/72/5a2edb46e216df811e73aa08c22f31aaa5209479a58941c1946bd0be92d7/pyobjc_framework_dictionaryservices-12.2.1-py2.py3-none-any.whl", hash = "sha256:2b9d3d09fc085d913f670e3069b6a88d35b76a03a5f37ce8636b47382dbb028d", size = 3956, upload-time = "2026-06-19T16:10:18.12Z" }, ] [[package]] name = "pyobjc-framework-discrecording" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c4/87/8bd4544793bfcdf507174abddd02b1f077b48fab0004b3db9a63142ce7e9/pyobjc_framework_discrecording-12.1.tar.gz", hash = "sha256:6defc8ea97fb33b4d43870c673710c04c3dc48be30cdf78ba28191a922094990", size = 55607, upload-time = "2025-11-14T10:14:58.276Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/ec/a7be70eb1c6700f446c68530e74bd71e9fde6ca2a5e76b237bae8fa980f1/pyobjc_framework_discrecording-12.2.1.tar.gz", hash = "sha256:2616daba51f50b8c6989d38d502ca98ed3ce53aee6b01c9457534267cbb1a4e2", size = 62013, upload-time = "2026-06-19T16:20:28.243Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/99/bd915504fd2a8b15b65817bc2d06c29846d312b972ce57acf0a5911ecfa2/pyobjc_framework_discrecording-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b940e018b57ce637f5ada4d44ed6775d349dbc4e67c6e563f682fc3277c7affe", size = 14545, upload-time = "2025-11-14T09:47:52.678Z" }, - { url = "https://files.pythonhosted.org/packages/0e/ce/89df4d53a0a5e3a590d6e735eca4f0ba4d1ccc0e0acfbc14164026a3c502/pyobjc_framework_discrecording-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f7d815f28f781e20de0bf278aaa10b0de7e5ea1189aa17676c0bf5b99e9e0d52", size = 14540, upload-time = "2025-11-14T09:47:55.442Z" }, - { url = "https://files.pythonhosted.org/packages/c8/70/14a5aa348a5eba16e8773bb56698575cf114aa55aa303037b7000fc53959/pyobjc_framework_discrecording-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:865f1551e58459da6073360afc8f2cc452472c676ba83dcaa9b0c44e7775e4b5", size = 14566, upload-time = "2025-11-14T09:47:57.503Z" }, - { url = "https://files.pythonhosted.org/packages/aa/29/0064a48b24694597890cb065f5d33f719eed2cfff2878f43f310f27485cc/pyobjc_framework_discrecording-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c682c458622db9b4ea8363335ee38f5dd98db6691680041a3fda73e26714346", size = 14567, upload-time = "2025-11-14T09:47:59.78Z" }, - { url = "https://files.pythonhosted.org/packages/de/78/b8b3f063ecda49d600548eeee0c29b47a0b7635623a68609038326bfa7e7/pyobjc_framework_discrecording-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:36e1ba4d37fe310bad2fbfeadd43c8ef001cfae9a2a0484d7318504c5dbefa3f", size = 14745, upload-time = "2025-11-14T09:48:02.271Z" }, - { url = "https://files.pythonhosted.org/packages/d1/f1/61b7d8a35fb654ece97b539912452334665abf0a1fa9e83cda809c674c9e/pyobjc_framework_discrecording-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a60e2cab88fdf923f2017effb248f7c32819fbe494a6d17acfa71754b44ff68c", size = 14632, upload-time = "2025-11-14T09:48:04.41Z" }, - { url = "https://files.pythonhosted.org/packages/59/f5/e3db465b3087a3d3550dc9b4a90b33fa281d19da24dd0a5b591eeddbbe64/pyobjc_framework_discrecording-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:3345fcb139f1646c2aef41be6344c5b944817ea4df85d7f61db27781a90d77a6", size = 14808, upload-time = "2025-11-14T09:48:06.496Z" }, + { url = "https://files.pythonhosted.org/packages/3e/33/3cc50b5f84b53c362528f92e7a9798189c53e80a2bb6d9d044c934916e0c/pyobjc_framework_discrecording-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b1276a4559efdfdbff63c3bc05ef7952974bceb4e633b2a1b9807d3d7f900df1", size = 14563, upload-time = "2026-06-19T16:10:19.037Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c0/83243a210288fb2a613b5a43602fe6baab6f94584d8ab66fe241325e469c/pyobjc_framework_discrecording-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b589f3aca1bc9dfb07ce9743b2a97ae4f0d0b6b5fc7f01fc5386db580b92e4cd", size = 14564, upload-time = "2026-06-19T16:10:20.226Z" }, + { url = "https://files.pythonhosted.org/packages/23/68/4260e1493cd661f900cc4966a055f82cc9fbba7e311fae5c42190106d258/pyobjc_framework_discrecording-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed929553896338cc0e5109c9b67c121dee2049e62735de94522795708d1b61e6", size = 14589, upload-time = "2026-06-19T16:10:21.373Z" }, + { url = "https://files.pythonhosted.org/packages/f2/76/207c9f94d956c40a19547456e8d301fbe21dbcc0df69944339960051623a/pyobjc_framework_discrecording-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8f6a590c94c53d6d0975b54218cf018f9660f145fe686992d377fbd7b9861706", size = 14591, upload-time = "2026-06-19T16:10:23.225Z" }, + { url = "https://files.pythonhosted.org/packages/78/ea/e5b722dd0daa4bb7c015d41e6c3e6f7013cd757bf27e18134f7954ae72ce/pyobjc_framework_discrecording-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:dc5557015d2df515bfc75e96b287d1209b59afa2ce1a4566af32e9b8b49e2318", size = 14765, upload-time = "2026-06-19T16:10:24.106Z" }, + { url = "https://files.pythonhosted.org/packages/e0/87/e06f86a6ac77d0896ca2944ab7f7ac88ba26e26d9a71bbe64f4d55a6107c/pyobjc_framework_discrecording-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:439fc5b5b930ae7246c06a053748cdd0962db377d1493dd9fbec130939ff4a19", size = 14655, upload-time = "2026-06-19T16:10:25.061Z" }, + { url = "https://files.pythonhosted.org/packages/19/4b/04f04fa4071d44d5caf367c8586266083097d541118d3c39a5ec44161604/pyobjc_framework_discrecording-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:5e0ad242013ed977c12a69beaef66b7c8e3cf24af5f802261da8241205ef15cf", size = 14827, upload-time = "2026-06-19T16:10:25.884Z" }, + { url = "https://files.pythonhosted.org/packages/8f/1d/709d072496482c9d568009e69f41c46818a16f4f18b42ffd50a89e48c53f/pyobjc_framework_discrecording-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:43bb79f284648b07187ce01b3e6b55b1271951b438eb99eea9fcf39c008a80c6", size = 14656, upload-time = "2026-06-19T16:10:26.69Z" }, + { url = "https://files.pythonhosted.org/packages/83/3a/8f96d7d0fb19411141f1c0e03ce1c2f2f9141b41ab31f299beb97d9b46e3/pyobjc_framework_discrecording-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:18a68340d2090a707620f82038d68edd800f5d7806d566605e80104d12068dd5", size = 14821, upload-time = "2026-06-19T16:10:27.57Z" }, ] [[package]] name = "pyobjc-framework-discrecordingui" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-discrecording" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/63/8667f5bb1ecb556add04e86b278cb358dc1f2f03862705cae6f09097464c/pyobjc_framework_discrecordingui-12.1.tar.gz", hash = "sha256:6793d4a1a7f3219d063f39d87f1d4ebbbb3347e35d09194a193cfe16cba718a8", size = 16450, upload-time = "2025-11-14T10:15:00.254Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/34/be343e4da6765228d1b6c6ac72a0c1c339ed8da931fa2701f28467c14aaa/pyobjc_framework_discrecordingui-12.2.1.tar.gz", hash = "sha256:1cf9e9e028c619f932ecf3ec0efc91227840bf6c6492c788cde91dc5c3744da6", size = 19538, upload-time = "2026-06-19T16:20:29.049Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/4e/76016130c27b98943c5758a05beab3ba1bc9349ee881e1dfc509ea954233/pyobjc_framework_discrecordingui-12.1-py2.py3-none-any.whl", hash = "sha256:6544ef99cad3dee95716c83cb207088768b6ecd3de178f7e1b17df5997689dfd", size = 4702, upload-time = "2025-11-14T09:48:08.01Z" }, + { url = "https://files.pythonhosted.org/packages/66/fa/5447695b7b646242b4f0e790b382c4432cc6e730152a19271896e4683a63/pyobjc_framework_discrecordingui-12.2.1-py2.py3-none-any.whl", hash = "sha256:a29bf02898481e6ee02a38ae932a35b9c6a4f68fe9c890504db18e94d15cfa45", size = 4723, upload-time = "2026-06-19T16:10:28.438Z" }, ] [[package]] name = "pyobjc-framework-diskarbitration" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3a/42/f75fcabec1a0033e4c5235cc8225773f610321d565b63bf982c10c6bbee4/pyobjc_framework_diskarbitration-12.1.tar.gz", hash = "sha256:6703bc5a09b38a720c9ffca356b58f7e99fa76fc988c9ec4d87112344e63dfc2", size = 17121, upload-time = "2025-11-14T10:15:02.223Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/25/d6a3231f7d81903e6cd6dd9674ef798d45dba2cf301dfb2224277e89c62f/pyobjc_framework_diskarbitration-12.2.1.tar.gz", hash = "sha256:b79a44c8a7791109371bb6aa78ee970c7cf0ee6a6ecdaf92ae3b9dcc4f57f469", size = 18174, upload-time = "2026-06-19T16:20:29.842Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/65/c1f54c47af17cb6b923eab85e95f22396c52f90ee8f5b387acffad9a99ea/pyobjc_framework_diskarbitration-12.1-py2.py3-none-any.whl", hash = "sha256:54caf3079fe4ae5ac14466a9b68923ee260a1a88a8290686b4a2015ba14c2db6", size = 4877, upload-time = "2025-11-14T09:48:09.945Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7e/c95163c4850fcd59f4d761ed0e453c50ab782914df478f53d402dd424314/pyobjc_framework_diskarbitration-12.2.1-py2.py3-none-any.whl", hash = "sha256:8fdde0943ca7499a246294d678b4f01c3f7c7569da19b029eeb31e4e9f01519c", size = 4914, upload-time = "2026-06-19T16:10:29.349Z" }, ] [[package]] name = "pyobjc-framework-dvdplayback" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cf/dd/7859a58e8dd336c77f83feb76d502e9623c394ea09322e29a03f5bc04d32/pyobjc_framework_dvdplayback-12.1.tar.gz", hash = "sha256:279345d4b5fb2c47dd8e5c2fd289e644b6648b74f5c25079805eeb61bfc4a9cd", size = 32332, upload-time = "2025-11-14T10:15:05.257Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/de/281c85dbcde3422af95cb6cf4607abde52612bcaa66850b8c1e630f25152/pyobjc_framework_dvdplayback-12.2.1.tar.gz", hash = "sha256:cc715bce5edceaec078e3b39141a660cb4ca2fbe0afdf133bd2c531c170e45a6", size = 34829, upload-time = "2026-06-19T16:20:30.576Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/7d/22c07c28fab1f15f0d364806e39a6ca63c737c645fe7e98e157878b5998c/pyobjc_framework_dvdplayback-12.1-py2.py3-none-any.whl", hash = "sha256:af911cc222272a55b46a1a02a46a355279aecfd8132231d8d1b279e252b8ad4c", size = 8243, upload-time = "2025-11-14T09:48:11.824Z" }, + { url = "https://files.pythonhosted.org/packages/c0/6c/ee10385cdd403471b205baf701890b5cb640a9dc357013d8a1801f1d5640/pyobjc_framework_dvdplayback-12.2.1-py2.py3-none-any.whl", hash = "sha256:56d737c2a1ffb1076d280b84906adc8091c055fcf6670367a902f38ea4877367", size = 8266, upload-time = "2026-06-19T16:10:30.25Z" }, ] [[package]] name = "pyobjc-framework-eventkit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b6/42/4ec97e641fdcf30896fe76476181622954cb017117b1429f634d24816711/pyobjc_framework_eventkit-12.1.tar.gz", hash = "sha256:7c1882be2f444b1d0f71e9a0cd1e9c04ad98e0261292ab741fc9de0b8bbbbae9", size = 28538, upload-time = "2025-11-14T10:15:07.878Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/94/757c963beb0fb86891c3cd16db56d2e1cf746c24d8256023d6d7f4c83ef7/pyobjc_framework_eventkit-12.2.1.tar.gz", hash = "sha256:2528a61da2fed7d71933d7e5407414176cfcac2e03fdba4633633f0d646c75fa", size = 33775, upload-time = "2026-06-19T16:20:31.384Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/35/142f43227627d6324993869d354b9e57eb1e88c4e229e2271592254daf25/pyobjc_framework_eventkit-12.1-py2.py3-none-any.whl", hash = "sha256:3d2d36d5bd9e0a13887a6ac7cdd36675985ebe2a9cb3cdf8cec0725670c92c60", size = 6820, upload-time = "2025-11-14T09:48:14.035Z" }, + { url = "https://files.pythonhosted.org/packages/4e/cf/504f2531b02010857df765ecebf41097fbd0bf1b803be47afec574199437/pyobjc_framework_eventkit-12.2.1-py2.py3-none-any.whl", hash = "sha256:8efb71afaf9a97450ba701a6fee7de79e0cdefe6d71b5f6724e87b2fd68c5bd4", size = 6952, upload-time = "2026-06-19T16:10:31.307Z" }, ] [[package]] name = "pyobjc-framework-exceptionhandling" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f1/17/5c9d4164f7ccf6b9100be0ad597a7857395dd58ea492cba4f0e9c0b77049/pyobjc_framework_exceptionhandling-12.1.tar.gz", hash = "sha256:7f0719eeea6695197fce0e7042342daa462683dc466eb6a442aad897032ab00d", size = 16694, upload-time = "2025-11-14T10:15:10.173Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/59/ef544e804de32c5e437b9936742ae2bfdb6873ee1b284609a70e98855220/pyobjc_framework_exceptionhandling-12.2.1.tar.gz", hash = "sha256:aef051e1afda09853289f66d4e6c1b58cd924656afc2a1bec05b15e22e20e5f1", size = 17174, upload-time = "2026-06-19T16:20:32.11Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/ad/8e05acf3635f20ea7d878be30d58a484c8b901a8552c501feb7893472f86/pyobjc_framework_exceptionhandling-12.1-py2.py3-none-any.whl", hash = "sha256:2f1eae14cf0162e53a0888d9ffe63f047501fe583a23cdc9c966e89f48cf4713", size = 7113, upload-time = "2025-11-14T09:48:15.685Z" }, + { url = "https://files.pythonhosted.org/packages/92/46/f271631363bf830d520ed52bc0c5257cfcfe196859ab1b58720b023ae17d/pyobjc_framework_exceptionhandling-12.2.1-py2.py3-none-any.whl", hash = "sha256:b1d33ccb5ded605f2c92240c5719b64c45505f4c0dbc42b7d8595223b395540c", size = 7138, upload-time = "2026-06-19T16:10:32.352Z" }, ] [[package]] name = "pyobjc-framework-executionpolicy" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/11/db765e76e7b00e1521d7bb3a61ae49b59e7573ac108da174720e5d96b61b/pyobjc_framework_executionpolicy-12.1.tar.gz", hash = "sha256:682866589365cd01d3a724d8a2781794b5cba1e152411a58825ea52d7b972941", size = 12594, upload-time = "2025-11-14T10:15:12.077Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/9e/c6f4962416713ee46ae5104b08091d3c1ff49fdcb69bf1edc03d2285b7e2/pyobjc_framework_executionpolicy-12.2.1.tar.gz", hash = "sha256:898d38b19e805e12da317930474f49a9f944f7e2335a9dc9c1644ecdd079d12e", size = 13043, upload-time = "2026-06-19T16:20:32.786Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/2c/f10352398f10f244401ab8f53cabd127dc3f5dbbfc8de83464661d716671/pyobjc_framework_executionpolicy-12.1-py2.py3-none-any.whl", hash = "sha256:c3a9eca3bd143cf202787dd5e3f40d954c198f18a5e0b8b3e2fcdd317bf33a52", size = 3739, upload-time = "2025-11-14T09:48:17.35Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a4/7557a818158c59ed531ce0abbe7bfa8071c8657734b376dcf08a1bd54fc3/pyobjc_framework_executionpolicy-12.2.1-py2.py3-none-any.whl", hash = "sha256:493340d7311f6294feb93327c75675fff24859ad9bb3b87c0ce8d339850c4c93", size = 3798, upload-time = "2026-06-19T16:10:33.329Z" }, ] [[package]] name = "pyobjc-framework-extensionkit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9a/d4/e9b1f74d29ad9dea3d60468d59b80e14ed3a19f9f7a25afcbc10d29c8a1e/pyobjc_framework_extensionkit-12.1.tar.gz", hash = "sha256:773987353e8aba04223dbba3149253db944abfb090c35318b3a770195b75da6d", size = 18694, upload-time = "2025-11-14T10:15:14.104Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/f3/51d50e7af4958d597d924fab765725746ed164ff02e59928296574f6e2d2/pyobjc_framework_extensionkit-12.2.1.tar.gz", hash = "sha256:9b8dea5867436ecfeefc7edb4cd8358c8e7741d31693047f1321e15dfc533540", size = 19239, upload-time = "2026-06-19T16:20:33.738Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/22/2714fe409dda791152bfe9463807a3deefcee316089af1a123019871a3cf/pyobjc_framework_extensionkit-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6764f7477bb0f6407a5217cbfc2da13a71a5d402ac5f005300958886e25fd9b5", size = 7919, upload-time = "2025-11-14T09:48:19.403Z" }, - { url = "https://files.pythonhosted.org/packages/4f/02/3d1df48f838dc9d64f03bedd29f0fdac6c31945251c9818c3e34083eb731/pyobjc_framework_extensionkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9139c064e1c7f21455411848eb39f092af6085a26cad322aa26309260e7929d9", size = 7919, upload-time = "2025-11-14T09:48:22.14Z" }, - { url = "https://files.pythonhosted.org/packages/9c/d9/8064dad6114a489e5439cc20d9fb0dd64cfc406d875b4a3c87015b3f6266/pyobjc_framework_extensionkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7e01d705c7ac6d080ae34a81db6d9b81875eabefa63fd6eafbfa30f676dd780b", size = 7932, upload-time = "2025-11-14T09:48:23.653Z" }, - { url = "https://files.pythonhosted.org/packages/f5/75/63c304543fc3c5c0755521ab0535e3f81f6ab8de656a02598e23f687cb6c/pyobjc_framework_extensionkit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8f2a87bd4fbb8d14900bbe9c979b23b7532b23685c0f5022671b26db4fa3e515", size = 7946, upload-time = "2025-11-14T09:48:25.803Z" }, - { url = "https://files.pythonhosted.org/packages/bd/40/2dab02d8726abf586f253fbddc2d0d9b2abd5dbb4b24272eb48c886741fc/pyobjc_framework_extensionkit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:570e8a89116380a27dd8df7ce28cd5f7296eb785aea4cb7dc6447954005360c2", size = 8086, upload-time = "2025-11-14T09:48:27.715Z" }, - { url = "https://files.pythonhosted.org/packages/fc/ec/a02ddac5ea7439dc4deb488ba551e27565920b8864c2f71611159794a1b5/pyobjc_framework_extensionkit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b002bd4ee7aa951298f8bdd41e2a59d172050975499f94a26caff263b5fadca4", size = 8004, upload-time = "2025-11-14T09:48:29.454Z" }, - { url = "https://files.pythonhosted.org/packages/15/21/2fad7badad0bb25c22bff840563041a3f9e10aee4da7232bdbbff1b48138/pyobjc_framework_extensionkit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d14ebebffe05d33d189bf2bec5b676721790cf041b7ee628bfd05bcda4c148cc", size = 8141, upload-time = "2025-11-14T09:48:31.37Z" }, + { url = "https://files.pythonhosted.org/packages/51/8b/ec3090bf65d01e21cb81eb8cd312ae37e425f1313ca0f0e1a10bc7013fa5/pyobjc_framework_extensionkit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f281797ec4ee34f2bcd6a3d824e26b74f4710dd920200b709ff27585939bd2b", size = 7942, upload-time = "2026-06-19T16:10:34.212Z" }, + { url = "https://files.pythonhosted.org/packages/1f/35/13c03829d2f8b70869da847d27552f0ff08c6b9431c0e58f02ca7ff3dcac/pyobjc_framework_extensionkit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:047ebbbfdd61369dc8796a5754cc9b3a50104ccae1d4392f48d8f73ee9b6f859", size = 7941, upload-time = "2026-06-19T16:10:35.104Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d2/f859740c55b569857a2f969a39177fcceb64d41fc345fe39c7d27d48d3cf/pyobjc_framework_extensionkit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9279dbc7e75f28b9b6553d351e48475fbf3841f9a30f4b503fe03e18c0ccb074", size = 7955, upload-time = "2026-06-19T16:10:36.008Z" }, + { url = "https://files.pythonhosted.org/packages/e1/18/c4162af7b93d92b0d6806bdbcfd16cbf0f3e5d6ff17e80b5feea4e0a2429/pyobjc_framework_extensionkit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e29f5860aa6acb53582c2742e477922067b959046c1998ed7fd962898fd78855", size = 7970, upload-time = "2026-06-19T16:10:37.038Z" }, + { url = "https://files.pythonhosted.org/packages/e4/4c/472391557a3ff3f3e4fcfb683c34c7f3bf9abef7c8d0eff563cb11168cbb/pyobjc_framework_extensionkit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b8dae4aa09e327a44b12c2e5c49250e3439fd98fd8eabd08ae97d4b71180aee2", size = 8108, upload-time = "2026-06-19T16:10:38.033Z" }, + { url = "https://files.pythonhosted.org/packages/9c/bc/89c590272ea844576f17bf52f46de5d6d9be06236be1c79b754e2847baf9/pyobjc_framework_extensionkit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:cb00d24a9a3b2284f9f5bdd2b65ca1e25f5743012e728b751109d66f28efb3c5", size = 8036, upload-time = "2026-06-19T16:10:38.899Z" }, + { url = "https://files.pythonhosted.org/packages/eb/56/4165f128867f83d4992aa3c7841826c98ea2142e115d7866580be70df59d/pyobjc_framework_extensionkit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1307a42439c28e55081e47729dc04ec1de33693fb13df38381914b849580fa51", size = 8169, upload-time = "2026-06-19T16:10:39.899Z" }, + { url = "https://files.pythonhosted.org/packages/e1/3a/68002c7bf2a8b0392af0d777510c3e7b754294f1b8e24b615547cf11bdb9/pyobjc_framework_extensionkit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:3599ba37400750fbca5a3500c7ecdf19f0df30182c9e9e50a51c0c0e12ee5cb5", size = 8024, upload-time = "2026-06-19T16:10:40.82Z" }, + { url = "https://files.pythonhosted.org/packages/3c/3e/005011c3f7a0bc53dc8ad34b4fdf429a472cd95696023f9f4a87f581f3d1/pyobjc_framework_extensionkit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:ee7853b036f2a7c52a35ec0d6b155c8c2a36ec6c5bf2e3a35318a8eee66f9564", size = 8165, upload-time = "2026-06-19T16:10:41.745Z" }, ] [[package]] name = "pyobjc-framework-externalaccessory" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/35/86c097ae2fdf912c61c1276e80f3e090a3fc898c75effdf51d86afec456b/pyobjc_framework_externalaccessory-12.1.tar.gz", hash = "sha256:079f770a115d517a6ab87db1b8a62ca6cdf6c35ae65f45eecc21b491e78776c0", size = 20958, upload-time = "2025-11-14T10:15:16.419Z" } +sdist = { url = "https://files.pythonhosted.org/packages/75/e2/96f79a29c7bc6c229035dd8e92f4f73001affa8e642248cf7ab5bf03b2c8/pyobjc_framework_externalaccessory-12.2.1.tar.gz", hash = "sha256:49658d55b3401c03ef3523ab3b8e2ed082739e971a0070d81b16d06441ac8d03", size = 22011, upload-time = "2026-06-19T16:20:34.554Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/af/754d05e6411be3464efffd8111bc12ce1b29d615910b19e532f39083dfc3/pyobjc_framework_externalaccessory-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ee55a06fe8ef7ff07a435d75544d8c7177e790afaff60b5a55e3ceea1c9f03e7", size = 8908, upload-time = "2025-11-14T09:48:33.173Z" }, - { url = "https://files.pythonhosted.org/packages/18/01/2a83b63e82ce58722277a00521c3aeec58ac5abb3086704554e47f8becf3/pyobjc_framework_externalaccessory-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:32208e05c9448c8f41b3efdd35dbea4a8b119af190f7a2db0d580be8a5cf962e", size = 8911, upload-time = "2025-11-14T09:48:35.349Z" }, - { url = "https://files.pythonhosted.org/packages/ec/52/984034396089766b6e5ff3be0f93470e721c420fa9d1076398557532234f/pyobjc_framework_externalaccessory-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dedbf7a09375ac19668156c1417bd7829565b164a246b714e225b9cbb6a351ad", size = 8932, upload-time = "2025-11-14T09:48:37.393Z" }, - { url = "https://files.pythonhosted.org/packages/2d/bf/9e368e16edb94d9507c1034542379b943e0d9c3bcc0ce8062ac330216317/pyobjc_framework_externalaccessory-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:34858f06cd75fe4e358555961a6898eb8778fd2931058fd660fcd5d6cf31b162", size = 8944, upload-time = "2025-11-14T09:48:39.07Z" }, - { url = "https://files.pythonhosted.org/packages/71/5b/643a00fe334485b4100d7a68330b6c6c349fe27434e0dc0fdf2065984555/pyobjc_framework_externalaccessory-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5551915fa82ff1eea8e5810f74c1298e5327aefe4ac90abeb9a7abd69ff33a22", size = 9100, upload-time = "2025-11-14T09:48:41.57Z" }, - { url = "https://files.pythonhosted.org/packages/7b/e4/b7f1c8b977e64b495a5f268f9f6d82ed71152268542a7e676c26c647a6b0/pyobjc_framework_externalaccessory-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:22efc5bf68f5f0ef39f4308ef06403c42544f5fc75f6eeb137a87af99357dda1", size = 8999, upload-time = "2025-11-14T09:48:43.386Z" }, - { url = "https://files.pythonhosted.org/packages/02/23/c038dd6c9dee7067dd51e430f5019a39f68102aade47ae9a89f64eb913d6/pyobjc_framework_externalaccessory-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:3a0f21fe660ee89b98d357ce3df9ff546f19161b6f569cc93888e6bcbd1d7f22", size = 9178, upload-time = "2025-11-14T09:48:45.398Z" }, + { url = "https://files.pythonhosted.org/packages/32/dd/83a97d3ad0b403c5cb8d6e1fbdd89f5a40045ed58d232aaae63f16e8e20d/pyobjc_framework_externalaccessory-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:906122469bcf04243395afb807bd0a2924b911e2f2447e4f1fb4ddebf492b7d8", size = 8932, upload-time = "2026-06-19T16:10:42.751Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e3/c690f51e505dd826680a3094e9aa511f9d7507bca543e4f4184af23f1d82/pyobjc_framework_externalaccessory-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:787ea43ded7a7f5621cabc3578a7414298dbfd62c73c8418ddceb720c23388de", size = 8931, upload-time = "2026-06-19T16:10:43.842Z" }, + { url = "https://files.pythonhosted.org/packages/42/29/cf69d3931127544a7a576a3c3fa4aede1808ef62ad3c77e3803414aefa68/pyobjc_framework_externalaccessory-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:47635ff739691b8446079b5018c71585dbf33c2d5eff6a34279efb0868caf432", size = 8951, upload-time = "2026-06-19T16:10:44.85Z" }, + { url = "https://files.pythonhosted.org/packages/21/21/6cc05d60f54f317ab6f0c36d025951ec9ac15cb088b219d88add060051d7/pyobjc_framework_externalaccessory-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c61d2d3ee83d4c0fbe4ef427041fc911c0f168bc7f10f9b7ff9d502e1a9995c8", size = 8966, upload-time = "2026-06-19T16:10:45.619Z" }, + { url = "https://files.pythonhosted.org/packages/c7/5b/c2799931e1c456cf2748c8c81ed9ac8218bc55cf838f4d71d0355f52153f/pyobjc_framework_externalaccessory-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:7cdaae96e146ffb1def2b14a0890555e4147ec77924fca241fb2361f93a36b31", size = 9124, upload-time = "2026-06-19T16:10:46.455Z" }, + { url = "https://files.pythonhosted.org/packages/0a/ee/088bed0b691c624e2dbe764f86408b4086748147d63149b08cd28a3eb31e/pyobjc_framework_externalaccessory-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:f13955be8d9f6f2e5922600d2063f765fb070bb3b4a2f2c0ef8ec21024d39ece", size = 9019, upload-time = "2026-06-19T16:10:47.241Z" }, + { url = "https://files.pythonhosted.org/packages/ab/29/8396b3231315a8221a8077805ce57889522037b18e9e5a42a926a6270eaa/pyobjc_framework_externalaccessory-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:16a269b33442580121353739ae128fa0dddd5c59f59705ee57b836932b750ad7", size = 9201, upload-time = "2026-06-19T16:10:48.062Z" }, + { url = "https://files.pythonhosted.org/packages/b0/19/07379ed2cde32acd29d783e0d76fadc5277f6cb117af934a805004bcd8c1/pyobjc_framework_externalaccessory-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:505ae95af4e8abc67016d26d59d2521b94ba3e6948b849d2cd85069dec31d044", size = 9011, upload-time = "2026-06-19T16:10:48.815Z" }, + { url = "https://files.pythonhosted.org/packages/80/3f/1310bfe12885753fa3cc497581441f145cc4ff5efa97d8a16115939c6eeb/pyobjc_framework_externalaccessory-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:d2d3a2dba827c5054832142faec0793ea50e3dd869aa33df54c777a9e03557eb", size = 9186, upload-time = "2026-06-19T16:10:49.601Z" }, ] [[package]] name = "pyobjc-framework-fileprovider" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cc/9a/724b1fae5709f8860f06a6a2a46de568f9bb8bdb2e2aae45b4e010368f51/pyobjc_framework_fileprovider-12.1.tar.gz", hash = "sha256:45034e0d00ae153c991aa01cb1fd41874650a30093e77ba73401dcce5534c8ad", size = 43071, upload-time = "2025-11-14T10:15:19.989Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/15/d161117076a478299804b40720c5f110b4540f77ddb1b55e76b18dcc3ea8/pyobjc_framework_fileprovider-12.2.1.tar.gz", hash = "sha256:fd94e8941de50b6bc94ab8fbbf0f4605eca0602e4bd48088b8d6c1162115b506", size = 50576, upload-time = "2026-06-19T16:20:35.382Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/eb/3ead14806cb784692504f331756f2c03a7254e384c01a6a08e0e16ba0115/pyobjc_framework_fileprovider-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6f672069340bd8e994f9ef144949d807485ced5b1b9e21917cc7810e1aa8cda0", size = 20976, upload-time = "2025-11-14T09:48:47.745Z" }, - { url = "https://files.pythonhosted.org/packages/1d/37/2f56167e9f43d3b25a5ed073305ca0cfbfc66bedec7aae9e1f2c9c337265/pyobjc_framework_fileprovider-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9d527c417f06d27c4908e51d4e6ccce0adcd80c054f19e709626e55c511dc963", size = 20970, upload-time = "2025-11-14T09:48:50.557Z" }, - { url = "https://files.pythonhosted.org/packages/2c/f5/56f0751a2988b2caca89d6800c8f29246828d1a7498bb676ef1ab28000b7/pyobjc_framework_fileprovider-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:89b140ea8369512ddf4164b007cbe35b4d97d1dcb8affa12a7264c0ab8d56e45", size = 21003, upload-time = "2025-11-14T09:48:53.128Z" }, - { url = "https://files.pythonhosted.org/packages/31/92/23deb9d12690a69599dd7a66f3f5a5a3c09824147d148759a33c5c2933fc/pyobjc_framework_fileprovider-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a1a7a6ac3af1e93d23f5644b4c7140dc7edf5ff79419cc0bd25ce7001afc1cf6", size = 21018, upload-time = "2025-11-14T09:48:55.504Z" }, - { url = "https://files.pythonhosted.org/packages/a4/99/cec0a13ca8da9283d1a1bbaeeabdff7903be5c85cfb27a2bb7cc121cb529/pyobjc_framework_fileprovider-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6d6744c8c4f915b6193a982365d947b63286cea605f990a2aaa3bb37069471f2", size = 21300, upload-time = "2025-11-14T09:48:57.948Z" }, - { url = "https://files.pythonhosted.org/packages/4f/8d/b1c6e0927d22d0c125c8a62cd2342c4613e3aabf13cb0e66ea62fe85fff1/pyobjc_framework_fileprovider-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:520b8c83b1ce63e0f668ea1683e3843f2e5379c0af76dceb19d5d540d584ff54", size = 21062, upload-time = "2025-11-14T09:49:00.305Z" }, - { url = "https://files.pythonhosted.org/packages/25/14/1a05c99849e6abb778f601eeb93e27f2fbbbb8f4ffaab42c8aa02ff62406/pyobjc_framework_fileprovider-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:de9aaea1308e37f7537dd2a8e89f151d4eaee2b0db5d248dc85cc1fd521adaaa", size = 21331, upload-time = "2025-11-14T09:49:02.803Z" }, + { url = "https://files.pythonhosted.org/packages/37/1f/c0a9fff434fa1dc01bc9a69149871d8425c99d2f34cc21b6ee537fbb1ec6/pyobjc_framework_fileprovider-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:696b72f0902caf46f9110f5c6cb058f28329a89fce31fb349fe58dfe5f5ea585", size = 21058, upload-time = "2026-06-19T16:10:50.663Z" }, + { url = "https://files.pythonhosted.org/packages/23/42/5848410e19ce30a784f30b586d762e1777a50ea74517dfd93a30dc0146cd/pyobjc_framework_fileprovider-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9fe7f23dc7bb3bb0a045ec443ff7a55eaf3e4007b2d9a99a79ff201c9536c58e", size = 21062, upload-time = "2026-06-19T16:10:51.862Z" }, + { url = "https://files.pythonhosted.org/packages/dc/79/d9b4d70c71f6828a6dd1393fcd2e956be2079203eebb33beab5880241df1/pyobjc_framework_fileprovider-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:772d963f342af3d2853e4b5a14788b0f72e3c25c457c301e27d8bcdefdbaca01", size = 21094, upload-time = "2026-06-19T16:10:52.811Z" }, + { url = "https://files.pythonhosted.org/packages/ca/89/fb75ac1019a89f5f10716d57af5a4a7fb5baa720278c688012f1acdc39ff/pyobjc_framework_fileprovider-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:95f7de1b2a048ac0edd1e02479b13956704b49521fabbd8e94e59564999204b2", size = 21100, upload-time = "2026-06-19T16:10:54.037Z" }, + { url = "https://files.pythonhosted.org/packages/19/56/4565504252898725ff960d8511318314d10f1c0c887031e30d0e19ac477d/pyobjc_framework_fileprovider-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:fe700e447f54a6bca8fdd13a4c21bb93d26665cb4598b958e099d1aca870b109", size = 21385, upload-time = "2026-06-19T16:10:54.874Z" }, + { url = "https://files.pythonhosted.org/packages/f1/5a/602bdd1faf969d2742af163aa182622ddfecf2ca481449aa7f24ec6bb684/pyobjc_framework_fileprovider-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:3bb8b09153e836d3a6223868a55762e5db42830bf58394c1785a90f20c044cef", size = 21145, upload-time = "2026-06-19T16:10:55.75Z" }, + { url = "https://files.pythonhosted.org/packages/76/92/49cb132b6e4fead0b89c2c4c05df9a87717c0a63c26e5c83b139dc3c2a5f/pyobjc_framework_fileprovider-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d62ac3e6dab2087831f9bbb778adb1def9362aacf630cebd2afba16debf1a151", size = 21421, upload-time = "2026-06-19T16:10:56.596Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1c/440acd08b10764b1adeb6fe21a1eb473e7b840d8727bfa0022a9ebee06a8/pyobjc_framework_fileprovider-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:b3592ee88ee6b328a990ed6e70f51a5396ff92f26f25e9a5a706c139d91de546", size = 21128, upload-time = "2026-06-19T16:10:57.525Z" }, + { url = "https://files.pythonhosted.org/packages/32/53/417701fd9cf53fdddd3ba062004413edc28ca8c3ca8df647187de0b733f8/pyobjc_framework_fileprovider-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:a23ac3e9c43894d7fc88e375b1f48f7d8695cf7d0e036d1ecbfdec1596e13a27", size = 21422, upload-time = "2026-06-19T16:10:58.357Z" }, ] [[package]] name = "pyobjc-framework-fileproviderui" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-fileprovider" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/50/00/234f9b93f75255845df81d9d5ea20cb83ecb5c0a4e59147168b622dd0b9d/pyobjc_framework_fileproviderui-12.1.tar.gz", hash = "sha256:15296429d9db0955abc3242b2920b7a810509a85118dbc185f3ac8234e5a6165", size = 12437, upload-time = "2025-11-14T10:15:22.044Z" } +sdist = { url = "https://files.pythonhosted.org/packages/77/4c/066d39b6d90f637fdb2d6ab8762ffb234b700e89e93cb7473729b49aa505/pyobjc_framework_fileproviderui-12.2.1.tar.gz", hash = "sha256:40d02bcb15e324af6c624c85f1d65d83f81f31dd72136b0e5ec87440dc0fba4c", size = 12863, upload-time = "2026-06-19T16:20:36.391Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/65/cc4397511bd0af91993d6302a2aed205296a9ad626146eefdfc8a9624219/pyobjc_framework_fileproviderui-12.1-py2.py3-none-any.whl", hash = "sha256:521a914055089e28631018bd78df4c4f7416e98b4150f861d4a5bc97d5b1ffe4", size = 3715, upload-time = "2025-11-14T09:49:04.213Z" }, + { url = "https://files.pythonhosted.org/packages/c7/3c/a030778db1b6272a2ed420a4ebd00a9084df932580c039d48f7891cad100/pyobjc_framework_fileproviderui-12.2.1-py2.py3-none-any.whl", hash = "sha256:2ff2f939ece56f06eae58b9494743941e40500b042a0cf3a64dcf78179d3d79a", size = 3739, upload-time = "2026-06-19T16:10:59.201Z" }, ] [[package]] name = "pyobjc-framework-findersync" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e4/63/c8da472e0910238a905bc48620e005a1b8ae7921701408ca13e5fb0bfb4b/pyobjc_framework_findersync-12.1.tar.gz", hash = "sha256:c513104cef0013c233bf8655b527df665ce6f840c8bc0b3781e996933d4dcfa6", size = 13507, upload-time = "2025-11-14T10:15:24.161Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/8d/344b385f233b5843f05e7b78bea125123e22bc4c3b0e1eb93d3e55d9664c/pyobjc_framework_findersync-12.2.1.tar.gz", hash = "sha256:be3d41c9b836a53f24473e064ade6bd9ac071cc483377547cb6603e5a0de5d90", size = 14303, upload-time = "2026-06-19T16:20:37.63Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/9f/ec7f393e3e2fd11cbdf930d884a0ba81078bdb61920b3cba4f264de8b446/pyobjc_framework_findersync-12.1-py2.py3-none-any.whl", hash = "sha256:e07abeca52c486cf14927f617afc27afa7a3828b99fab3ad02355105fb29203e", size = 4889, upload-time = "2025-11-14T09:49:05.763Z" }, + { url = "https://files.pythonhosted.org/packages/31/73/eb80247a8fb7edd8565370dd7029e3f2e1ec76cc8f78efa1093f73328747/pyobjc_framework_findersync-12.2.1-py2.py3-none-any.whl", hash = "sha256:5b91a226b72a4c83a7ab5bf7e59164d59185021a396bd5804712ba8a55949db0", size = 4914, upload-time = "2026-06-19T16:11:00.173Z" }, ] [[package]] name = "pyobjc-framework-fsevents" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/43/17/21f45d2bca2efc72b975f2dfeae7a163dbeabb1236c1f188578403fd4f09/pyobjc_framework_fsevents-12.1.tar.gz", hash = "sha256:a22350e2aa789dec59b62da869c1b494a429f8c618854b1383d6473f4c065a02", size = 26487, upload-time = "2025-11-14T10:15:26.796Z" } +sdist = { url = "https://files.pythonhosted.org/packages/be/fc/b31d09b6b58e50c8bcd16acf251d397bafc454bff9160f0e2c922cc9cbe4/pyobjc_framework_fsevents-12.2.1.tar.gz", hash = "sha256:f78c98f68bc643794668a9484fc348ef3e98df359db7d8726cada35310af93b4", size = 27163, upload-time = "2026-06-19T16:20:38.372Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/97/8c0cc7fb5e3e2c94fa11b3e2a1e6a2546af067263c6da1eafe09485492c3/pyobjc_framework_fsevents-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b3af4e030325672679e3fce55d34fff2a6d9da0cf27810f3cfc0eec0880e45e8", size = 13057, upload-time = "2025-11-14T09:49:07.774Z" }, - { url = "https://files.pythonhosted.org/packages/a4/3f/a7fe5656b205ee3a9fd828e342157b91e643ee3e5c0d50b12bd4c737f683/pyobjc_framework_fsevents-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:459cc0aac9850c489d238ba778379d09f073bbc3626248855e78c4bc4d97fe46", size = 13059, upload-time = "2025-11-14T09:49:09.814Z" }, - { url = "https://files.pythonhosted.org/packages/2d/e3/2c5eeea390c0b053e2d73b223af3ec87a3e99a8106e8d3ee79942edb0822/pyobjc_framework_fsevents-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a2949358513fd7bc622fb362b5c4af4fc24fc6307320070ca410885e5e13d975", size = 13141, upload-time = "2025-11-14T09:49:11.947Z" }, - { url = "https://files.pythonhosted.org/packages/19/41/f06d14020eb9ec10c0e36f5e3f836f8541b989dcde9f53ea172852a7c864/pyobjc_framework_fsevents-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b30c72239a9ced4e4604fcf265a1efee788cb47850982dd80fcbaafa7ee64f9", size = 13143, upload-time = "2025-11-14T09:49:14.019Z" }, - { url = "https://files.pythonhosted.org/packages/2b/3a/10c1576da38f7e39d6adb592f54fa1b058c859c7d38d03b0cdaf25e12f8d/pyobjc_framework_fsevents-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:05220368b0685783e0ae00c885e167169d47ff5cf66de7172ca8074682dfc330", size = 13511, upload-time = "2025-11-14T09:49:16.423Z" }, - { url = "https://files.pythonhosted.org/packages/90/f6/d6ea1ce944adb3e2c77abc84470a825854428c72e71efe5742bad1c1b1cd/pyobjc_framework_fsevents-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:90819f2fe0516443f679273b128c212d9e6802570f2f1c8a1e190fed76e2dc48", size = 13033, upload-time = "2025-11-14T09:49:18.658Z" }, - { url = "https://files.pythonhosted.org/packages/be/73/62129609d6ef33987351297d052d25ff042d2d9a3876767915e8dc75d183/pyobjc_framework_fsevents-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:028f6a3195c6a00ca29baef31019cb2ca0c54e799072f0f0246b391dc6c4c1d3", size = 13495, upload-time = "2025-11-14T09:49:20.545Z" }, + { url = "https://files.pythonhosted.org/packages/d1/59/eecdd4c2dc02e1ffc96d604bce929377aae43ee4273f3765bbb08334ec86/pyobjc_framework_fsevents-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:eccc2a9d66f9ba5f4f49f40f18f984759dc406f09ecd77ca3a4070479e01d6d9", size = 13070, upload-time = "2026-06-19T16:11:01.136Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d4/f1e36b1d4b0216658999028aaeb322da1bae01dad1c8c033854c787977b2/pyobjc_framework_fsevents-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e78bd73365c3c0c42b3adfedbc8a66a7056ea16b84dea9f8af43635f931e48be", size = 13073, upload-time = "2026-06-19T16:11:02.224Z" }, + { url = "https://files.pythonhosted.org/packages/ce/f0/a9025139c69511600b653e4e3bed2b009e0c1d901405cd9d48c6eacfae93/pyobjc_framework_fsevents-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5e863d5b188b4a7bccdda0cc1993b451768ca7425dd866a90a79857d459a2052", size = 13156, upload-time = "2026-06-19T16:11:03.048Z" }, + { url = "https://files.pythonhosted.org/packages/1d/c2/bacaa5063b1e4ec270885fc0a268708c7cdf32b78b129fe753dca5daf470/pyobjc_framework_fsevents-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:11552274cdaa97ffa23aa1cb167a6cb03a4b0923330bb75e4757fdd38b61a796", size = 13160, upload-time = "2026-06-19T16:11:03.86Z" }, + { url = "https://files.pythonhosted.org/packages/3d/dd/1c376dd0b434ab505565e239e90fcb77a40d888b9fd052dda85b1820a1e6/pyobjc_framework_fsevents-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:11d13fc2b3bcaa2a746b1f28b95ce9fa9d9078a7da61d6cb4a2f6bc7c163f108", size = 13519, upload-time = "2026-06-19T16:11:04.773Z" }, + { url = "https://files.pythonhosted.org/packages/72/e4/a0d14951d97f1552201c28f644abaa3b77d221d717b5faeeecf69c7b0c6f/pyobjc_framework_fsevents-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c2b0df089a87971ea2bcc8ecf36696943a9358bb6704780308fc4b392375a8f1", size = 13057, upload-time = "2026-06-19T16:11:05.587Z" }, + { url = "https://files.pythonhosted.org/packages/bd/73/4e166b1c9e7e67e4462970a83bedcfa2d19aff1d0ee1b1db3bce91ccf9cc/pyobjc_framework_fsevents-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:7b47592f5ff58553aa2d9365aa7bba89b4bff3f3f2059fb7e0c3811712814219", size = 13513, upload-time = "2026-06-19T16:11:06.432Z" }, + { url = "https://files.pythonhosted.org/packages/7a/0c/38fa04fac209c1619ce8792a226bf2f0b50e6da52cd9135dd06e4f9ecc0a/pyobjc_framework_fsevents-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:b198330738637152999ba90ea49a744ecfcb0d64b1176dfdb6aed90bcd4ab926", size = 13063, upload-time = "2026-06-19T16:11:07.456Z" }, + { url = "https://files.pythonhosted.org/packages/72/e5/7758ebdc963f2ae70f60e78d5e5b6b9e9a7046d45fcec056ce1ce3793ce2/pyobjc_framework_fsevents-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:81f19060ae21a378b6e6c20cdf0ca1955199af45f3c3423b83dae53d2abb8c4c", size = 13537, upload-time = "2026-06-19T16:11:08.459Z" }, ] [[package]] name = "pyobjc-framework-fskit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/55/d00246d6e6d9756e129e1d94bc131c99eece2daa84b2696f6442b8a22177/pyobjc_framework_fskit-12.1.tar.gz", hash = "sha256:ec54e941cdb0b7d800616c06ca76a93685bd7119b8aa6eb4e7a3ee27658fc7ba", size = 42372, upload-time = "2025-11-14T10:15:30.411Z" } +sdist = { url = "https://files.pythonhosted.org/packages/59/38/4d47e4c2ef4a0e474469a715e5244b8e8dde89edde12c94551c5ed3ff7b0/pyobjc_framework_fskit-12.2.1.tar.gz", hash = "sha256:2607cef80fabe2394b30e1e3c10dc942c709afdbe7baacd3f86112b38add942b", size = 49577, upload-time = "2026-06-19T16:20:39.181Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/14/b76574b79afe10d6365915868c168c2c7d3825c6be212cadc55add85f319/pyobjc_framework_fskit-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:521423e7dfc4d2c5c262ba1db0adeb0e0b60976a2d74dec285642914f50252b2", size = 20228, upload-time = "2025-11-14T09:49:22.913Z" }, - { url = "https://files.pythonhosted.org/packages/e7/1a/5a0b6b8dc18b9dbcb7d1ef7bebdd93f12560097dafa6d7c4b3c15649afba/pyobjc_framework_fskit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:95b9135eea81eeed319dcca32c9db04b38688301586180b86c4585fef6b0e9cd", size = 20228, upload-time = "2025-11-14T09:49:25.324Z" }, - { url = "https://files.pythonhosted.org/packages/6d/a9/0c47469fe80fa14bc698bb0a5b772b44283cc3aca0f67e7f70ab45e09b24/pyobjc_framework_fskit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:50972897adea86508cfee33ec4c23aa91dede97e9da1640ea2fe74702b065be1", size = 20250, upload-time = "2025-11-14T09:49:28.065Z" }, - { url = "https://files.pythonhosted.org/packages/ce/99/eb30b8b99a4d62ff90b8aa66c6074bf6e2732705a3a8f086ba623fcc642f/pyobjc_framework_fskit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:528b988ea6af1274c81ff698f802bb55a12e32633862919dd4b303ec3b941fae", size = 20258, upload-time = "2025-11-14T09:49:30.893Z" }, - { url = "https://files.pythonhosted.org/packages/50/b6/0579127ff0ad03f6b8f26a7e856e5c9998c9b0efb7ac944b27e23136acf7/pyobjc_framework_fskit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:55e3e00e51bc33d43ed57efb9ceb252abfceba0bd563dae07c7b462da7add849", size = 20491, upload-time = "2025-11-14T09:49:33.249Z" }, - { url = "https://files.pythonhosted.org/packages/7f/4a/10a5d0a35ab18129289e0dfa2ab56469af2f1a9b2c8eeccd814d9c171e63/pyobjc_framework_fskit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:d856df1b12ef79803e11904571411ffe5720ceb8840f489ca7ec977c1d789e57", size = 20291, upload-time = "2025-11-14T09:49:35.636Z" }, - { url = "https://files.pythonhosted.org/packages/35/0b/cd618c1ea92f2bc8450bc3caa9c3f01ab54536a8d437b4df22f075b9d654/pyobjc_framework_fskit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1fc9ccf7a0f483ce98274ed89bc91226c3f1aaa32cb380b4fdd8b258317cc8fb", size = 20538, upload-time = "2025-11-14T09:49:37.962Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f7/261a47621193605942debd99feb2cddc33b96705e026c8ea3a98bbf20d9d/pyobjc_framework_fskit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a3e8de696fb30ff20cbe3a97158d2d56ca04bb7a96ffe5e258db4136d9ce71ca", size = 20589, upload-time = "2026-06-19T16:11:09.606Z" }, + { url = "https://files.pythonhosted.org/packages/f6/4b/3548dddea2ea1ca25e874165f33f2a68adf042701bd2fb297732d77d1516/pyobjc_framework_fskit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:29cda74aaa693513121805726bf458d2a2b60008216f363709c8d224dd0afaea", size = 20587, upload-time = "2026-06-19T16:11:10.63Z" }, + { url = "https://files.pythonhosted.org/packages/e4/6c/c43923e868f915f152cf67bacda50d6e8c44053f0b3b108a5c9a2edcbcb9/pyobjc_framework_fskit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:27deb130e5c3dab34689724a2da275ad83c579bc29da63904e0d2438b4d24ff9", size = 20605, upload-time = "2026-06-19T16:11:11.454Z" }, + { url = "https://files.pythonhosted.org/packages/83/d3/1148d635a3861e5f10d4cb23097ac797b0adb490922663acfad86b24e1f6/pyobjc_framework_fskit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b6094592998da2b3f61fd6a6599064e722ed91ef8a53e27b0c0cd080575cbb75", size = 20620, upload-time = "2026-06-19T16:11:12.278Z" }, + { url = "https://files.pythonhosted.org/packages/cf/fc/a9a70c58484674873d48343bec31996f913d06071dc4575d6cc0dcdaf93c/pyobjc_framework_fskit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:58f9c31ad24af40b7f37000c31c684a4be1c2057345a921cb0fe57b111d62063", size = 20848, upload-time = "2026-06-19T16:11:13.187Z" }, + { url = "https://files.pythonhosted.org/packages/95/0d/0936e7af5dfbaae8e1efc3aa9b2b8cca42ca2770991363216edf0e718cfb/pyobjc_framework_fskit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:375ac21b3c028d772bd37aa4fb669aca11f6bd9d6015526f172acd0ace3cc88e", size = 20653, upload-time = "2026-06-19T16:11:13.987Z" }, + { url = "https://files.pythonhosted.org/packages/8e/79/edecf1a9d9857ef8959aa3649572e29f990c864ecf4272712ae5d654b084/pyobjc_framework_fskit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:5992fb9c6e0b2b25ed322299a5cba1b239fa198712aa1a6f4e5ba00060350b12", size = 20912, upload-time = "2026-06-19T16:11:14.931Z" }, + { url = "https://files.pythonhosted.org/packages/18/b5/6b6522cc5ac39162514bcab45d1800fb8a0a915801921275ab67c9917ce6/pyobjc_framework_fskit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:f8dd8367162fa1048321183725c4117a0aa6bc1a54be9e289d03f64ba77c1916", size = 20658, upload-time = "2026-06-19T16:11:15.998Z" }, + { url = "https://files.pythonhosted.org/packages/cf/dc/bba39ec334208768f81c7d88733b7c557a142f164e23da0ecbe7d7826db0/pyobjc_framework_fskit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:f6f13449364bc465c9cc5d45c1280aeae9fec9f777d962a0604d456aec0bc6ba", size = 20917, upload-time = "2026-06-19T16:11:16.878Z" }, ] [[package]] name = "pyobjc-framework-gamecenter" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d2/f8/b5fd86f6b722d4259228922e125b50e0a6975120a1c4d957e990fb84e42c/pyobjc_framework_gamecenter-12.1.tar.gz", hash = "sha256:de4118f14c9cf93eb0316d49da410faded3609ce9cd63425e9ef878cebb7ea72", size = 31473, upload-time = "2025-11-14T10:15:33.38Z" } +sdist = { url = "https://files.pythonhosted.org/packages/16/5d/a2969d6cb14a9fe10a744a89daa2b671a4fb5dd25354e75511a65f7f8249/pyobjc_framework_gamecenter-12.2.1.tar.gz", hash = "sha256:70fff5b1c0ac9d622709b4deb8e0bdf47cfa43591f1438ce2c6099abd93bbfde", size = 32149, upload-time = "2026-06-19T16:20:40.006Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/e4/6a8223224d58e68ffef3809f6d6cf6bbabff89d373b27df9e56454f911b7/pyobjc_framework_gamecenter-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25f6a403352aaf8755a3874477fb70b1107ca28769e585541b52727ec50834be", size = 18827, upload-time = "2025-11-14T09:49:40.148Z" }, - { url = "https://files.pythonhosted.org/packages/ca/17/6491f9e96664e05ec00af7942a6c2f69217771522c9d1180524273cac7cb/pyobjc_framework_gamecenter-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:30943512f2aa8cb129f8e1abf951bf06922ca20b868e918b26c19202f4ee5cc4", size = 18824, upload-time = "2025-11-14T09:49:42.543Z" }, - { url = "https://files.pythonhosted.org/packages/16/ee/b496cc4248c5b901e159d6d9a437da9b86a3105fc3999a66744ba2b2c884/pyobjc_framework_gamecenter-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e8d6d10b868be7c00c2d5a0944cc79315945735dcf17eaa3fec1a7986d26be9b", size = 18868, upload-time = "2025-11-14T09:49:44.767Z" }, - { url = "https://files.pythonhosted.org/packages/cd/b4/d89eaeae9057e5fc6264ad47247739160650dfd02b1e85a84d45036f25f9/pyobjc_framework_gamecenter-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:16c885eae6ad29abb8d3ad17a9068c920f778622bff5401df31842fdbcebdd84", size = 18873, upload-time = "2025-11-14T09:49:47.072Z" }, - { url = "https://files.pythonhosted.org/packages/20/17/e5fe5a8f80288e61d70b6f9ccf05cffe6f1809736c11f172570af24216f6/pyobjc_framework_gamecenter-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9112d7aa8807d4b18a3f7190f310d60380640faaf405a1d0a9fd066c6420ae5b", size = 19154, upload-time = "2025-11-14T09:49:49.26Z" }, - { url = "https://files.pythonhosted.org/packages/7c/fb/5b4f1bd82e324f2fb598d3131f626744b6fbc9f87feda894bc854058de66/pyobjc_framework_gamecenter-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c452f65aaa102c11196193f44d41061ce33a66be2e9cf79d890d8eb611f84aa9", size = 18923, upload-time = "2025-11-14T09:49:51.474Z" }, - { url = "https://files.pythonhosted.org/packages/22/93/96305e0e96610a489604d15746a14f648b70dad44a8a7ca8a89ec31e12f4/pyobjc_framework_gamecenter-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:55352b0b4cf6803b3489a9dc63b6c177df462fbc4fee7902a4576af067e41714", size = 19214, upload-time = "2025-11-14T09:49:53.675Z" }, + { url = "https://files.pythonhosted.org/packages/f8/a9/d67b837630531ffea27c5d428028f9a0e14e9a23c23d78dac5a22a48606f/pyobjc_framework_gamecenter-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3b2f12f6122ad3872a675edd48784060a992794366b7a698ebaa2355e25968f1", size = 18841, upload-time = "2026-06-19T16:11:17.704Z" }, + { url = "https://files.pythonhosted.org/packages/45/38/f58a4e9ce61da62407a839991960301a61b8ca9568ee376d1e2334118df4/pyobjc_framework_gamecenter-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5aeb41658a2f0f3bc6b5a069f1c74324e3c37f662d54fbc458b0f6894581c006", size = 18843, upload-time = "2026-06-19T16:11:18.719Z" }, + { url = "https://files.pythonhosted.org/packages/a9/a5/b9a70c5e9e6ea55ffe9c7de8f3378473a4773d476aa25fe20bc5f0420c7d/pyobjc_framework_gamecenter-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e144d257b9461f8195512fb4b1a1cfd4cc0d603583ea039af7127a951ea7ad76", size = 18886, upload-time = "2026-06-19T16:11:19.608Z" }, + { url = "https://files.pythonhosted.org/packages/41/a5/a09890c3585740684c1a28620ef31bceaa4b455dd1aea59c95a1e30e6bba/pyobjc_framework_gamecenter-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:618fd07d19d715f172338a0d906f5a9e3dff67d3913e47d9033c0e7e4a6f5e88", size = 18889, upload-time = "2026-06-19T16:11:20.566Z" }, + { url = "https://files.pythonhosted.org/packages/ca/43/1b87ab10c0ccac15fa3daeb2bb0d915cb36c605d7c1ac24cd4eeaa3ba59b/pyobjc_framework_gamecenter-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:8929a2ca38c79debb975bcd4bb15b0a3bde93f367f90e05cc530badeeae82b54", size = 19172, upload-time = "2026-06-19T16:11:21.461Z" }, + { url = "https://files.pythonhosted.org/packages/95/7f/510426e4c61ee1e5a8435348a1cbba2074e23b42e51367acc62fbc2b3280/pyobjc_framework_gamecenter-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a4e8056403464b3ae14a8ecb8ad7024ad6d5672af9cc189436f709952f071e4d", size = 18940, upload-time = "2026-06-19T16:11:22.406Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ac/98cb3210fe91470c9401c0a9e8d5affeb2eb6d8a69364a8f86abf914c103/pyobjc_framework_gamecenter-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0e42fdbf26cde6d8e302d61a2f318de0d33c90060f2a8848722cf5ef0cb2f4c8", size = 19230, upload-time = "2026-06-19T16:11:23.325Z" }, + { url = "https://files.pythonhosted.org/packages/30/ae/10f18ce863f9d94813ab64859ba9481937713ae901da768c3869a75cdc9a/pyobjc_framework_gamecenter-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:b6dbe68dc2e7b7882ba82c295e913999d441caa9c23c868db3a28b351e20909c", size = 18933, upload-time = "2026-06-19T16:11:24.209Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ca/96ad6ebc2fd96f37915462c1852d47c2828156cdcb2ca8620e3afd797568/pyobjc_framework_gamecenter-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:5f61afc1269cee0518b7104c05131a6a652cf19ee2dd97471335c4943e0aac34", size = 19217, upload-time = "2026-06-19T16:11:25.034Z" }, ] [[package]] name = "pyobjc-framework-gamecontroller" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/21/14/353bb1fe448cd833839fd199ab26426c0248088753e63c22fe19dc07530f/pyobjc_framework_gamecontroller-12.1.tar.gz", hash = "sha256:64ed3cc4844b67f1faeb540c7cc8d512c84f70b3a4bafdb33d4663a2b2a2b1d8", size = 54554, upload-time = "2025-11-14T10:15:37.591Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/94/3f01f6a15b892402e9ec5dd5530e53ce7feab236c374236059ab10961ee7/pyobjc_framework_gamecontroller-12.2.1.tar.gz", hash = "sha256:d40667869da0ef5d9905b4b3c365e275f731758bc0b09f10f7dfba579b1ee7d0", size = 65320, upload-time = "2026-06-19T16:20:40.792Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/20/998ad37fe6640b1ccb91bb9bb99e9baefd95238d8b2de43d4a0e07d5b80a/pyobjc_framework_gamecontroller-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:38b290dfb8f5999c599b883fd13d3cade78f26111d010bc003b19ee400182aa5", size = 20916, upload-time = "2025-11-14T09:49:56.33Z" }, - { url = "https://files.pythonhosted.org/packages/e4/dc/1d8bd4845a46cb5a5c1f860d85394e64729b2447bbe149bb33301bc99056/pyobjc_framework_gamecontroller-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2633c2703fb30ce068b2f5ce145edbd10fd574d2670b5cdee77a9a126f154fec", size = 20913, upload-time = "2025-11-14T09:49:58.863Z" }, - { url = "https://files.pythonhosted.org/packages/06/28/9f03d0ef7c78340441f78b19fb2d2c952af04a240da5ed30c7cf2d0d0f4e/pyobjc_framework_gamecontroller-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:878aa6590c1510e91bfc8710d6c880e7a8f3656a7b7b6f4f3af487a6f677ccd5", size = 20949, upload-time = "2025-11-14T09:50:01.608Z" }, - { url = "https://files.pythonhosted.org/packages/9d/7c/4553f7c37eedef4cd2e6f0d9b6c63da556ed2fbe7dd2a79735654e082932/pyobjc_framework_gamecontroller-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2105b4309222e538b9bccf906d24f083c3cbf1cd1c18b3ae6876e842e84d2163", size = 20956, upload-time = "2025-11-14T09:50:04.123Z" }, - { url = "https://files.pythonhosted.org/packages/ad/ed/19e27404ce87256642431a60914ef2cb0578142727981714d494970e21c3/pyobjc_framework_gamecontroller-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a772cc9fbe09bcc601abcc36855a70cbad4640bd3349c1d611c09fcc7e45b73b", size = 21226, upload-time = "2025-11-14T09:50:06.462Z" }, - { url = "https://files.pythonhosted.org/packages/38/0a/4386a2436b7ae4df62c30b8a96d89be15c6c9e302b89fc7e7cd19ba3429c/pyobjc_framework_gamecontroller-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:3404a6488bb498989304aa87ce6217c973505a627b6eb9ae7884fd804569b8e4", size = 21005, upload-time = "2025-11-14T09:50:08.894Z" }, - { url = "https://files.pythonhosted.org/packages/c1/94/7e45309ddb873b7ea4ac172e947021a9ecdb7dc0b58415d1574abcd87cce/pyobjc_framework_gamecontroller-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:f4a16cd469aec142ec8e199d52a797f771441b3ea7198d21f6d75c2cc218b4e6", size = 21266, upload-time = "2025-11-14T09:50:11.271Z" }, + { url = "https://files.pythonhosted.org/packages/c0/30/b5776fcb8fe31ca1afdf7e1684b818f3ef55e21b5a22ca33deccf14148c3/pyobjc_framework_gamecontroller-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:97b6e8d9d27b7e129c67114797f0f74939a052253d9dafa32f698acf4a9e3a24", size = 21501, upload-time = "2026-06-19T16:11:25.898Z" }, + { url = "https://files.pythonhosted.org/packages/57/7d/074428109273999cbbd5e4484814207b284f29457a0b065cd97b99f4b03c/pyobjc_framework_gamecontroller-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ef83260973e620207b35bd3a4d68333d76c2e5463f0ca8405efd8327d27f8946", size = 21508, upload-time = "2026-06-19T16:11:26.899Z" }, + { url = "https://files.pythonhosted.org/packages/5e/97/a55cac5ba675a43b280454a50e377fd9733629d0afcf0bde51c042dd20b2/pyobjc_framework_gamecontroller-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:68d074ef1fbe4feb258e622f58764e79accd006ee39e069f8c50252b0657dff0", size = 21527, upload-time = "2026-06-19T16:11:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/77/c9/55faa145c569b45b1f82d0437e89e84c5d42bcf3b822429eccfdb89643b7/pyobjc_framework_gamecontroller-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:94af74d0229d5ef06e71d2d005a56018930827435778c946cf28ee98914bd0fa", size = 21543, upload-time = "2026-06-19T16:11:28.656Z" }, + { url = "https://files.pythonhosted.org/packages/01/29/115e4e4ce6d34095fdbc689c1963b6a8688beca380ca5db43b9be6ab3175/pyobjc_framework_gamecontroller-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:bb5704a35e501b8902e6210f1f11c7d7f55954dd906847cd6da2390b7176d357", size = 21784, upload-time = "2026-06-19T16:11:29.495Z" }, + { url = "https://files.pythonhosted.org/packages/71/b5/c8e9c80af4bc992bf06d1578736060fbb9955a174528a570aa0ed4172d48/pyobjc_framework_gamecontroller-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:3602c85180be0bc1bcade88f802848e4649850115ba180923c8211311be62430", size = 21559, upload-time = "2026-06-19T16:11:30.468Z" }, + { url = "https://files.pythonhosted.org/packages/bf/23/fcf75429dd30f4029ec296cc62e802fd98ec3aa965cffd09dc5f76730d8a/pyobjc_framework_gamecontroller-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6a53c426ea670e45027a599c50112af2c4d557ac74e53d553a0f78167f5d7aba", size = 21857, upload-time = "2026-06-19T16:11:31.243Z" }, + { url = "https://files.pythonhosted.org/packages/44/95/b54d79a802da063dfa7621638f7a4c7beaffd849147fb82bfcf6483cfbef/pyobjc_framework_gamecontroller-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:c78519ed3422c5dee9be57a48242b81b482d5339528c7f1362e86efa7231307d", size = 21560, upload-time = "2026-06-19T16:11:32.055Z" }, + { url = "https://files.pythonhosted.org/packages/4d/aa/b349c94923e0f30ae4224078280662a76544630cc1bdb0f72a6eac93e208/pyobjc_framework_gamecontroller-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:6a63603e317168ae229af7ae7e5ed38644f32732127bdbb6e698bf6e787deb3e", size = 21851, upload-time = "2026-06-19T16:11:32.952Z" }, ] [[package]] name = "pyobjc-framework-gamekit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-quartz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/52/7b/d625c0937557f7e2e64200fdbeb867d2f6f86b2f148b8d6bfe085e32d872/pyobjc_framework_gamekit-12.1.tar.gz", hash = "sha256:014d032c3484093f1409f8f631ba8a0fd2ff7a3ae23fd9d14235340889854c16", size = 63833, upload-time = "2025-11-14T10:15:42.842Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a0/f3/6a4ae01c6a8e0e5b74e818694139edf9ebd395bbe04d275fb5f5e73ac919/pyobjc_framework_gamekit-12.2.1.tar.gz", hash = "sha256:e5e90dfa0eba5215406710d636c08ee9aa4ba886d9e0bf11849247b161aef1da", size = 82427, upload-time = "2026-06-19T16:20:41.641Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/dc/0cd33dcfc9730a8ba22e848d431a7212a7aa0b4559101c389ae9bab77c7e/pyobjc_framework_gamekit-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b15492801dafcbb569dee8c58d26e16ce06b33912872e85dfd50cf39b8f98f1e", size = 22457, upload-time = "2025-11-14T09:50:13.772Z" }, - { url = "https://files.pythonhosted.org/packages/06/47/d3b78cf57bc2d62dc1408aaad226b776d167832063bbaa0c7cc7a9a6fa12/pyobjc_framework_gamekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb263e90a6af3d7294bc1b1ea5907f8e33bb77d62fb806696f8df7e14806ccad", size = 22463, upload-time = "2025-11-14T09:50:16.444Z" }, - { url = "https://files.pythonhosted.org/packages/c4/05/1c49e1030dc9f2812fa8049442158be76c32f271075f4571f94e4389ea86/pyobjc_framework_gamekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2eee796d5781157f2c5684f7ef4c2a7ace9d9b408a26a9e7e92e8adf5a3f63d7", size = 22493, upload-time = "2025-11-14T09:50:19.129Z" }, - { url = "https://files.pythonhosted.org/packages/8a/7d/65b16b18dc15283d6f56df5ebf30ae765eaf1f8e67e6eb30539581fe9749/pyobjc_framework_gamekit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ad14393ac496a4cb8008b6172d536f5c07fc11bb7b00fb541b044681cf9e4a34", size = 22505, upload-time = "2025-11-14T09:50:21.989Z" }, - { url = "https://files.pythonhosted.org/packages/98/19/433595ff873684e0df73067b32aba6fc4b360d3ed552444115285a5d969a/pyobjc_framework_gamekit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:97e41b4800be30cb3e6a88007b6f741cb18935467d1631537ac23b918659900e", size = 22798, upload-time = "2025-11-14T09:50:24.583Z" }, - { url = "https://files.pythonhosted.org/packages/05/39/4a9a51cae1ced9d0f74ca6c68e7304b9b1c2d184fed11b736947535ba59f/pyobjc_framework_gamekit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:14080fdea98ec01c3e06260f1f5b31aaf59c78c2872fe8b843e17fd0ce151fa4", size = 22536, upload-time = "2025-11-14T09:50:27.675Z" }, - { url = "https://files.pythonhosted.org/packages/0e/0f/282f10f5ebd427ec1774ef639a467e5b26c5174f473e8da24ac084139a7c/pyobjc_framework_gamekit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9867991539dfc70b52f0ee8ce19bc661d0706c7f64c35417e97ca7c90e3158c0", size = 22845, upload-time = "2025-11-14T09:50:30.287Z" }, + { url = "https://files.pythonhosted.org/packages/56/26/a59b4536e08f4df46dacdc9d9bf03fbe0a3083a2dc3ef6ffdc44cec20933/pyobjc_framework_gamekit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd502bd1e8251c1b7a17d802034be0df7cab8b9aca090676868109c198e5ddd1", size = 22549, upload-time = "2026-06-19T16:11:33.806Z" }, + { url = "https://files.pythonhosted.org/packages/10/1d/8f369bab83b5fc446de73164e612fa65ea96d83e0c3cd4fe5fd48339c555/pyobjc_framework_gamekit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7015d24515e6c069c1cd15c7f2921961c4b9bedc037c3475d068b3a9ce40d03e", size = 22554, upload-time = "2026-06-19T16:11:34.895Z" }, + { url = "https://files.pythonhosted.org/packages/ee/88/5c93226453c925d22a102df0aef522aac0786168171e1d49b3a15f5bd5fa/pyobjc_framework_gamekit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d29a5ee705be7eb0e0fe46bd319462f8bb2f08c8761d65f0e22957f35b6db72a", size = 22586, upload-time = "2026-06-19T16:11:35.828Z" }, + { url = "https://files.pythonhosted.org/packages/24/d3/467011b105702bfb1e1145bb20863f5c72c49b2ac8f206eb16ed2df76c87/pyobjc_framework_gamekit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7fbfa5eeb6c97183aa65f88a8d904bffe79b56db2352dc03ac0a1104c8a6cc56", size = 22599, upload-time = "2026-06-19T16:11:36.753Z" }, + { url = "https://files.pythonhosted.org/packages/ef/c4/4554e23d5115aa93d1d375c3e17a44c6363174e337754404ecc13d7dd367/pyobjc_framework_gamekit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:21b5ca685cfcc64ed05f5dd7a07b6ee852a6bebe251980185e795cc7117fa949", size = 22886, upload-time = "2026-06-19T16:11:37.66Z" }, + { url = "https://files.pythonhosted.org/packages/cb/91/42aeffba8651ab0ac7e69016d8a27f11f441bb3282568a521a2de1ce8ef3/pyobjc_framework_gamekit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a95830e02163542dba2a4573062b151e298932cdfeef2cf48cc2cc63fca97e7e", size = 22629, upload-time = "2026-06-19T16:11:38.586Z" }, + { url = "https://files.pythonhosted.org/packages/71/d9/b5682acbbdaeb87778aeff55ba1b365a94729f6d0ba36c75d9ed71cbc01a/pyobjc_framework_gamekit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a4f7fc8e01c9d80edf3d7792f2c35c2eedf67246d12242eea2b480863b642708", size = 22939, upload-time = "2026-06-19T16:11:39.4Z" }, + { url = "https://files.pythonhosted.org/packages/97/cb/cf33218de97a97824dc7ccb71cf7d8b274d1cef58e2209a18301c010526e/pyobjc_framework_gamekit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:239aadec9465eafba7be40293192b79ac572dd0f97c41f3b5c1a2574f85be22a", size = 22619, upload-time = "2026-06-19T16:11:40.629Z" }, + { url = "https://files.pythonhosted.org/packages/e2/53/b20606954747d99f9f7f86e2ea1cdb78419c087839b74bd47414fb47d623/pyobjc_framework_gamekit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:b1a9873076cb61f19e792b5a87adb852b21353f6dea675a34c03512de18ecae6", size = 22937, upload-time = "2026-06-19T16:11:41.498Z" }, ] [[package]] name = "pyobjc-framework-gameplaykit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-spritekit" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e2/11/c310bbc2526f95cce662cc1f1359bb11e2458eab0689737b4850d0f6acb7/pyobjc_framework_gameplaykit-12.1.tar.gz", hash = "sha256:935ebd806d802888969357946245d35a304c530c86f1ffe584e2cf21f0a608a8", size = 41511, upload-time = "2025-11-14T10:15:46.529Z" } +sdist = { url = "https://files.pythonhosted.org/packages/12/fa/df67a7bd14b44808060dcf82da26492cd7365bd6759d9437a004fb761a32/pyobjc_framework_gameplaykit-12.2.1.tar.gz", hash = "sha256:6ef81407e241016853cfdc6e580503d2d75a452ab0028f5c83390f102685c853", size = 50742, upload-time = "2026-06-19T16:20:42.656Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/0f/9ff71cf1871603d12f19a11b6287c2d6dff9d250efff5e40453003bac796/pyobjc_framework_gameplaykit-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1af3963897c1ff2dfbcc6782ff162d6bf34488f6736e60cfc73411fdfaba2b31", size = 13129, upload-time = "2025-11-14T09:50:32.249Z" }, - { url = "https://files.pythonhosted.org/packages/3b/84/7a4a2c358770f5ffdb6bdabb74dcefdfa248b17c250a7c0f9d16d3b8d987/pyobjc_framework_gameplaykit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b2fb27f9f48c3279937e938a0456a5231b5c89e53e3199b9d54009a0bbd1228a", size = 13125, upload-time = "2025-11-14T09:50:34.384Z" }, - { url = "https://files.pythonhosted.org/packages/35/1f/e5fe404f92ec0f9c8c37b00d6cb3ba96ee396c7f91b0a41a39b64bfc2743/pyobjc_framework_gameplaykit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:309b0d7479f702830c9be92dbe5855ac2557a9d23f05f063caf9d9fdb85ff5f0", size = 13150, upload-time = "2025-11-14T09:50:36.884Z" }, - { url = "https://files.pythonhosted.org/packages/08/c9/d90505bed51b487d7a8eff54a51dda0d9b8e2d76740a99924b5067b58062/pyobjc_framework_gameplaykit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:947911902e0caf1d82dedae8842025891d57e91504714a7732dc7c4f80d486a1", size = 13164, upload-time = "2025-11-14T09:50:39.251Z" }, - { url = "https://files.pythonhosted.org/packages/ad/42/9d5ac9a4398f1d1566ce83f16f68aeaa174137de78bec4515ed927c24530/pyobjc_framework_gameplaykit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:3218de7a56ac63a47ab7c50ce30592d626759196c937d20426a0ea74091e0614", size = 13383, upload-time = "2025-11-14T09:50:41.227Z" }, - { url = "https://files.pythonhosted.org/packages/38/a5/e10365b7287eb4a8e83275f04942d085f8e87da0a65c375df14a78df23c8/pyobjc_framework_gameplaykit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:786036bdf266faf196b29b23e123faf76df5f3e90f113e2a7cdd4d04af071dc2", size = 13170, upload-time = "2025-11-14T09:50:43.238Z" }, - { url = "https://files.pythonhosted.org/packages/a3/65/eb00ab56a00f048d1638bb819f61d3e8221d72088947070ac9367bc17efa/pyobjc_framework_gameplaykit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d58c0cc671ac8b80a4bf702efabbb9c0a42020999b87efed162b71830db005a9", size = 13363, upload-time = "2025-11-14T09:50:45.394Z" }, + { url = "https://files.pythonhosted.org/packages/51/e0/41b939506372e0df236ff456cf0f185a386c3fdb89401ca51958839a0f95/pyobjc_framework_gameplaykit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:349a8c77ad13dfa5eecc45e954cec8d8e1d873cb0e9ab16a65a865b536dcf62e", size = 13609, upload-time = "2026-06-19T16:11:42.343Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0a/552898c2b5585fcfc138b9e62bec6b2d0e85ac1147c59c4544dc45721971/pyobjc_framework_gameplaykit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3cfd99b4a37df41a72f7b15fbbd00abecb11021745bae4876e2d8517f75317a5", size = 13612, upload-time = "2026-06-19T16:11:43.358Z" }, + { url = "https://files.pythonhosted.org/packages/1b/23/d64fe0c702ff3ee8644bcde26e2c7d9062b19b7d40a60134ba2b4b353b45/pyobjc_framework_gameplaykit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e1ef41d991e29358a26c92916bcedf722568e50ee66eab3088d2e88b9d77d9e3", size = 13638, upload-time = "2026-06-19T16:11:44.269Z" }, + { url = "https://files.pythonhosted.org/packages/b5/27/4e0e8260e2f3b4a3e99ea756166c9d7b12a971d470376bf417aa4edea133/pyobjc_framework_gameplaykit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3b51b11e20066f10532c225262d05fa6711e49685f5ecd9519b6c12ef69efa68", size = 13646, upload-time = "2026-06-19T16:11:45.234Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/9f36ade305fcf88bc077924404df1f96ec61e6e6dae2cb7625abca1a077c/pyobjc_framework_gameplaykit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:95b06ed97e4e636140c5d28f507a298974413b501599cdcd2bb80fbc8fb85ae1", size = 13863, upload-time = "2026-06-19T16:11:46.257Z" }, + { url = "https://files.pythonhosted.org/packages/80/b9/143bdf083c3c4cf52430518a97ae515aac2fbd50feacfbab1fe486c1bb9f/pyobjc_framework_gameplaykit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a29a178fe0a64eeb959e1bb719248f7e9e44704b2b0a2f03b4ec6d951750f64c", size = 13653, upload-time = "2026-06-19T16:11:47.085Z" }, + { url = "https://files.pythonhosted.org/packages/7e/44/e98e8f3f8c131ceed736267f9067835a54ac9dc1d0ffff37dd5634722d6a/pyobjc_framework_gameplaykit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9a4810ad39a1d6729ac164803513043dde33842c19e91c158e645379aab5f873", size = 13850, upload-time = "2026-06-19T16:11:47.94Z" }, + { url = "https://files.pythonhosted.org/packages/2c/74/a60cd84430487a8190a2e463d47f5ebdbe81901c58fe6042410b6ed062d8/pyobjc_framework_gameplaykit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:f8af54bedd499db1daa54174c0badcb48162601ecb71bfd97c4f7fe77d3ce007", size = 13647, upload-time = "2026-06-19T16:11:48.845Z" }, + { url = "https://files.pythonhosted.org/packages/70/64/ac578939f804b6b7419e7fe482c353959274db818c36291740f6ca78fd4f/pyobjc_framework_gameplaykit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:720523342cdd978704fd2fea21312f8256d837dd64af2a04c683fbd2daa48995", size = 13842, upload-time = "2026-06-19T16:11:49.652Z" }, ] [[package]] name = "pyobjc-framework-gamesave" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1b/1f/8d05585c844535e75dbc242dd6bdfecfc613d074dcb700362d1c908fb403/pyobjc_framework_gamesave-12.1.tar.gz", hash = "sha256:eb731c97aa644e78a87838ed56d0e5bdbaae125bdc8854a7772394877312cc2e", size = 12654, upload-time = "2025-11-14T10:15:48.344Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/96/c4c604170d28f2a2cddb1217dd0d8340ef9435ab2cf1042d85b45a14c2ed/pyobjc_framework_gamesave-12.2.1.tar.gz", hash = "sha256:d317d37f2716194e61cc91e855d6054c3c2b7ceaa82aaeff9c688d9f2cc43a42", size = 13238, upload-time = "2026-06-19T16:20:43.664Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/ec/93d48cb048a1b35cea559cc9261b07f0d410078b3af029121302faa410d0/pyobjc_framework_gamesave-12.1-py2.py3-none-any.whl", hash = "sha256:432e69f8404be9290d42c89caba241a3156ed52013947978ac54f0f032a14ffd", size = 3689, upload-time = "2025-11-14T09:50:47.263Z" }, + { url = "https://files.pythonhosted.org/packages/48/4c/8fb1226802cf960ceaac85bbe7bb85aeb949f9c9ae477ecd35876a63eceb/pyobjc_framework_gamesave-12.2.1-py2.py3-none-any.whl", hash = "sha256:5d632ea0b62ef55b1b0f62e8ab6b9800bc14ca78f5a1bb629c95b28376b82379", size = 3750, upload-time = "2026-06-19T16:11:50.468Z" }, ] [[package]] name = "pyobjc-framework-healthkit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/af/67/436630d00ba1028ea33cc9df2fc28e081481433e5075600f2ea1ff00f45e/pyobjc_framework_healthkit-12.1.tar.gz", hash = "sha256:29c5e5de54b41080b7a4b0207698ac6f600dcb9149becc9c6b3a69957e200e5c", size = 91802, upload-time = "2025-11-14T10:15:54.661Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/a6/a7f9f6d525c19ff1b2533220058cc70ca5bf3976a6adbe2efaa66ff3a349/pyobjc_framework_healthkit-12.2.1.tar.gz", hash = "sha256:d0a8c956746e8705edbe76a046e8c17ab6d7ae2603d8436e4477d30573acb457", size = 116224, upload-time = "2026-06-19T16:20:44.519Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/d9/2bbcb7816a46ede9bc99239208ec4787188ed522a7a2983483dd8b72acea/pyobjc_framework_healthkit-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f1850c4f079374aaa3e91f22ab22b83817872460cc3a9c5310fe18c6140dc307", size = 20791, upload-time = "2025-11-14T09:50:49.708Z" }, - { url = "https://files.pythonhosted.org/packages/1e/37/b23d3c04ee37cbb94ff92caedc3669cd259be0344fcf6bdf1ff75ff0a078/pyobjc_framework_healthkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e67bce41f8f63c11000394c6ce1dc694655d9ff0458771340d2c782f9eafcc6e", size = 20785, upload-time = "2025-11-14T09:50:52.152Z" }, - { url = "https://files.pythonhosted.org/packages/65/87/bb1c438de51c4fa733a99ce4d3301e585f14d7efd94031a97707c0be2b46/pyobjc_framework_healthkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:15b6fc958ff5de42888b18dffdec839cb36d2dd8b82076ed2f21a51db5271109", size = 20799, upload-time = "2025-11-14T09:50:54.531Z" }, - { url = "https://files.pythonhosted.org/packages/40/f8/4bbaf71a11a99649a4aa9f4ac28d94a2bf357cd4c88fba91439000301cf0/pyobjc_framework_healthkit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c57ba8e3cce620665236d9f6b77482c9cfb16fe3372c8b6bbabc50222fb1b790", size = 20812, upload-time = "2025-11-14T09:50:57.238Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ef/4461f34f42e8f78b941161df7045d27e48d73d203847a21921b5a36ffe68/pyobjc_framework_healthkit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b2a0890d920015b40afe8ecda6c541840d20b4ae6c7f2daaa9efbaafae8cc1bc", size = 20980, upload-time = "2025-11-14T09:50:59.644Z" }, - { url = "https://files.pythonhosted.org/packages/f2/6f/99933449e0cb8d6424de8e709fe423427efc634f75930885a723debcce11/pyobjc_framework_healthkit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:1f10a3abf6d5a326192e96343e7e1d9d16efa0cf4b39266335e385455680bc69", size = 20867, upload-time = "2025-11-14T09:51:02.359Z" }, - { url = "https://files.pythonhosted.org/packages/63/ad/7ea9a3bc54c092efb5dbf9b571dd6a1a064712ce434e80c42e2830f88bb5/pyobjc_framework_healthkit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:54f02b673b2ea8ec8cfa17cac0c377435cbf89a15d5539d4699fa8b12abc42de", size = 21039, upload-time = "2025-11-14T09:51:04.699Z" }, + { url = "https://files.pythonhosted.org/packages/65/7f/3941417fab8de6528dc9664ca03bc8cb8753d20d3835f0f2cea8d714b104/pyobjc_framework_healthkit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:328bce901eb8f2f55c8b764abc0b387992ef3b8a27e1a02fc8d658742a3d490c", size = 22058, upload-time = "2026-06-19T16:11:51.485Z" }, + { url = "https://files.pythonhosted.org/packages/c4/4b/6d6cda4263f3a0952ddf7e750023c6af71bec74b18df77a0915a827784a8/pyobjc_framework_healthkit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e4d0da98b552ba33532f37ab718bb3b77e15d3c0c1ab426e6d01522d2d256244", size = 22060, upload-time = "2026-06-19T16:11:52.762Z" }, + { url = "https://files.pythonhosted.org/packages/92/f1/09a7ffbe1dec87ca60cb63c93b81b3f8e6b168e4e24c0fb244809a71bdee/pyobjc_framework_healthkit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2c945edc98952bd7d1abad876ed7e36c3442ea6e6c1ec6718ab7554b8a47fba1", size = 22070, upload-time = "2026-06-19T16:11:53.843Z" }, + { url = "https://files.pythonhosted.org/packages/94/91/31d4365275ce87ce1a0069a25ba1bc33e0ad205eacfe251633dcebd92171/pyobjc_framework_healthkit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0408b552eb98319d83a106b25944d645dbe9927dff0ce9c701c25d472672aa63", size = 22083, upload-time = "2026-06-19T16:11:54.717Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c1/213460cfcc6f8f549c942873ed984f6aaa584c01f7874f012fb073693575/pyobjc_framework_healthkit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:15d6a4a80a85e29d38371e98f70656a6b378cadbd9430579b9c15a46b81a0a2d", size = 22251, upload-time = "2026-06-19T16:11:55.52Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e6/bc39d024ae5bd4ad51a4903e8aa8707d55f4707c5bb978233ed5e7caf8df/pyobjc_framework_healthkit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:d8a44d60387e7e2d54e1d7b613ef93e5701837799bfd962d8d7df08b060d43d7", size = 22135, upload-time = "2026-06-19T16:11:56.333Z" }, + { url = "https://files.pythonhosted.org/packages/96/7d/35d5265a5da088437c498b7edfd05ad4ea77140ed570844fe6f924d5e929/pyobjc_framework_healthkit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9e6fa59e078ca6c4df56eb987a2c6267ef094b5c6a4b386865535da6e3ac2f05", size = 22315, upload-time = "2026-06-19T16:11:57.218Z" }, + { url = "https://files.pythonhosted.org/packages/a8/90/bdb840292838021ecda74a9498276a159cc0b232dad21ae09c98495fbadf/pyobjc_framework_healthkit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:f2d2278f3966c7ddf1f828461f11a94373ac3f25a6d052254a2fb0761cf62ea4", size = 22130, upload-time = "2026-06-19T16:11:58.095Z" }, + { url = "https://files.pythonhosted.org/packages/d6/07/d971feb74d6faa189bbb9304745a85a823828a23cfb3a967b7d21c4cc886/pyobjc_framework_healthkit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:849a6abf2c895db3dd160fcf31b63c229eabbff5cdacabebc0f63e8e49f3c7b9", size = 22300, upload-time = "2026-06-19T16:11:58.924Z" }, ] [[package]] name = "pyobjc-framework-imagecapturecore" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/a1/39347381fc7d3cd5ab942d86af347b25c73f0ddf6f5227d8b4d8f5328016/pyobjc_framework_imagecapturecore-12.1.tar.gz", hash = "sha256:c4776c59f4db57727389d17e1ffd9c567b854b8db52198b3ccc11281711074e5", size = 46397, upload-time = "2025-11-14T10:15:58.541Z" } +sdist = { url = "https://files.pythonhosted.org/packages/57/fb/792b0945e2fb9de91b388e56603f5e6548573511cb40555ca0047d7f798b/pyobjc_framework_imagecapturecore-12.2.1.tar.gz", hash = "sha256:c1568be8cc0fd06046f7e344634951b3c02af3ad4f8a35fe1e2fecd9547b7042", size = 53435, upload-time = "2026-06-19T16:20:45.418Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/f0/249b7897425f3dbcde1df6c3e0b23112966ff026125747030e3e66e1ba2d/pyobjc_framework_imagecapturecore-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7cb050fcbfc3cd20cf0427523f080939f6d815cd85f58a2ebcac930567764384", size = 15986, upload-time = "2025-11-14T09:51:07.029Z" }, - { url = "https://files.pythonhosted.org/packages/b4/6b/b34d5c9041e90b8a82d87025a1854b60a8ec2d88d9ef9e715f3a40109ed5/pyobjc_framework_imagecapturecore-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:64d1eb677fe5b658a6b6ed734b7120998ea738ca038ec18c4f9c776e90bd9402", size = 15983, upload-time = "2025-11-14T09:51:09.978Z" }, - { url = "https://files.pythonhosted.org/packages/50/13/632957b284dec3743d73fb30dbdf03793b3cf1b4c62e61e6484d870f3879/pyobjc_framework_imagecapturecore-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a2777e17ff71fb5a327a897e48c5c7b5a561723a80f990d26e6ed5a1b8748816", size = 16012, upload-time = "2025-11-14T09:51:12.058Z" }, - { url = "https://files.pythonhosted.org/packages/f9/32/2d936320147f299d83c14af4eb8e28821d226f2920d2df3f7a3b3daf61dc/pyobjc_framework_imagecapturecore-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2ae57b54e7b92e2efb40b7346e12d7767f42ed2bcf8f050cd9a88a9926a1e387", size = 16025, upload-time = "2025-11-14T09:51:14.387Z" }, - { url = "https://files.pythonhosted.org/packages/09/5a/7bfa64b0561c7eb858dac9b2e0e3a50000e9dc50416451e8ae40b316eb8f/pyobjc_framework_imagecapturecore-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:08f8ed5434ee5cc7605e71227c284c0c3fa0a32a6d83e1862e7870543a65a630", size = 16213, upload-time = "2025-11-14T09:51:16.531Z" }, - { url = "https://files.pythonhosted.org/packages/50/fc/feb035f2866050737f8315958e31cfe2bf5d6d4d046a7268d28b94cd8155/pyobjc_framework_imagecapturecore-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b7a7feeb0b53f5b0e0305c5c41f6b722d5f8cfca506c49678902244cd339ac10", size = 16028, upload-time = "2025-11-14T09:51:18.573Z" }, - { url = "https://files.pythonhosted.org/packages/38/58/58c3d369d90077eff896c234755ac6814b3fa9f00caeca2ec391555b1a22/pyobjc_framework_imagecapturecore-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1fcfcc907673331cc4be3ea63fce6e1346620ac74661a19566dfcdf855bb8eee", size = 16207, upload-time = "2025-11-14T09:51:20.616Z" }, + { url = "https://files.pythonhosted.org/packages/d7/20/7e4121e5b48a6f59d90b4dab8f149dbd22e2ec22e3004a429122f9bdf18e/pyobjc_framework_imagecapturecore-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fc347400caf54f01b512bb21d6e9d693537fe7515a65a0281efa34c09eb9e3d5", size = 16043, upload-time = "2026-06-19T16:11:59.842Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b3/05c58659d8c52516d5ed45cd49b49c574e68ec3c6131e34fc3193ea5fd0b/pyobjc_framework_imagecapturecore-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7d083a0b180b833cc06b6d5419d657b9b96629f24def0c666147acd95ec6c2d4", size = 16042, upload-time = "2026-06-19T16:12:00.891Z" }, + { url = "https://files.pythonhosted.org/packages/69/cb/f542c35ad49f9f1e33b6e3d43860be84da2a24298579b7f508dd1f0a7e2a/pyobjc_framework_imagecapturecore-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:34165b84443a98ce8b7f12c12603cfbebe09bacc06f33ae1ed3774b649d0cec7", size = 16064, upload-time = "2026-06-19T16:12:01.886Z" }, + { url = "https://files.pythonhosted.org/packages/9e/7a/fc4bf6e0973ef553be039d5651e22ea5802f8095154f41310c1df1a9bb2d/pyobjc_framework_imagecapturecore-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8f6882e96cf748096fce49adcddeb882090cb1adb9c6699d1b869866053365d9", size = 16081, upload-time = "2026-06-19T16:12:02.86Z" }, + { url = "https://files.pythonhosted.org/packages/7f/bb/a7d080589fe074ec317b4022603f60adbab4dd3f671dc16a7516157e16d5/pyobjc_framework_imagecapturecore-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b6339dd78448762e1c017698b32736b809048ae3b0e51d1c625c03f47e59de52", size = 16268, upload-time = "2026-06-19T16:12:03.729Z" }, + { url = "https://files.pythonhosted.org/packages/e8/a5/c502525300c68887985ace05015c4d4a3fa2ba657bf13e6f4e404fda9918/pyobjc_framework_imagecapturecore-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:f6ab44c9e3005b1a30de18af8aacd65b8f1a4117ea1017efc72157b3e53914e6", size = 16080, upload-time = "2026-06-19T16:12:04.599Z" }, + { url = "https://files.pythonhosted.org/packages/ca/c8/d17b4a5bd73bdf388e55c6ae4c6c5467be95324bb114fffd826489c47a58/pyobjc_framework_imagecapturecore-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:900ccad9c3ab452017bd3a184ecc7a866d1a8fc599c2e704fa8b62d7f75539c2", size = 16260, upload-time = "2026-06-19T16:12:05.473Z" }, + { url = "https://files.pythonhosted.org/packages/11/3d/07f1ac5f135b2a88303a429cefb27102d8f5311b2446505b0dc32c37cec1/pyobjc_framework_imagecapturecore-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:ca3bff1d31e453b182c7ff7e91e6cf14e6564ff772371a5d53956204f903278e", size = 16074, upload-time = "2026-06-19T16:12:06.358Z" }, + { url = "https://files.pythonhosted.org/packages/24/84/b7cbd69f0fe6041368ca6b9f14f1a7de6b75520880d79ea10363745e9793/pyobjc_framework_imagecapturecore-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:fb11ecc92fc4e1df467d662afa1a26c89c026daa44b270fe324045b9e38d5c6d", size = 16255, upload-time = "2026-06-19T16:12:07.253Z" }, ] [[package]] name = "pyobjc-framework-inputmethodkit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5d/b8/d33dd8b7306029bbbd80525bf833fc547e6a223c494bf69a534487283a28/pyobjc_framework_inputmethodkit-12.1.tar.gz", hash = "sha256:f63b6fe2fa7f1412eae63baea1e120e7865e3b68ccfb7d8b0a4aadb309f2b278", size = 23054, upload-time = "2025-11-14T10:16:01.464Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/9c/2bb0c543cfb7a1d38f4750b8b54c57663ec7c54f07499a3218c2a16e3e54/pyobjc_framework_inputmethodkit-12.2.1.tar.gz", hash = "sha256:008d792827ea1b11a051f4871c99c720ca5430395078158f082846cab43b04e1", size = 26255, upload-time = "2026-06-19T16:20:46.333Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/8a/16b91f6744fcb6e978bb2eb9dd9c6da55c55e677087dcc426f34b1460795/pyobjc_framework_inputmethodkit-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e655947e314e6fe743ce225fc3170466003030b0186b8a7db161b54e87ee5c0e", size = 9500, upload-time = "2025-11-14T09:51:22.631Z" }, - { url = "https://files.pythonhosted.org/packages/a7/04/1315f84dba5704a4976ea0185f877f0f33f28781473a817010cee209a8f0/pyobjc_framework_inputmethodkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4e02f49816799a31d558866492048d69e8086178770b76f4c511295610e02ab", size = 9502, upload-time = "2025-11-14T09:51:24.708Z" }, - { url = "https://files.pythonhosted.org/packages/01/c2/59bea66405784b25f5d4e821467ba534a0b92dfc98e07257c971e2a8ed73/pyobjc_framework_inputmethodkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0b7d813d46a060572fc0c14ef832e4fe538ebf64e5cab80ee955191792ce0110", size = 9506, upload-time = "2025-11-14T09:51:26.924Z" }, - { url = "https://files.pythonhosted.org/packages/9e/ec/502019d314729e7e82a7fa187dd52b6f99a6097ac0ab6dc675ccd60b5677/pyobjc_framework_inputmethodkit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b5c7458082e3f7e8bb115ed10074ad862cc6566da7357540205d3cd1e24e2b9f", size = 9523, upload-time = "2025-11-14T09:51:30.751Z" }, - { url = "https://files.pythonhosted.org/packages/47/68/76a75461de5b9c195a6b5081179578fef7136f19ffc4990f6591cabae591/pyobjc_framework_inputmethodkit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a4e782edd8e59b1ea81ea688d27edbf98cc5c8262e081cb772cf8c36c74733df", size = 9694, upload-time = "2025-11-14T09:51:32.616Z" }, - { url = "https://files.pythonhosted.org/packages/76/f8/6915cc42826e1178c18cc9232edda15ef5d1f57950eef8fd6f8752853b9c/pyobjc_framework_inputmethodkit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:3b27c166574ad08d196129c979c5eec891cd630d249c75a970e26f3949578cb9", size = 9574, upload-time = "2025-11-14T09:51:34.366Z" }, - { url = "https://files.pythonhosted.org/packages/97/36/6d3debe09cf1fbcb40b15cc29e7cdc04b07a2f14815d0ffcdcb4a3823ead/pyobjc_framework_inputmethodkit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1f065cb44041821a1812861e13ee1eca4aee37b57c8de0ce7ffd7e55f7af8907", size = 9746, upload-time = "2025-11-14T09:51:36.034Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/63ebb6101d4066d808caf2543537751aab219ec3d11c5767c4e7a11f1df4/pyobjc_framework_inputmethodkit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8fee1c7da58c934eb8fd754de493e17b834034d4b8266233f670197c1fbfb87c", size = 9526, upload-time = "2026-06-19T16:12:08.091Z" }, + { url = "https://files.pythonhosted.org/packages/af/5f/d82e583e6131717ef7080807a7757ae429dbc0dcaf08e20fe33f42ee0fb3/pyobjc_framework_inputmethodkit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ade69fed2930f32434707a39825665bdedb944e835c030c624da5e3a898e2b75", size = 9526, upload-time = "2026-06-19T16:12:09.053Z" }, + { url = "https://files.pythonhosted.org/packages/06/54/eef2b45c70f0ebcc54db74b866753d7a140982519b18f738e9b6c00d7730/pyobjc_framework_inputmethodkit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5c979473f54fa344cefc91525b2f7caadb682da7f7cc93f2bf49ba730569f05c", size = 9532, upload-time = "2026-06-19T16:12:09.889Z" }, + { url = "https://files.pythonhosted.org/packages/1a/9b/338f8abbb7fdfecb8a89cd7ecaac0a1853d4a7399c77054b0875b3195e29/pyobjc_framework_inputmethodkit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2fb01dfa2c624beafc25c9520806eaf2cc3212c7180a6623a1436d84e84ad84d", size = 9554, upload-time = "2026-06-19T16:12:10.749Z" }, + { url = "https://files.pythonhosted.org/packages/85/2a/d65f3b8606d8f32ae125e434fdf7aeadc5de5ececfa0f543c7cfd1c65b65/pyobjc_framework_inputmethodkit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:8d02da4d81146258fae8f2571b47df41a912b0e1f84bb38384e1a4d858feaf28", size = 9715, upload-time = "2026-06-19T16:12:11.541Z" }, + { url = "https://files.pythonhosted.org/packages/b6/cb/c57dd6e1e7acca699c9861801334337478368327ad7571dc4d63c5a4ead3/pyobjc_framework_inputmethodkit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9b7754bcf52aa753cbb616eaab5834f13b8ef4bdd163c89ad2f921e8fc89c05a", size = 9599, upload-time = "2026-06-19T16:12:12.473Z" }, + { url = "https://files.pythonhosted.org/packages/78/f7/ce41a93ef757dbbb3537f2d9552a7fbef05cb928315e67d34f6b3667968b/pyobjc_framework_inputmethodkit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:017c2b316fa6f4477b4395b21a276e2caef8a4c558411110271b267bdd1a2276", size = 9767, upload-time = "2026-06-19T16:12:13.494Z" }, + { url = "https://files.pythonhosted.org/packages/f8/13/9196e22ccfa58996547b0ebc1931e3c6aa9b1ec2425b05d859db4ff476ce/pyobjc_framework_inputmethodkit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:ea2503ebbae052622c2fada50f445766906757b4ddccde3c2e66993703fb0393", size = 9595, upload-time = "2026-06-19T16:12:14.254Z" }, + { url = "https://files.pythonhosted.org/packages/6a/4c/f79bc867cf5b06c5fba04422b6a7a690ea8c0b9f8c88d5f04bf5818b516d/pyobjc_framework_inputmethodkit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:05886f2ab68f06693cd5c5c6f8a99a37ddebcd5a085d98ebbfc8b001b31a122d", size = 9766, upload-time = "2026-06-19T16:12:15.066Z" }, ] [[package]] name = "pyobjc-framework-installerplugins" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e7/60/ca4ab04eafa388a97a521db7d60a812e2f81a3c21c2372587872e6b074f9/pyobjc_framework_installerplugins-12.1.tar.gz", hash = "sha256:1329a193bd2e92a2320a981a9a421a9b99749bade3e5914358923e94fe995795", size = 25277, upload-time = "2025-11-14T10:16:04.379Z" } +sdist = { url = "https://files.pythonhosted.org/packages/64/2e/23d4d49b755fc6e179956d745311115dbff6b43a505a8ad627a13a301082/pyobjc_framework_installerplugins-12.2.1.tar.gz", hash = "sha256:118ee84e6e7f6f7913ade58818bfd2c12e2078ff7f6090e4941df1e739c8685d", size = 25978, upload-time = "2026-06-19T16:20:47.151Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/99/1f/31dca45db3342882a628aa1b27707a283d4dc7ef558fddd2533175a0661a/pyobjc_framework_installerplugins-12.1-py2.py3-none-any.whl", hash = "sha256:d2201c81b05bdbe0abf0af25db58dc230802573463bea322f8b2863e37b511d5", size = 4813, upload-time = "2025-11-14T09:51:37.836Z" }, + { url = "https://files.pythonhosted.org/packages/12/16/7ae359b8f858d6caeb076d779ef1123ed2f1d287e5e9a7262f7355b25819/pyobjc_framework_installerplugins-12.2.1-py2.py3-none-any.whl", hash = "sha256:aef22bff292d3b8fb9cb655f868d041dcc76d3d6b47d3022557fe3f657c28c69", size = 4841, upload-time = "2026-06-19T16:12:15.946Z" }, ] [[package]] name = "pyobjc-framework-instantmessage" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-quartz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d4/67/66754e0d26320ba24a33608ca94d3f38e60ee6b2d2e094cb6269b346fdd4/pyobjc_framework_instantmessage-12.1.tar.gz", hash = "sha256:f453118d5693dc3c94554791bd2aaafe32a8b03b0e3d8ec3934b44b7fdd1f7e7", size = 31217, upload-time = "2025-11-14T10:16:07.693Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/66/89688001f6b1a76a09876b4fbe02760994b783d031e05b3d7e039514748a/pyobjc_framework_instantmessage-12.2.1.tar.gz", hash = "sha256:80d31bca02459c6d2364605b51a8064d0fbe3e7dba085b5b8ac59ee98157710c", size = 34047, upload-time = "2026-06-19T16:20:47.868Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/38/6ae95b5c87d887c075bd5f4f7cca3d21dafd0a77cfdde870e87ca17579eb/pyobjc_framework_instantmessage-12.1-py2.py3-none-any.whl", hash = "sha256:cd91d38e8f356afd726b6ea8c235699316ea90edfd3472965c251efbf4150bc9", size = 5436, upload-time = "2025-11-14T09:51:39.557Z" }, + { url = "https://files.pythonhosted.org/packages/04/0e/b042906e81eb1865b123ef529516af7b2a2bc28f5bfbfd7b0e0d40a9f1de/pyobjc_framework_instantmessage-12.2.1-py2.py3-none-any.whl", hash = "sha256:42c7be0357b1d4e808b40c7d212f5d807e1901a87e0cbb6a215bd0232f87374d", size = 5464, upload-time = "2026-06-19T16:12:16.821Z" }, ] [[package]] name = "pyobjc-framework-intents" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f1/a1/3bab6139e94b97eca098e1562f5d6840e3ff10ea1f7fd704a17111a97d5b/pyobjc_framework_intents-12.1.tar.gz", hash = "sha256:bd688c3ab34a18412f56e459e9dae29e1f4152d3c2048fcacdef5fc49dfb9765", size = 132262, upload-time = "2025-11-14T10:16:16.428Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/a9/107e313345eef0a2be05017227073678b65b58f77af14312ff6403999856/pyobjc_framework_intents-12.2.1.tar.gz", hash = "sha256:579a36b1c2dae423ecf8f7fc02fc8a2a3d079366a073903ba323d40adeeabc2a", size = 187763, upload-time = "2026-06-19T16:20:48.645Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/4d/6ce09716747342e5833fb7b1b428017566d9e1633481159688ea955ab578/pyobjc_framework_intents-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4dcca96da5b302583e3f8792f697e1d60a86f62b16ee07e9db11ce4186ca243c", size = 32137, upload-time = "2025-11-14T09:51:42.793Z" }, - { url = "https://files.pythonhosted.org/packages/d0/25/648db47b9c3879fa50c65ab7cc5fbe0dd400cc97141ac2658ef2e196c0b6/pyobjc_framework_intents-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dc68dc49f1f8d9f8d2ffbc0f57ad25caac35312ddc444899707461e596024fec", size = 32134, upload-time = "2025-11-14T09:51:46.369Z" }, - { url = "https://files.pythonhosted.org/packages/7a/90/e9489492ae90b4c1ffd02c1221c0432b8768d475787e7887f79032c2487a/pyobjc_framework_intents-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ea9f3e79bf4baf6c7b0fd2d2797184ed51a372bf7f32974b4424f9bd067ef50", size = 32156, upload-time = "2025-11-14T09:51:49.438Z" }, - { url = "https://files.pythonhosted.org/packages/74/83/6b03ac6d5663be41d76ab69412a21f94eff69c67ffa13516a91e4b946890/pyobjc_framework_intents-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1da8d1501c8c85198dfbc4623ea18db96077f9947f6e1fe5ffa2ed06935e8a3b", size = 32168, upload-time = "2025-11-14T09:51:52.888Z" }, - { url = "https://files.pythonhosted.org/packages/d9/f8/1fd0a75de415d335a1aa43e9c86e468960b3a4d969a87aa4a70084452277/pyobjc_framework_intents-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:50ab244f2a9ad4c94bbc1dd81421f8553f59121d4e0ad0c894a927a878319843", size = 32413, upload-time = "2025-11-14T09:51:56.057Z" }, - { url = "https://files.pythonhosted.org/packages/42/8a/d319b1a014dcf52cd46c2c956bed0e66f7c80253acaebd1ec5920b01bf41/pyobjc_framework_intents-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:5c50c336418a3ba8fdfa5b5d12e46dca290e4321fb9844245af4a32b11cf6563", size = 32191, upload-time = "2025-11-14T09:51:59.097Z" }, - { url = "https://files.pythonhosted.org/packages/38/cd/b5ce5d389a3ca767b3d0ce70daf35c52cb35775e4a285ed4bedaa89ab75e/pyobjc_framework_intents-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:03cbccec0380a431bc291725af0fcbaf61ea1bb1301a70cb267c8ecf2d04d608", size = 32481, upload-time = "2025-11-14T09:52:02.16Z" }, + { url = "https://files.pythonhosted.org/packages/5a/05/ca398c68daa3001269437bd59938faba3fff2a69d3ad3996865d2b4b74ab/pyobjc_framework_intents-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0493027193f298ef11208c12a4e8aac8cacbd0c89e3d28899a979151a05cd6b5", size = 35254, upload-time = "2026-06-19T16:12:17.864Z" }, + { url = "https://files.pythonhosted.org/packages/b4/3f/848e213c2e9424c5304aac6a4ac26f7ab53088bd58fd5a950c23246862e3/pyobjc_framework_intents-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b6e0687c4dd12ebfd8f057364b2fa7367d324957d514747d8dd109083486d422", size = 35259, upload-time = "2026-06-19T16:12:19.041Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f0/cd105a3a262e9048accde15734d84869ea6d2e1eae95a9f5c32dfcb9f2c1/pyobjc_framework_intents-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:440fb3b4f2ffe3184f3fc8ba0abba83a3a59ba63dad12a5bb2b6792eab8722eb", size = 35276, upload-time = "2026-06-19T16:12:19.958Z" }, + { url = "https://files.pythonhosted.org/packages/28/22/4f3a7ca8f8fd8a646074f2e9fae8798d2c8e9378d8fa191085b2b1abaa27/pyobjc_framework_intents-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c21cf1db00b8c997d3e6cfde53ec135ddee5b03138cf184deb842dbc925c1286", size = 35293, upload-time = "2026-06-19T16:12:20.884Z" }, + { url = "https://files.pythonhosted.org/packages/33/ef/59efde70a85b826bd7328c2d77fd19163014b571f0dac1543c46a1fbd143/pyobjc_framework_intents-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:21ed7ca804106bb1a606c070a081d59dc93fb23bd570a802518a8ed36357c275", size = 35529, upload-time = "2026-06-19T16:12:21.746Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b9/29ec0832512c3db3d5708eaa4f5ab4419aea65f2e7e6b4aeaf4f413b6086/pyobjc_framework_intents-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8fcc3326ed27b800a5da3e8634cdddd6506efd0c73a95f5e624f3dc963516baf", size = 35308, upload-time = "2026-06-19T16:12:22.626Z" }, + { url = "https://files.pythonhosted.org/packages/34/dd/272debdcd22abb68d5b9574aa9fb87da590a281eb663f2bcf7ef9f0c9327/pyobjc_framework_intents-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1b4dc0c62df523a3391dc43c754cc9a4cae7334342785c882be0fa76b2ea22c6", size = 35597, upload-time = "2026-06-19T16:12:23.62Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f0/2aaae2250c7346d0c946a404aa7a6901b9bc2394ce986f7094e2167f53a5/pyobjc_framework_intents-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:bba9340e7ec61747a5545d4fe569d77a3c10e870d1ec06a9e17412c29a1fdd07", size = 35317, upload-time = "2026-06-19T16:12:24.497Z" }, + { url = "https://files.pythonhosted.org/packages/03/9c/fe3f1cea21ada9707e184cfbc6e661c4833fe2a68c4bb4b37123e775d8fe/pyobjc_framework_intents-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:18a726d52b4a24f6a274c094b1abb5cd757d1f29946a8058c85a32bc0857dab0", size = 35599, upload-time = "2026-06-19T16:12:25.398Z" }, ] [[package]] name = "pyobjc-framework-intentsui" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-intents" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/cf/f0e385b9cfbf153d68efe8d19e5ae672b59acbbfc1f9b58faaefc5ec8c9e/pyobjc_framework_intentsui-12.1.tar.gz", hash = "sha256:16bdf4b7b91c0d1ec9d5513a1182861f1b5b7af95d4f4218ff7cf03032d57f99", size = 19784, upload-time = "2025-11-14T10:16:18.716Z" } +sdist = { url = "https://files.pythonhosted.org/packages/45/31/1426d5ca5dba96d82dc08c6f287361eec9c3d8008295403b015843bf7b51/pyobjc_framework_intentsui-12.2.1.tar.gz", hash = "sha256:ec1a0fa3861911da7e43abfb6f783052644d62f587554e15a06303a648f9f361", size = 20768, upload-time = "2026-06-19T16:20:49.755Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/c1/f7bc2d220354740dcbc2e8d2b416f7ab84e0664d1ef45321341726390a01/pyobjc_framework_intentsui-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:631baf74bc8ca65ebab6ed8914a116d12af8323dfa89b156a9f64f55a7fdde5b", size = 8959, upload-time = "2025-11-14T09:52:03.84Z" }, - { url = "https://files.pythonhosted.org/packages/84/cc/7678f901cbf5bca8ccace568ae85ee7baddcd93d78754ac43a3bb5e5a7ac/pyobjc_framework_intentsui-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a877555e313d74ac3b10f7b4e738647eea9f744c00a227d1238935ac3f9d7968", size = 8961, upload-time = "2025-11-14T09:52:05.595Z" }, - { url = "https://files.pythonhosted.org/packages/f1/17/06812542a9028f5b2dcce56f52f25633c08b638faacd43bad862aad1b41d/pyobjc_framework_intentsui-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cb894fcc4c9ea613a424dcf6fb48142d51174559b82cfdafac8cb47555c842cf", size = 8983, upload-time = "2025-11-14T09:52:07.667Z" }, - { url = "https://files.pythonhosted.org/packages/57/af/4dc8b6f714ba1bd9cf0218da98c49ece5dcee4e0593b59196ec5aa85e07c/pyobjc_framework_intentsui-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:369a88db1ff3647e4d8cf38d315f1e9b381fc7732d765b08994036f9d330f57d", size = 9004, upload-time = "2025-11-14T09:52:09.625Z" }, - { url = "https://files.pythonhosted.org/packages/18/ab/794ed92dcf955dc2d0a0dcfbc384e087864f2dacd330d59d1185f8403353/pyobjc_framework_intentsui-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:8742e9237ef2df8dbb1566cdc77e4d747b2693202f438d49435e0c3c91eaa709", size = 9177, upload-time = "2025-11-14T09:52:11.26Z" }, - { url = "https://files.pythonhosted.org/packages/68/07/61dc855f6eeaf75d274ad4b66006e05b0bef2138a6a559c60f0bc59d32ea/pyobjc_framework_intentsui-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:d01222760005421324c3892b6b98c5b4295828a6b157a1fc410f63eb336b2d97", size = 9054, upload-time = "2025-11-14T09:52:12.896Z" }, - { url = "https://files.pythonhosted.org/packages/76/fa/d6dabff68951b66f2d7d8c8aa651f2a139a1ca0be556e1e64c6bdd7be18b/pyobjc_framework_intentsui-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:547aef7233b6c7495b3c679aa779f01368fc992883732ade065523235f07fa3b", size = 9248, upload-time = "2025-11-14T09:52:14.936Z" }, + { url = "https://files.pythonhosted.org/packages/d4/60/663c12672ab0aa61b4c286e1c473ca6902a044b33881edb84bea8b89b57f/pyobjc_framework_intentsui-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:871a0e95563aa5d32cd98358f715adeed388418336f7e2033693e552804673f3", size = 9028, upload-time = "2026-06-19T16:12:26.207Z" }, + { url = "https://files.pythonhosted.org/packages/60/14/8946ca41f1f9b113fc8df7da720b7813ff1851b1f14588cde54ba620e41b/pyobjc_framework_intentsui-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ecd53ed0f5f234d690985bee9eba1ee94f34b7d206f2d375fbcf6691fa446e84", size = 9027, upload-time = "2026-06-19T16:12:27.267Z" }, + { url = "https://files.pythonhosted.org/packages/18/8e/6cb7c5e2a9cf64fd1bacdbe103dc0355f85fcebbcedafeacdca9ae3678c8/pyobjc_framework_intentsui-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b8f92e0018ca0d720f339b589ead6b46443b95f844258eb4f0a215101a74eb1b", size = 9047, upload-time = "2026-06-19T16:12:28.149Z" }, + { url = "https://files.pythonhosted.org/packages/c3/7b/06fa5e8a04202dc42ba0eb0887ee362016417074343602cd1ae97a811978/pyobjc_framework_intentsui-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3200f217607900018605f8b25a75d1e5b2b6739e2244b25147a77d9278ff745c", size = 9064, upload-time = "2026-06-19T16:12:28.935Z" }, + { url = "https://files.pythonhosted.org/packages/f2/bf/6bd5674aecf4f745fc1bd4ce0c5d75e4b562e43d8cdd65856d1665d998c3/pyobjc_framework_intentsui-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0af7ede17b56defe1c19426e5b5f2dd39d8215eeee30c6dcd1501dba609566d0", size = 9245, upload-time = "2026-06-19T16:12:29.777Z" }, + { url = "https://files.pythonhosted.org/packages/4a/f3/05119d645544e385d2ce402c3219f2ea46ef3a09aa58687704f81c99fba8/pyobjc_framework_intentsui-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0d3088164f6028b0f8eda429345f3ad33c7b8c70ca8ed3d75d178bc62ccc96f0", size = 9118, upload-time = "2026-06-19T16:12:30.597Z" }, + { url = "https://files.pythonhosted.org/packages/93/e5/62c2e46ec1a8cea1aafa74f4262246e48eabf781312b58e79f0dfc354adc/pyobjc_framework_intentsui-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:f91ef9f61f65dcbf08b5277cf947ce390393eaf4829800d6aa5132e49dde6888", size = 9313, upload-time = "2026-06-19T16:12:31.522Z" }, + { url = "https://files.pythonhosted.org/packages/f1/2b/3884fbd8920ab24401f9dbcfd381e285a3fab1314fb3464666090e862779/pyobjc_framework_intentsui-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:8bccc0dfa36cf12ee4de87195d1caaa8ba15334658cb06c81c6da8bb44a2c9d7", size = 9113, upload-time = "2026-06-19T16:12:32.567Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e6/54039e403a04afd25456953ce6f1240cafe0f2972e199c93b629b4029e06/pyobjc_framework_intentsui-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:70d30672d1b7213ea7018b7bf53bb37000179429f83759a471dda456bb07bdce", size = 9306, upload-time = "2026-06-19T16:12:33.565Z" }, ] [[package]] name = "pyobjc-framework-iobluetooth" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e4/aa/ca3944bbdfead4201b4ae6b51510942c5a7d8e5e2dc3139a071c74061fdf/pyobjc_framework_iobluetooth-12.1.tar.gz", hash = "sha256:8a434118812f4c01dfc64339d41fe8229516864a59d2803e9094ee4cbe2b7edd", size = 155241, upload-time = "2025-11-14T10:16:28.896Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/5c/acb79d6180b7dc243b82c129292fc2c9e1f695793165dd6e89f55a000a49/pyobjc_framework_iobluetooth-12.2.1.tar.gz", hash = "sha256:eb99c27187c68f984dee4e9ac620f5b210f11f821a2063b2fb9161359a1f1754", size = 174846, upload-time = "2026-06-19T16:20:50.714Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/92/1bd6d76a005f0eb54e15608534cb7ce73f5d37afdcf82dc86e2ab54314e2/pyobjc_framework_iobluetooth-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8963d7115fdcd753598f0757f36b19267d01d00ac84cf617c02d38889c3a40fd", size = 40409, upload-time = "2025-11-14T09:52:18.411Z" }, - { url = "https://files.pythonhosted.org/packages/f6/ab/ad6b36f574c3d52b5e935b1d57ab0f14f4e4cd328cc922d2b6ba6428c12d/pyobjc_framework_iobluetooth-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:77959f2ecf379aa41eb0848fdb25da7c322f9f4a82429965c87c4bc147137953", size = 40415, upload-time = "2025-11-14T09:52:22.069Z" }, - { url = "https://files.pythonhosted.org/packages/0b/b6/933b56afb5e84c3c35c074c9e30d7b701c6038989d4867867bdaa7ab618b/pyobjc_framework_iobluetooth-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:111a6e54be9e9dcf77fa2bf84fdac09fae339aa33087d8647ea7ffbd34765d4c", size = 40439, upload-time = "2025-11-14T09:52:26.071Z" }, - { url = "https://files.pythonhosted.org/packages/15/6f/5e165daaf3b637d37fee50f42beda62ab3d5e6e99b1d84c4af4700d39d01/pyobjc_framework_iobluetooth-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2ee0d4fdddf871fb89c49033495ae49973cc8b0e8de50c2e60c92355ce3bea86", size = 40452, upload-time = "2025-11-14T09:52:29.68Z" }, - { url = "https://files.pythonhosted.org/packages/37/bd/7cc5f01fbf573112059766c94535ae3f9c044d6e0cf49c599e490224db58/pyobjc_framework_iobluetooth-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0cd2ea9384e93913703bf40641196a930af83c2f6f62f59f8606b7162fe1caa3", size = 40659, upload-time = "2025-11-14T09:52:33.299Z" }, - { url = "https://files.pythonhosted.org/packages/ef/58/4553d846513840622cd56ef715543f922d7d5ddfbe38316dbc7e43f23832/pyobjc_framework_iobluetooth-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a14506046ad9403ea95c75c1dd248167f41aef4aed62f50b567bf2482056ebf5", size = 40443, upload-time = "2025-11-14T09:52:37.21Z" }, - { url = "https://files.pythonhosted.org/packages/8a/da/4846a76bd9cb73fb1e562d1fb7044bd3df15a289ab986bcaf053a65dbb88/pyobjc_framework_iobluetooth-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:42ec9a40e7234a00f434489c8b18458bc5deb6ea6938daba50b9527100e21f0c", size = 40649, upload-time = "2025-11-14T09:52:40.793Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a6/2eee6c125076977d6292f886a209e8736267613054ffd1422ec5836f5b4b/pyobjc_framework_iobluetooth-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:18b769c4ebd6d00eff5ecc4e4fa70cee5713afed5bb3c4273f3e57f718297e87", size = 40534, upload-time = "2026-06-19T16:12:34.439Z" }, + { url = "https://files.pythonhosted.org/packages/1b/0d/352fbaa3c33de658760e1de4e9c2276c77a1e6964d8f4aae38250f54f960/pyobjc_framework_iobluetooth-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e3c3fe2e35d4b20cf0ffafe1ee234bb250c742a3e348abb299df6e348e92922b", size = 40539, upload-time = "2026-06-19T16:12:35.521Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a0/59dd27082e6b01ab5f181a465ac86ce62ab5353d477254d78472afa7c26c/pyobjc_framework_iobluetooth-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8ca12b1ef8533fbd63c44c68ad80afe7ca0446f056399305e9f51f573c406b42", size = 40560, upload-time = "2026-06-19T16:12:36.361Z" }, + { url = "https://files.pythonhosted.org/packages/19/fb/aa00b0c445d1a20d8b67d67348c8890d122c1d1e76a5735b8e14c28649cc/pyobjc_framework_iobluetooth-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6d5e76af8d2f0ad9f0a25158c0aa6dafa2dc59c57135dd1d4f39aff89e898344", size = 40571, upload-time = "2026-06-19T16:12:37.155Z" }, + { url = "https://files.pythonhosted.org/packages/0a/be/8d058fb08b3ce438e9878ca27e6c91d4e89e7cca9de8eb281317ff3d022f/pyobjc_framework_iobluetooth-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:fed3eb2ce6aec555f1e22d04d06d2212a1d475740eaa2b832244198e09b5ce11", size = 40787, upload-time = "2026-06-19T16:12:38.145Z" }, + { url = "https://files.pythonhosted.org/packages/60/69/763ad564f5ef8bde212024bdbfc750c0e0a73ec1f2ff5562f155669ef5c7/pyobjc_framework_iobluetooth-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:18c9b901de39aeb79ce36bb4e550410ad5112917bf7f4198776c25c07099ce0c", size = 40564, upload-time = "2026-06-19T16:12:39.004Z" }, + { url = "https://files.pythonhosted.org/packages/7c/c2/65cd8eff70014848f1584ecd6e534d12062e4543db16c963806797dd1540/pyobjc_framework_iobluetooth-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9dfa596bd8fc4d1648909c04b2856193e627dcb46ceecee9997085b99e395a8c", size = 40765, upload-time = "2026-06-19T16:12:39.807Z" }, + { url = "https://files.pythonhosted.org/packages/41/c0/d16c0daa3cf2214301614641e83a49aed33eacbcd9f30505bbd3552959aa/pyobjc_framework_iobluetooth-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:aeca5abfaca23d204d9a4a32b68aa854b1d2893cfc593f0c3dab69c106c8ec63", size = 40555, upload-time = "2026-06-19T16:12:40.853Z" }, + { url = "https://files.pythonhosted.org/packages/b1/14/9fa4e6f0a7d990c9d0216d77222595991d3f802627dd089e4277890d46d1/pyobjc_framework_iobluetooth-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:c80d734d08e2fd5d4764e0039693792fe2a7799156c08501b6734357a11e6c5e", size = 40761, upload-time = "2026-06-19T16:12:41.856Z" }, ] [[package]] name = "pyobjc-framework-iobluetoothui" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-iobluetooth" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8f/39/31d9a4e8565a4b1ec0a9ad81480dc0879f3df28799eae3bc22d1dd53705d/pyobjc_framework_iobluetoothui-12.1.tar.gz", hash = "sha256:81f8158bdfb2966a574b6988eb346114d6a4c277300c8c0a978c272018184e6f", size = 16495, upload-time = "2025-11-14T10:16:31.212Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/79/99c3fce73768d0fbbb9c2fbb9dda092c828c8d15e9e5ba4dd802b719582e/pyobjc_framework_iobluetoothui-12.2.1.tar.gz", hash = "sha256:53bbfa9451c3c1bb55e91f063bb0539509e1e2b11887736967cc218b93695087", size = 18013, upload-time = "2026-06-19T16:20:51.717Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/c9/69aeda0cdb5d25d30dc4596a1c5b464fc81b5c0c4e28efc54b7e11bde51c/pyobjc_framework_iobluetoothui-12.1-py2.py3-none-any.whl", hash = "sha256:a6d8ab98efa3029130577a57ee96b183c35c39b0f1c53a7534f8838260fab993", size = 4045, upload-time = "2025-11-14T09:52:42.201Z" }, + { url = "https://files.pythonhosted.org/packages/62/ea/015e595050aecaea98cfd9cb62d93bd86408af7550efc405d9bc661a69c2/pyobjc_framework_iobluetoothui-12.2.1-py2.py3-none-any.whl", hash = "sha256:321160ac3181349054d29f7804b24944549fe442f4d6840f9123bbcd48f62aee", size = 4066, upload-time = "2026-06-19T16:12:42.682Z" }, ] [[package]] name = "pyobjc-framework-iosurface" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/07/61/0f12ad67a72d434e1c84b229ec760b5be71f53671ee9018593961c8bfeb7/pyobjc_framework_iosurface-12.1.tar.gz", hash = "sha256:4b9d0c66431aa296f3ca7c4f84c00dc5fc961194830ad7682fdbbc358fa0db55", size = 17690, upload-time = "2025-11-14T10:16:33.282Z" } +sdist = { url = "https://files.pythonhosted.org/packages/26/bb/9f1b513ce177d725b6aa0936f69ece74afa695698ff2f59660b427824b4e/pyobjc_framework_iosurface-12.2.1.tar.gz", hash = "sha256:f886630d6f2419fed9f89152b1e738758b735219bd39506bc12c2e1f65456dea", size = 18604, upload-time = "2026-06-19T16:20:52.414Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ad/793d98a7ed9b775dc8cce54144cdab0df1808a1960ee017e46189291a8f3/pyobjc_framework_iosurface-12.1-py2.py3-none-any.whl", hash = "sha256:e784e248397cfebef4655d2c0025766d3eaa4a70474e363d084fc5ce2a4f2a3f", size = 4902, upload-time = "2025-11-14T09:52:43.899Z" }, + { url = "https://files.pythonhosted.org/packages/6d/22/c1a95f27988883450ec1c8210f351da5fb006c753d686e8437426daf821b/pyobjc_framework_iosurface-12.2.1-py2.py3-none-any.whl", hash = "sha256:92cf617b6ad6c216ec5fcdd47584fa0744dc7ff38fee378b2689dfcf46194df1", size = 4925, upload-time = "2026-06-19T16:12:43.545Z" }, ] [[package]] name = "pyobjc-framework-ituneslibrary" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f5/46/d9bcec88675bf4ee887b9707bd245e2a793e7cb916cf310f286741d54b1f/pyobjc_framework_ituneslibrary-12.1.tar.gz", hash = "sha256:7f3aa76c4d05f6fa6015056b88986cacbda107c3f29520dd35ef0936c7367a6e", size = 23730, upload-time = "2025-11-14T10:16:36.127Z" } +sdist = { url = "https://files.pythonhosted.org/packages/11/9d/1b18df4c94a4a45cb9fc86edf2656d1932c1f56c31dcf6ac673cd9138808/pyobjc_framework_ituneslibrary-12.2.1.tar.gz", hash = "sha256:be3afc865881c762765101be35f7216616c5eb4c76025f590e36fe1cbd2518fb", size = 26184, upload-time = "2026-06-19T16:20:53.1Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/92/b598694a1713ee46f45c4bfb1a0425082253cbd2b1caf9f8fd50f292b017/pyobjc_framework_ituneslibrary-12.1-py2.py3-none-any.whl", hash = "sha256:fb678d7c3ff14c81672e09c015e25880dac278aa819971f4d5f75d46465932ef", size = 5205, upload-time = "2025-11-14T09:52:45.733Z" }, + { url = "https://files.pythonhosted.org/packages/19/7d/05c50a1bd493ccc1844a89461ad7eb82b7ea7a361ed219377f8abaa625c4/pyobjc_framework_ituneslibrary-12.2.1-py2.py3-none-any.whl", hash = "sha256:076712e495e2df43dad14e92167e468c4a46b9d06f1b84b98b2b65ff24285043", size = 5237, upload-time = "2026-06-19T16:12:44.426Z" }, ] [[package]] name = "pyobjc-framework-kernelmanagement" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0a/7e/ecbac119866e8ac2cce700d7a48a4297946412ac7cbc243a7084a6582fb1/pyobjc_framework_kernelmanagement-12.1.tar.gz", hash = "sha256:488062893ac2074e0c8178667bf864a21f7909c11111de2f6a10d9bc579df59d", size = 11773, upload-time = "2025-11-14T10:16:38.216Z" } +sdist = { url = "https://files.pythonhosted.org/packages/00/34/21b155304cd14f2b6ed9b732c2925b1c23c6eb1a941676e83d972c14dddd/pyobjc_framework_kernelmanagement-12.2.1.tar.gz", hash = "sha256:49591c0603057d2ea2596b9b414c38fe521f506e1320753bb49ccb2262b97bdc", size = 11961, upload-time = "2026-06-19T16:20:53.903Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/32/04325a20f39d88d6d712437e536961a9e6a4ec19f204f241de6ed54d1d84/pyobjc_framework_kernelmanagement-12.1-py2.py3-none-any.whl", hash = "sha256:926381bfbfbc985c3e6dfcb7004af21bb16ff66ecbc08912b925989a705944ff", size = 3704, upload-time = "2025-11-14T09:52:47.268Z" }, + { url = "https://files.pythonhosted.org/packages/0c/57/4b0e4520f06f62e3a70eb50249efe750fd220fe23072e021048a23a2eeb1/pyobjc_framework_kernelmanagement-12.2.1-py2.py3-none-any.whl", hash = "sha256:9facdb93e49717a9e5999ab7b20cd1643ce00119f7b91c4933fec0fe3638edd1", size = 3696, upload-time = "2026-06-19T16:12:45.473Z" }, ] [[package]] name = "pyobjc-framework-latentsemanticmapping" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/88/3c/b621dac54ae8e77ac25ee75dd93e310e2d6e0faaf15b8da13513258d6657/pyobjc_framework_latentsemanticmapping-12.1.tar.gz", hash = "sha256:f0b1fa823313eefecbf1539b4ed4b32461534b7a35826c2cd9f6024411dc9284", size = 15526, upload-time = "2025-11-14T10:16:40.149Z" } +sdist = { url = "https://files.pythonhosted.org/packages/08/37/35c98e572e98df7763c6d7bc437c7aee7d5a36c030c51841d793d0e89939/pyobjc_framework_latentsemanticmapping-12.2.1.tar.gz", hash = "sha256:96e6b523c7fe7944cee30b723f035f9082500c3bf8ba8237013c8e37b112c493", size = 15906, upload-time = "2026-06-19T16:20:54.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/8e/74a7eb29b545f294485cd3cf70557b4a35616555fe63021edbb3e0ea4c20/pyobjc_framework_latentsemanticmapping-12.1-py2.py3-none-any.whl", hash = "sha256:7d760213b42bc8b1bc1472e1873c0f78ee80f987225978837b1fecdceddbdbf4", size = 5471, upload-time = "2025-11-14T09:52:48.939Z" }, + { url = "https://files.pythonhosted.org/packages/b2/80/1e80736b58665094f12ed736f1d675a9658ebb992e33fea72cef35815010/pyobjc_framework_latentsemanticmapping-12.2.1-py2.py3-none-any.whl", hash = "sha256:7d7669ae5c6ca53e6aa7bd70ddfed23914a29169c3f57b94a40a0397abaa66e2", size = 5499, upload-time = "2026-06-19T16:12:46.356Z" }, ] [[package]] name = "pyobjc-framework-launchservices" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-coreservices" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/37/d0/24673625922b0ad21546be5cf49e5ec1afaa4553ae92f222adacdc915907/pyobjc_framework_launchservices-12.1.tar.gz", hash = "sha256:4d2d34c9bd6fb7f77566155b539a2c70283d1f0326e1695da234a93ef48352dc", size = 20470, upload-time = "2025-11-14T10:16:42.499Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/8e/26d4adeb32fcde532d6e3afd342ebfe055017f183b2c2f730a28f1b3de84/pyobjc_framework_launchservices-12.2.1.tar.gz", hash = "sha256:1d288543c1c4e53e6e24314987e18904ada821d6bef5437e6bd9e8e6873fe4a6", size = 20834, upload-time = "2026-06-19T16:20:55.548Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/af/9a0aebaab4c15632dc8fcb3669c68fa541a3278d99541d9c5f966fbc0909/pyobjc_framework_launchservices-12.1-py2.py3-none-any.whl", hash = "sha256:e63e78fceeed4d4dc807f9dabd5cf90407e4f552fab6a0d75a8d0af63094ad3c", size = 3905, upload-time = "2025-11-14T09:52:50.71Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/6ef104cc5c2724d07ebd119ad20c80d40a964f1074e6c72053b5d658556c/pyobjc_framework_launchservices-12.2.1-py2.py3-none-any.whl", hash = "sha256:81ca37abab29e96ebd69e1d97d63789d729510a6fe1f6cd4041e5b24f340f115", size = 3934, upload-time = "2026-06-19T16:12:47.283Z" }, ] [[package]] name = "pyobjc-framework-libdispatch" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/26/e8/75b6b9b3c88b37723c237e5a7600384ea2d84874548671139db02e76652b/pyobjc_framework_libdispatch-12.1.tar.gz", hash = "sha256:4035535b4fae1b5e976f3e0e38b6e3442ffea1b8aa178d0ca89faa9b8ecdea41", size = 38277, upload-time = "2025-11-14T10:16:46.235Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/3f/561653aff3f19873457c95c053f0298da517be89fdfc0ec35115ed5b7030/pyobjc_framework_libdispatch-12.2.1.tar.gz", hash = "sha256:0d24eda41c6c258135077f60d410e704bc7b5a67adcb2ca463918896c7363795", size = 40336, upload-time = "2026-06-19T16:20:56.371Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/76/9936d97586dbae4d7d10f3958d899ee7a763930af69b5ad03d4516178c7c/pyobjc_framework_libdispatch-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:50a81a29506f0e35b4dc313f97a9d469f7b668dae3ba597bb67bbab94de446bd", size = 20471, upload-time = "2025-11-14T09:52:53.134Z" }, - { url = "https://files.pythonhosted.org/packages/1f/75/c4aeab6ce7268373d4ceabbc5c406c4bbf557038649784384910932985f8/pyobjc_framework_libdispatch-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:954cc2d817b71383bd267cc5cd27d83536c5f879539122353ca59f1c945ac706", size = 20463, upload-time = "2025-11-14T09:52:55.703Z" }, - { url = "https://files.pythonhosted.org/packages/83/6f/96e15c7b2f7b51fc53252216cd0bed0c3541bc0f0aeb32756fefd31bed7d/pyobjc_framework_libdispatch-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0e9570d7a9a3136f54b0b834683bf3f206acd5df0e421c30f8fd4f8b9b556789", size = 15650, upload-time = "2025-11-14T09:52:59.284Z" }, - { url = "https://files.pythonhosted.org/packages/38/3a/d85a74606c89b6b293782adfb18711026ff79159db20fc543740f2ac0bc7/pyobjc_framework_libdispatch-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:58ffce5e6bcd7456b4311009480b195b9f22107b7682fb0835d4908af5a68ad0", size = 15668, upload-time = "2025-11-14T09:53:01.354Z" }, - { url = "https://files.pythonhosted.org/packages/cc/40/49b1c1702114ee972678597393320d7b33f477e9d24f2a62f93d77f23dfb/pyobjc_framework_libdispatch-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e9f49517e253716e40a0009412151f527005eec0b9a2311ac63ecac1bdf02332", size = 15938, upload-time = "2025-11-14T09:53:03.461Z" }, - { url = "https://files.pythonhosted.org/packages/59/d8/7d60a70fc1a546c6cb482fe0595cb4bd1368d75c48d49e76d0bc6c0a2d0f/pyobjc_framework_libdispatch-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0ebfd9e4446ab6528126bff25cfb09e4213ddf992b3208978911cfd3152e45f5", size = 15693, upload-time = "2025-11-14T09:53:05.531Z" }, - { url = "https://files.pythonhosted.org/packages/99/32/15e08a0c4bb536303e1568e2ba5cae1ce39a2e026a03aea46173af4c7a2d/pyobjc_framework_libdispatch-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:23fc9915cba328216b6a736c7a48438a16213f16dfb467f69506300b95938cc7", size = 15976, upload-time = "2025-11-14T09:53:07.936Z" }, + { url = "https://files.pythonhosted.org/packages/13/33/362a92511af642d6168bf46369dbc3a98aa20fc2329768748f1e4d02786d/pyobjc_framework_libdispatch-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a47e8c53511eaf739f3972c48e0813bf8fd10dc88dc5543afceaec6554f5c4c2", size = 20486, upload-time = "2026-06-19T16:12:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1d/b0/dc263ed1cee54badccaf60f061de1e25bea95504984401a22bee274b5f59/pyobjc_framework_libdispatch-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e295775c76eace23f60e53ef74b0f79429c665f91a870e4665c4b9887466efa", size = 20488, upload-time = "2026-06-19T16:12:49.441Z" }, + { url = "https://files.pythonhosted.org/packages/b3/8f/42cfa987c07a2b5ce8c236a42b0fb388b8807dac72c25e004cd4905ea9a3/pyobjc_framework_libdispatch-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8f41b5021ff70bc51220a79b41ebd1eacb55fe3ceb67448594f30e491a2c42a5", size = 15656, upload-time = "2026-06-19T16:12:50.361Z" }, + { url = "https://files.pythonhosted.org/packages/22/c6/cfe97f1beb13f5b7ca5c4348158c2de886d58ffba5be09a9376557f7d6f6/pyobjc_framework_libdispatch-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9c0ebf99520083bf17c007a544c100056a0d4ae5c346fb89e1bdfe6d041f16f2", size = 15679, upload-time = "2026-06-19T16:12:51.28Z" }, + { url = "https://files.pythonhosted.org/packages/d7/de/ef6b51bc72fe5ac1df80c34b1b13a97d0922ddd6bc5d3ecf5ead1557bf34/pyobjc_framework_libdispatch-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:3f56fd71b963a0b6e440ed2f0ea2fb635221758b7eb908ba38f96f5144b83ca3", size = 15946, upload-time = "2026-06-19T16:12:52.098Z" }, + { url = "https://files.pythonhosted.org/packages/42/87/5b4a6c8580f2a486daf4b0d14a2356c47abfda401b329e71e46ac9b5460c/pyobjc_framework_libdispatch-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:999bad9a2c9198c837ba8f57a3ca9f05b4fc4bf7b69318baaa266dd2ab2fc8f7", size = 15699, upload-time = "2026-06-19T16:12:52.917Z" }, + { url = "https://files.pythonhosted.org/packages/bd/44/68cff50cb37a6ea311b7e805105ea13c33043762772714bc25d269c0730d/pyobjc_framework_libdispatch-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:3fc93971f40d9757995c1e4b995a1614a468a5178be27e3d81e9bdc0b5e3cf75", size = 15981, upload-time = "2026-06-19T16:12:53.845Z" }, + { url = "https://files.pythonhosted.org/packages/b3/5d/1f48e023555817f1271e86849ebd092743fc8bd292b6f82e87aba5df6122/pyobjc_framework_libdispatch-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:26a096c81c8cf272f4f1bb8f6c4b7565e005d273d218b53b83d925da5292633f", size = 15719, upload-time = "2026-06-19T16:12:54.64Z" }, + { url = "https://files.pythonhosted.org/packages/d8/44/b45c32851a3bcd367c62804c23aa55ea7918af6e16fddf1df23f5d7ca750/pyobjc_framework_libdispatch-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:82c6512fb4985f3bcd6b60b0cff79a4b483b44d1d2e5405010e34dd4b60aa01b", size = 16009, upload-time = "2026-06-19T16:12:55.513Z" }, ] [[package]] name = "pyobjc-framework-libxpc" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/16/e4/364db7dc26f235e3d7eaab2f92057f460b39800bffdec3128f113388ac9f/pyobjc_framework_libxpc-12.1.tar.gz", hash = "sha256:e46363a735f3ecc9a2f91637750623f90ee74f9938a4e7c833e01233174af44d", size = 35186, upload-time = "2025-11-14T10:16:49.503Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/e5/92d47d5387baa85461be9802dccd90f6fe9232a98878caad7ab899d207df/pyobjc_framework_libxpc-12.2.1.tar.gz", hash = "sha256:83b814672715ef1f4f83eaaae49416a77db38579e6f6af2e14cd328a76f5d598", size = 37265, upload-time = "2026-06-19T16:20:57.109Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/f1/95af3a601744e1bc9b08d889f0bf0032b0d0ae8725976654e0d5fbe9a5f8/pyobjc_framework_libxpc-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f135b9734ee461c30dc692e58ce4b58c84c9a455738afe9ac70040c893625f4f", size = 19616, upload-time = "2025-11-14T09:53:10.139Z" }, - { url = "https://files.pythonhosted.org/packages/7c/c9/701630d025407497b7af50a795ddb6202c184da7f12b46aa683dae3d3552/pyobjc_framework_libxpc-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8d7201db995e5dcd38775fd103641d8fb69b8577d8e6a405c5562e6c0bb72fd1", size = 19620, upload-time = "2025-11-14T09:53:12.529Z" }, - { url = "https://files.pythonhosted.org/packages/82/7f/fdec72430f90921b154517a6f9bbeefa7bacfb16b91320742eb16a5955c5/pyobjc_framework_libxpc-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ba93e91e9ca79603dd265382e9f80e9bd32309cd09c8ac3e6489fc5b233676c8", size = 19730, upload-time = "2025-11-14T09:53:17.113Z" }, - { url = "https://files.pythonhosted.org/packages/0a/64/c4e2f9a4f92f4d2b84c0e213b4a9410968b5f181f15a764eeb43f92c4eb2/pyobjc_framework_libxpc-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:635520187a6456ad259e40dd04829caeef08561d0a1a0cfd09787ebd281d47b3", size = 19729, upload-time = "2025-11-14T09:53:19.038Z" }, - { url = "https://files.pythonhosted.org/packages/51/c2/654dd2a22b6f505ff706a66117c522029df9449a9a19ca4827af0d16b5b3/pyobjc_framework_libxpc-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:1c36e3e109a95275f90b319161265a7f6a5e0e674938ce49babdf3a64d9fc892", size = 20309, upload-time = "2025-11-14T09:53:22.657Z" }, - { url = "https://files.pythonhosted.org/packages/fc/9d/d66559d9183dae383962c79ca67eaabf7fe9f8bb9f65cf5a4369fbdcdd0e/pyobjc_framework_libxpc-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:bc5eaed7871fab8971631e99151ea0271f64d4059790c9f41a30ae4841f4fd89", size = 19451, upload-time = "2025-11-14T09:53:24.418Z" }, - { url = "https://files.pythonhosted.org/packages/6b/f6/cb5d5e6f83d94cff706dff533423fdf676249ee392dc9ae4acdd0e02d451/pyobjc_framework_libxpc-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:c862ed4f79c82e7a246fe49a8fae9e9684a7163512265f1c01790899dc730551", size = 20022, upload-time = "2025-11-14T09:53:26.605Z" }, + { url = "https://files.pythonhosted.org/packages/57/be/8a8c73f4020fccfd29ef7e70be506b48d0482bf481e61c1cc43e6e72b4bd/pyobjc_framework_libxpc-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f58dda691cfe0de53d06d6987c797c79b877d9e6d4ccc48afac0239856471ec", size = 19647, upload-time = "2026-06-19T16:12:56.509Z" }, + { url = "https://files.pythonhosted.org/packages/a6/86/e5110414fab0191fa10c5f9901e5f9feb4c8dfb3df3c3099a23e7c2000da/pyobjc_framework_libxpc-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:157db58d0bf530d7a2838c3229aa5fdd973fc2359beaa557fa3ab370ca541637", size = 19649, upload-time = "2026-06-19T16:12:57.531Z" }, + { url = "https://files.pythonhosted.org/packages/a0/27/ab74145db0b6e9eafbcfe24ee5f906f1466ecfa40265b20120affb48f946/pyobjc_framework_libxpc-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:905964e12dd9180e70bc88bb04cdc02e4920a48a045e62ebb47f207ab3085376", size = 19774, upload-time = "2026-06-19T16:12:58.453Z" }, + { url = "https://files.pythonhosted.org/packages/7c/3d/6432bb6e2333b3dcbc8da1fe26f4436895e3b7046efb70c4cd8c0ee92aea/pyobjc_framework_libxpc-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:158fd1b11eabc4c42d1bb60264ecd19871c3e24ab661e7c6d982bcb5b6cd8da7", size = 19780, upload-time = "2026-06-19T16:12:59.378Z" }, + { url = "https://files.pythonhosted.org/packages/2c/a5/89cb0149bd82f25f2555e9195d7d8f24be979fd7e88ffda0db6e6e61cbc9/pyobjc_framework_libxpc-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:dad8ca82fe4162e85c9a6057382eaa6adc6b3aa22011e1e454d6eafb997dfc02", size = 20332, upload-time = "2026-06-19T16:13:00.296Z" }, + { url = "https://files.pythonhosted.org/packages/3f/d0/afe32cbc4c7cd5a5856532ebc6b90b26d25beb244522a1d13e106a74787c/pyobjc_framework_libxpc-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:1610ef8016bdeb83d2193851e5c19fbf6073741d01a0d590afa4501531b788e1", size = 19509, upload-time = "2026-06-19T16:13:01.213Z" }, + { url = "https://files.pythonhosted.org/packages/74/a2/590645eae51a2aa113bf1221351738a6d00b7144323c98a1b08c2702e72b/pyobjc_framework_libxpc-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:90331eb7dee2a50e6cd70463dd156d7f679919b9d03e52864707bfdebea0adcb", size = 20038, upload-time = "2026-06-19T16:13:02.033Z" }, + { url = "https://files.pythonhosted.org/packages/19/35/87cc08847d11d21e385bbdb90d3274230ceabec8cdfa0aa021eb36aced09/pyobjc_framework_libxpc-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:88bba9ba27aa974a6d89dc1bbb5fbec1d96407ac36f01737ce92179d1b1d383d", size = 19515, upload-time = "2026-06-19T16:13:02.846Z" }, + { url = "https://files.pythonhosted.org/packages/04/b4/e24b80803a8e7e77fdc8caf36274822c9e13fec173467b21786625bb0462/pyobjc_framework_libxpc-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:153ba3845c64a9f8003a87f22de4b4ec678f2c36c329c8c3c96c30cffe7a0570", size = 20062, upload-time = "2026-06-19T16:13:04.185Z" }, ] [[package]] name = "pyobjc-framework-linkpresentation" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-quartz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e3/58/c0c5919d883485ccdb6dccd8ecfe50271d2f6e6ab7c9b624789235ccec5a/pyobjc_framework_linkpresentation-12.1.tar.gz", hash = "sha256:84df6779591bb93217aa8bd82c10e16643441678547d2d73ba895475a02ade94", size = 13330, upload-time = "2025-11-14T10:16:52.169Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/21/368316aa17c5a70a29fb5bc23d29e8608509de539ad5402b8e273dcae5f9/pyobjc_framework_linkpresentation-12.2.1.tar.gz", hash = "sha256:96f30800eef18543a4c75fff6b1a7cce7fcd649564784cc523341870908382c9", size = 13978, upload-time = "2026-06-19T16:20:57.903Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/51/226eb45f196f3bf93374713571aae6c8a4760389e1d9435c4a4cc3f38ea4/pyobjc_framework_linkpresentation-12.1-py2.py3-none-any.whl", hash = "sha256:853a84c7b525b77b114a7a8d798aef83f528ed3a6803bda12184fe5af4e79a47", size = 3865, upload-time = "2025-11-14T09:53:28.386Z" }, + { url = "https://files.pythonhosted.org/packages/54/14/76bdc42790ced9ab3be43a946a4466a0debc3d7e7642fa4836081768c382/pyobjc_framework_linkpresentation-12.2.1-py2.py3-none-any.whl", hash = "sha256:2299fc001002ecf501249a0a61bda35d50aae5057c9f2aca36274261add4fa4d", size = 3887, upload-time = "2026-06-19T16:13:05.149Z" }, ] [[package]] name = "pyobjc-framework-localauthentication" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-security" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8d/0e/7e5d9a58bb3d5b79a75d925557ef68084171526191b1c0929a887a553d4f/pyobjc_framework_localauthentication-12.1.tar.gz", hash = "sha256:2284f587d8e1206166e4495b33f420c1de486c36c28c4921d09eec858a699d05", size = 29947, upload-time = "2025-11-14T10:16:54.923Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/e8/fcbea8814ab28d00e18e4f6fc84af2fbf58eee916bfe85a30685abef0729/pyobjc_framework_localauthentication-12.2.1.tar.gz", hash = "sha256:05162d6d603fe6a9bf8eba8d5df7da379bc2b8eaf2a405bf0132a71477f5ed1c", size = 33086, upload-time = "2026-06-19T16:20:58.613Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/bd/f5b5b2bdce4340b917dedbd95cca90ea74dc549fa33235315a3013871fbb/pyobjc_framework_localauthentication-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b1b24c9b453c3b3eb862a78a3a7387f357a90b99ec61cd1950b38e1cf08307ec", size = 10762, upload-time = "2025-11-14T09:53:30.228Z" }, - { url = "https://files.pythonhosted.org/packages/6e/cb/cf9d13943e13dc868a68844448a7714c16f4ee6ecac384d21aaa5ac43796/pyobjc_framework_localauthentication-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2d7e1b3f987dc387361517525c2c38550dc44dfb3ba42dec3a9fbf35015831a6", size = 10762, upload-time = "2025-11-14T09:53:32.035Z" }, - { url = "https://files.pythonhosted.org/packages/05/93/91761ad4e5fa1c3ec25819865d1ccfbee033987147087bff4fcce67a4dc4/pyobjc_framework_localauthentication-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3af1acd287d830cc7f912f46cde0dab054952bde0adaf66c8e8524311a68d279", size = 10773, upload-time = "2025-11-14T09:53:34.074Z" }, - { url = "https://files.pythonhosted.org/packages/e4/f5/a12c76525e4839c7fc902c6b0f0c441414a4dd9bc9a2d89ae697f6cd8850/pyobjc_framework_localauthentication-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e26e746717f4774cce0568debec711f1d8effc430559ad634ff6b06fefd0a0bf", size = 10792, upload-time = "2025-11-14T09:53:35.876Z" }, - { url = "https://files.pythonhosted.org/packages/5c/ed/2714934b027afc6a99d0d817e42bf482d08c711422795fe777e3cd9ad8be/pyobjc_framework_localauthentication-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:02357cddc979aa169782bf09f380aab1c3af475c9eb6ffb07c77084ed10f6a6a", size = 10931, upload-time = "2025-11-14T09:53:37.672Z" }, - { url = "https://files.pythonhosted.org/packages/e6/58/6dfb304103b4cdaee44acd7f5093c07f3053df0cc9648c87876f1e5fc690/pyobjc_framework_localauthentication-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:f8d525ed2ad5cd56e420436187b534454d1f7d1fae6e585df82397d6d92c6e54", size = 10841, upload-time = "2025-11-14T09:53:39.337Z" }, - { url = "https://files.pythonhosted.org/packages/17/af/1c7ce26b46cc978852895017212cf3637d5334274213265234149e0937d4/pyobjc_framework_localauthentication-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:93c5470a9d60b53afa0faf31d95dc8d6fc3a7ff85c425ab157ea491b6dc3af39", size = 10975, upload-time = "2025-11-14T09:53:41.177Z" }, + { url = "https://files.pythonhosted.org/packages/67/6a/3208f344aac7b722a00f0a7592e875bf44592fd2335ad1319daf487bb2e7/pyobjc_framework_localauthentication-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b89ad8bb9d9ea053902055e656ed694d625a0ea8b950b176b01a50ec82b73343", size = 10892, upload-time = "2026-06-19T16:13:06.243Z" }, + { url = "https://files.pythonhosted.org/packages/2e/d5/47486c13c3481ef868411f61dd4c4f99bc8a45f5fb2675da132160211817/pyobjc_framework_localauthentication-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be6c72699cc3c3a970483f1cf7fce3b40663253202ac023b8439d34f93ea224a", size = 10896, upload-time = "2026-06-19T16:13:07.216Z" }, + { url = "https://files.pythonhosted.org/packages/bb/60/5c4f1fe4e70dc68ddb3d55dcb8f81ae7806972fa37108a0f9a394020e657/pyobjc_framework_localauthentication-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:591dc5bd8143868c0414a487c433d6c651c44c3f0ec751ad30ff3e0f4260db6a", size = 10905, upload-time = "2026-06-19T16:13:08.002Z" }, + { url = "https://files.pythonhosted.org/packages/9f/fc/ca8853e87bf1d1bcf2a1331eef8f410a7177aaee6f019c6ee99036d28592/pyobjc_framework_localauthentication-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:29d6de29ece22d49a95279de80405f1441ccec74ea30db5251ffa8f46c51ae94", size = 10920, upload-time = "2026-06-19T16:13:08.781Z" }, + { url = "https://files.pythonhosted.org/packages/61/af/3a02c67f96bda4f1a2d3d3e5923e5ec70946b62e054ae64b6dfa81695ab9/pyobjc_framework_localauthentication-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:52f89e993950b66d2ec701fb2d5f523a5e6dfafb8e0da9c194aa4fc5aae3a30e", size = 11063, upload-time = "2026-06-19T16:13:09.615Z" }, + { url = "https://files.pythonhosted.org/packages/40/40/3f4d8628b4b60cbe6ef1c9d96250b4f54854e4f0beecc102a80594899cbc/pyobjc_framework_localauthentication-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:326f9bea423f705d61bdd36ebae087ade99c39c91fcd6643780697d539e8a575", size = 10962, upload-time = "2026-06-19T16:13:10.429Z" }, + { url = "https://files.pythonhosted.org/packages/04/32/d289d8778665de6098a0f6790198dde03946985ec46dfcd438acd7c99d05/pyobjc_framework_localauthentication-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:49a99b96196e1ebe0791ed546bb538d4c28ee4ffbdd1b0e4c7666d063104f849", size = 11106, upload-time = "2026-06-19T16:13:11.222Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a5/8310eab80699cf11329de3fc7e1ecb276c935f53ff29f3691d72eaa870c3/pyobjc_framework_localauthentication-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:867541c093477d5ed26efdd911fa131db533d837cad575cfa5930a1fb5d7f434", size = 10971, upload-time = "2026-06-19T16:13:12.068Z" }, + { url = "https://files.pythonhosted.org/packages/eb/45/42ac5c192d2c2c1bb60d91af9e85ed357f3d8f4957056326745e0631609e/pyobjc_framework_localauthentication-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:aa094c1a5b2dbb02fb3be32a0a5fcfb23aea21815827ece2655737d4b8dd7af2", size = 11100, upload-time = "2026-06-19T16:13:12.813Z" }, ] [[package]] name = "pyobjc-framework-localauthenticationembeddedui" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-localauthentication" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/31/20/83ab4180e29b9a4a44d735c7f88909296c6adbe6250e8e00a156aff753e1/pyobjc_framework_localauthenticationembeddedui-12.1.tar.gz", hash = "sha256:a15ec44bf2769c872e86c6b550b6dd4f58d4eda40ad9ff00272a67d279d1d4e9", size = 13611, upload-time = "2025-11-14T10:16:57.145Z" } +sdist = { url = "https://files.pythonhosted.org/packages/16/be/1cae60d052314b16e2e279cc187b918637e6afa7f3bc7aca6eb2ae6c2256/pyobjc_framework_localauthenticationembeddedui-12.2.1.tar.gz", hash = "sha256:f97666380541a40e593c7ed2214c11da30effcc909a4b13595220050a9888577", size = 14146, upload-time = "2026-06-19T16:20:59.37Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/7d/0d46639c7a26b6af928ab4c822cd28b733791e02ac28cc84c3014bcf7dc7/pyobjc_framework_localauthenticationembeddedui-12.1-py2.py3-none-any.whl", hash = "sha256:a7ce7b56346597b9f4768be61938cbc8fc5b1292137225b6c7f631b9cde97cd7", size = 3991, upload-time = "2025-11-14T09:53:42.958Z" }, + { url = "https://files.pythonhosted.org/packages/bc/c5/42b5b4260d20309ab9832c98bbb20585191248c846036e8975b9f194809e/pyobjc_framework_localauthenticationembeddedui-12.2.1-py2.py3-none-any.whl", hash = "sha256:a77da7a7471ee9277d4ae7269fb3d0bc508c5908e93ccf8e4cdf723bde21d1af", size = 4013, upload-time = "2026-06-19T16:13:13.559Z" }, ] [[package]] name = "pyobjc-framework-mailkit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2a/98/3d9028620c1cd32ff4fb031155aba3b5511e980cdd114dd51383be9cb51b/pyobjc_framework_mailkit-12.1.tar.gz", hash = "sha256:d5574b7259baec17096410efcaacf5d45c7bb5f893d4c25cbb7072369799b652", size = 20996, upload-time = "2025-11-14T10:16:59.449Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/09/49fc5fe2ba2489b2844e2a2f25cf526718f34c53847009fff2e9b1f94fc2/pyobjc_framework_mailkit-12.2.1.tar.gz", hash = "sha256:8a8e84f6828f13c7c67c6f8f299889127df05d25ffe52b6c942d69a329681c75", size = 23888, upload-time = "2026-06-19T16:21:00.167Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/8d/3c968b736a3a8bd9d8e870b39b1c772a013eea1b81b89fc4efad9021a6cb/pyobjc_framework_mailkit-12.1-py2.py3-none-any.whl", hash = "sha256:536ac0c4ea3560364cd159a6512c3c18a744a12e4e0883c07df0f8a2ff21e3fe", size = 4871, upload-time = "2025-11-14T09:53:44.697Z" }, + { url = "https://files.pythonhosted.org/packages/53/cc/4a9e1f5a387df7e5bec26ad3488e414d6fb065c46131072d20f07a6314f8/pyobjc_framework_mailkit-12.2.1-py2.py3-none-any.whl", hash = "sha256:e453e42d8ad97f58593296a85c71ec8c71378f8d5d79ac4548845f5dcba725b9", size = 5018, upload-time = "2026-06-19T16:13:14.528Z" }, ] [[package]] name = "pyobjc-framework-mapkit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, @@ -2945,33 +3103,35 @@ dependencies = [ { name = "pyobjc-framework-corelocation" }, { name = "pyobjc-framework-quartz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/36/bb/2a668203c20e509a648c35e803d79d0c7f7816dacba74eb5ad8acb186790/pyobjc_framework_mapkit-12.1.tar.gz", hash = "sha256:dbc32dc48e821aaa9b4294402c240adbc1c6834e658a07677b7c19b7990533c5", size = 63520, upload-time = "2025-11-14T10:17:04.221Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/29/6f5a817054f8998629ae4c8146674a78850a56477ebbc8e6cad2732dad3c/pyobjc_framework_mapkit-12.2.1.tar.gz", hash = "sha256:b0d34e03e100adb471b91f0915b6ecb2266251886e54a1729ee534ec832ad392", size = 79578, upload-time = "2026-06-19T16:21:01.038Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/54/d6cc71c8dd456c36367f198e9373948bb012b8c690c9fb0966d3adf03488/pyobjc_framework_mapkit-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f48a5c9a95735d41a12929446fc45fd43913367faddedf852ab02e0452e06db4", size = 22492, upload-time = "2025-11-14T09:53:47.342Z" }, - { url = "https://files.pythonhosted.org/packages/d0/8f/411067e5c5cd23b9fe4c5edfb02ed94417b94eefe56562d36e244edc70ff/pyobjc_framework_mapkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e8aa82d4aae81765c05dcd53fd362af615aa04159fc7a1df1d0eac9c252cb7d5", size = 22493, upload-time = "2025-11-14T09:53:50.112Z" }, - { url = "https://files.pythonhosted.org/packages/11/00/a3de41cdf3e6cd7a144e38999fe1ea9777ad19e19d863f2da862e7affe7b/pyobjc_framework_mapkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:84ad7766271c114bdc423e4e2ff5433e5fc6771a3338b5f8e4b54d0340775800", size = 22518, upload-time = "2025-11-14T09:53:52.727Z" }, - { url = "https://files.pythonhosted.org/packages/5e/f1/db2aa9fa44669b9c060a3ae02d5661052a05868ccba1674543565818fdaf/pyobjc_framework_mapkit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ea210ba88bef2468adb5c8303071d86118d630bf37a29d28cf236c13c3bb85ad", size = 22539, upload-time = "2025-11-14T09:53:55.543Z" }, - { url = "https://files.pythonhosted.org/packages/c1/e4/7dd9f7333eea7f4666274f568cac03e4687b442c9b20622f244497700177/pyobjc_framework_mapkit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:dfee615b73bb687101f08e7fd839eea2aa8b241563ad4cabbcb075d12f598266", size = 22712, upload-time = "2025-11-14T09:53:58.159Z" }, - { url = "https://files.pythonhosted.org/packages/06/ef/f802b9f0a620039b277374ba36702a0e359fe54e8526dcd90d2b061d2594/pyobjc_framework_mapkit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c2f47e813e81cb13e48343108ea3185a856c13bab1cb17e76d0d87568e18459b", size = 22562, upload-time = "2025-11-14T09:54:00.735Z" }, - { url = "https://files.pythonhosted.org/packages/fd/6b/aae01ed3322326e034113140d41a6d7529d2a298d9da3ce1f89184fbeb95/pyobjc_framework_mapkit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:59a746ac2d4bb32fca301325430b37cde7959213ce1b6c3e30fa40d6085bf75a", size = 22775, upload-time = "2025-11-14T09:54:03.354Z" }, + { url = "https://files.pythonhosted.org/packages/01/83/3e1472513e078410901e1bbc795408e30c4361715866b72d4f57232aae39/pyobjc_framework_mapkit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:473e3a61ffd69b81b5bd1c70c4e9c88fbe4d20d0d1ef60433bda8aaebe45a5c2", size = 22840, upload-time = "2026-06-19T16:13:15.419Z" }, + { url = "https://files.pythonhosted.org/packages/14/ae/8e0b03353841dbf36a380ea73fba3202c98ca94c47137fafaf04811a7b83/pyobjc_framework_mapkit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c312c5ca0634c6676328c9b205d36fcd3138eacaf339480b9e9572e0c579e9d5", size = 22833, upload-time = "2026-06-19T16:13:16.587Z" }, + { url = "https://files.pythonhosted.org/packages/cf/29/45ae9a8e02b487270d29cc65e968cce3ea82c9b1fc6d208bc3d305c40f8d/pyobjc_framework_mapkit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f423892cba28bef2becdc5cef9011e358f223b66478aecc8ae5e3083c8e1700f", size = 22863, upload-time = "2026-06-19T16:13:17.659Z" }, + { url = "https://files.pythonhosted.org/packages/b9/94/bdf2e3e7e6d5a05fb9fe11de7d8b85f75e49eaa3b31a8c7b45b2f28f804e/pyobjc_framework_mapkit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e94362c148355d36aec29d7a77c14178e44e464504dd2cbcc8de28b18a5e00e8", size = 22890, upload-time = "2026-06-19T16:13:18.735Z" }, + { url = "https://files.pythonhosted.org/packages/22/a9/744ce0d68454c29522d8dd97a00f4a260cc06941763a2ac6cc189c653e66/pyobjc_framework_mapkit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9c128d8e4af7698629545d5761cc1fca5a26a3e13121c2ff7c77e28308922b70", size = 23075, upload-time = "2026-06-19T16:13:19.855Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d4/2b796d0da8e2d90d7402ff58070c61a16be33c842de4eb270d2125e72749/pyobjc_framework_mapkit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:1263fb5c1e13ad7d34e60b455cd56b79627b4505488cbaaacee9ad8acf4eee80", size = 22914, upload-time = "2026-06-19T16:13:20.785Z" }, + { url = "https://files.pythonhosted.org/packages/d0/59/adf23b8bf39f02e8820b97eb50f23fb145d7f6d43a471f1b3047ed0209af/pyobjc_framework_mapkit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ceb93bb2aa7ced0dc200508d9baa0de2438f8e0194f1dfabe7407ab0d734bafb", size = 23120, upload-time = "2026-06-19T16:13:21.632Z" }, + { url = "https://files.pythonhosted.org/packages/4c/64/87caba8d8a9cf4f2556718839a9fac9c9958b1ef3d6933cc4c93cb39bba8/pyobjc_framework_mapkit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:5b39d0cbeca27e3eef130e5686adc8e0edd06981e0f8afe359ca070ed790f329", size = 22920, upload-time = "2026-06-19T16:13:22.518Z" }, + { url = "https://files.pythonhosted.org/packages/5e/d7/b953ae58dfdef45fe6360a1731986d519277cbf087037da4ce9a89d42f76/pyobjc_framework_mapkit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:f9ef9633e84afa849bcb923ef0ee9cc78106d210e45637f58941dca4408681f9", size = 23130, upload-time = "2026-06-19T16:13:23.362Z" }, ] [[package]] name = "pyobjc-framework-mediaaccessibility" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e0/10/dc1007e56944ed2e981e69e7b2fed2b2202c79b0d5b742b29b1081d1cbdd/pyobjc_framework_mediaaccessibility-12.1.tar.gz", hash = "sha256:cc4e3b1d45e84133d240318d53424eff55968f5c6873c2c53267598853445a3f", size = 16325, upload-time = "2025-11-14T10:17:07.454Z" } +sdist = { url = "https://files.pythonhosted.org/packages/be/46/c07388b3911f10cf84347c0fc7b250792e6bdabbdd9a51083331b8ae58ff/pyobjc_framework_mediaaccessibility-12.2.1.tar.gz", hash = "sha256:6d816a09d874519bea85035db7c62c0566063a5be63e3ab25b2059205576ade8", size = 17250, upload-time = "2026-06-19T16:21:01.963Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/0c/7fb5462561f59d739192c6d02ba0fd36ad7841efac5a8398a85a030ef7fc/pyobjc_framework_mediaaccessibility-12.1-py2.py3-none-any.whl", hash = "sha256:2ff8845c97dd52b0e5cf53990291e6d77c8a73a7aac0e9235d62d9a4256916d1", size = 4800, upload-time = "2025-11-14T09:54:05.04Z" }, + { url = "https://files.pythonhosted.org/packages/71/bd/bc6277582c43eb6b4916cbd411352bd5066fa3e1b48ed5add4e41932e4cd/pyobjc_framework_mediaaccessibility-12.2.1-py2.py3-none-any.whl", hash = "sha256:8543ff6664ce35815083ab10a6f4b1b9241594d60c27132e08385b0316d55013", size = 4847, upload-time = "2026-06-19T16:13:24.243Z" }, ] [[package]] name = "pyobjc-framework-mediaextension" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, @@ -2979,336 +3139,360 @@ dependencies = [ { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-coremedia" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d6/aa/1e8015711df1cdb5e4a0aa0ed4721409d39971ae6e1e71915e3ab72423a3/pyobjc_framework_mediaextension-12.1.tar.gz", hash = "sha256:44409d63cc7d74e5724a68e3f9252cb62fd0fd3ccf0ca94c6a33e5c990149953", size = 39425, upload-time = "2025-11-14T10:17:11.486Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/fd/8f2489cf3673a705d806124edb73ea27438919f58af1fa41dd789163838c/pyobjc_framework_mediaextension-12.2.1.tar.gz", hash = "sha256:d31057582878ec2574559ad253fd448de3f11a68e454d14f0665348c82b3dee0", size = 44554, upload-time = "2026-06-19T16:21:02.824Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/fa/8b3f2dc9cbf39ba7b647d70da464112bcaa7159118d688bdbdb64b062d5a/pyobjc_framework_mediaextension-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1aa7964ffa9e1d877a45c86692047928dbe2735188d89b52aad7d6e24b2fbcb9", size = 38961, upload-time = "2025-11-14T09:54:09.549Z" }, - { url = "https://files.pythonhosted.org/packages/8e/6f/60b63edf5d27acf450e4937b7193c1a2bd6195fee18e15df6a5734dedb71/pyobjc_framework_mediaextension-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9555f937f2508bd2b6264cba088e2c2e516b2f94a6c804aee40e33fd89c2fb78", size = 38957, upload-time = "2025-11-14T09:54:13.22Z" }, - { url = "https://files.pythonhosted.org/packages/2b/ed/99038bcf72ec68e452709af10a087c1377c2d595ba4e66d7a2b0775145d2/pyobjc_framework_mediaextension-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:442bc3a759efb5c154cb75d643a5e182297093533fcdd1c24be6f64f68b93371", size = 38973, upload-time = "2025-11-14T09:54:16.701Z" }, - { url = "https://files.pythonhosted.org/packages/01/df/7ecdbac430d2d2844fb2145e26f3e87a8a7692fa669d0629d90f32575991/pyobjc_framework_mediaextension-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0f3bdca0eb11923efc1e3b95beb1e6e01c675fd7809ed7ef0b475334e3562931", size = 38991, upload-time = "2025-11-14T09:54:20.316Z" }, - { url = "https://files.pythonhosted.org/packages/fc/98/88ac2edeb69bde3708ef3f7b6434f810ba89321d8375914ad642c9a575b0/pyobjc_framework_mediaextension-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0101b8495051bac9791a0488530386eefe9c722477a5239c5bd208967d0eaa67", size = 39198, upload-time = "2025-11-14T09:54:23.806Z" }, - { url = "https://files.pythonhosted.org/packages/4a/f0/fcff5206bb1a7ce89b9923ceb3215af767fd3c91dafc9d176ba08d6a3f30/pyobjc_framework_mediaextension-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:4f66719c97f508c619368377d768266c58cc783cf5fc51bd9d8e5e0cad0c824c", size = 38980, upload-time = "2025-11-14T09:54:27.413Z" }, - { url = "https://files.pythonhosted.org/packages/26/30/bdea26fe2ca33260edcbd93f212e0141c6e145586d53c58fac4416e0135f/pyobjc_framework_mediaextension-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:eef6ab5104fdfb257e17a73c2e7c11b0db09a94ced24f2a4948e1d593ec6200e", size = 39191, upload-time = "2025-11-14T09:54:30.798Z" }, + { url = "https://files.pythonhosted.org/packages/0a/79/248e532fa0eb07a90ef8a793bfc84cdbfc5f484a2dcbb5e7aa6ab4dcfc81/pyobjc_framework_mediaextension-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:efac3f8d4f36f21cf4559bde2a58ee72d8c9d955028b59435852209d4c06eb9b", size = 39029, upload-time = "2026-06-19T16:13:25.399Z" }, + { url = "https://files.pythonhosted.org/packages/7f/12/4606689b0259defde9897f6d046fbfa72360520a9a97e78c9739c760a314/pyobjc_framework_mediaextension-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e8d383f458e4812993a0b4132f128a588ea34d9be51644261ddd04f90cf530ed", size = 39032, upload-time = "2026-06-19T16:13:26.521Z" }, + { url = "https://files.pythonhosted.org/packages/e3/6e/3b9f69ef0caea40e6175e4226a9a8df7c7997cfb8dbe6ca430575c21b4df/pyobjc_framework_mediaextension-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bbf5ea3683dc5b6a2b0f218ec0e9935a21e6aebe7d00760be78395268fb04770", size = 39048, upload-time = "2026-06-19T16:13:28.614Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/02aa57d87451fab20d27c270545243e4868b2172b0519f75a1c7755a3e04/pyobjc_framework_mediaextension-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:600e289380d19b24d7166027988c63b5fa8cd297a379fa9538dd0bcffd527c0a", size = 39062, upload-time = "2026-06-19T16:13:29.79Z" }, + { url = "https://files.pythonhosted.org/packages/05/4e/a338813ca7bb23e59d44c15eea0c43d2120de949c93acf408117b17f4051/pyobjc_framework_mediaextension-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c84027e9c775d51ae58c5c532935145ef1ddaae7b24b769e6ff7026b62a7e4ea", size = 39263, upload-time = "2026-06-19T16:13:31.184Z" }, + { url = "https://files.pythonhosted.org/packages/b6/33/5079aef3cfb5740e11f214d8c5ae9318e774f66a8891d72524f1ea2c9384/pyobjc_framework_mediaextension-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:1f5554002df229352672d5d2f7ec5b8d63cb8fae762df67da7ce0902ecbd2837", size = 39051, upload-time = "2026-06-19T16:13:32.275Z" }, + { url = "https://files.pythonhosted.org/packages/5a/67/16c437191e23c781a61e8008473ace2a4ddfe2f9151c3236427c3ed2c2a7/pyobjc_framework_mediaextension-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:116d9cf91c283bd741ee1f5f8350e858d002a002c655b0378c471ec0d81ddef5", size = 39263, upload-time = "2026-06-19T16:13:33.506Z" }, + { url = "https://files.pythonhosted.org/packages/14/c6/07abc4c226c2b81457b2aaaf2fa349cfcb26a997117f70292343e431d435/pyobjc_framework_mediaextension-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:6d3779c1923a059b8d5c4e5ec5c190d015f63270d4d657e0f5ad1a97df99ff19", size = 39046, upload-time = "2026-06-19T16:13:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/0f/4b/c14389fc31083b0f5ac3efdaa6d580ce8099300e5028a96c370d67e78355/pyobjc_framework_mediaextension-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:1f0da78f3ccedc09db0d8dfa6d6c008fbb411e5f9eb52c4dc0318b57416d37c1", size = 39257, upload-time = "2026-06-19T16:13:35.423Z" }, ] [[package]] name = "pyobjc-framework-medialibrary" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-quartz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/e9/848ebd02456f8fdb41b42298ec585bfed5899dbd30306ea5b0a7e4c4b341/pyobjc_framework_medialibrary-12.1.tar.gz", hash = "sha256:690dcca09b62511df18f58e8566cb33d9652aae09fe63a83f594bd018b5edfcd", size = 15995, upload-time = "2025-11-14T10:17:15.45Z" } +sdist = { url = "https://files.pythonhosted.org/packages/01/3b/af0d8cf4bef550b77ecddea3db59bce0ab3ab5e22e258c583e92ba5a630a/pyobjc_framework_medialibrary-12.2.1.tar.gz", hash = "sha256:18fb56e727399f11ea588d2c512b7585147386892d72c65eb9c4b6387abd6643", size = 19052, upload-time = "2026-06-19T16:21:04.063Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/cd/eeaf8585a343fda5b8cf3b8f144c872d1057c845202098b9441a39b76cb0/pyobjc_framework_medialibrary-12.1-py2.py3-none-any.whl", hash = "sha256:1f03ad6802a5c6e19ee3208b065689d3ec79defe1052cb80e00f54e1eff5f2a0", size = 4361, upload-time = "2025-11-14T09:54:32.259Z" }, + { url = "https://files.pythonhosted.org/packages/12/da/9f38ae220465c54ca064d24a0e42d93dc2da901ec811324c04d2ccc6a229/pyobjc_framework_medialibrary-12.2.1-py2.py3-none-any.whl", hash = "sha256:4d69f910f17bbccb4f815b171270b9279f9d7526b318cf45a7b2ddc84fc38d22", size = 4381, upload-time = "2026-06-19T16:13:36.34Z" }, ] [[package]] name = "pyobjc-framework-mediaplayer" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-avfoundation" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e9/f0/851f6f47e11acbd62d5f5dcb8274afc969135e30018591f75bf3cbf6417f/pyobjc_framework_mediaplayer-12.1.tar.gz", hash = "sha256:5ef3f669bdf837d87cdb5a486ec34831542360d14bcba099c7c2e0383380794c", size = 35402, upload-time = "2025-11-14T10:17:18.97Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/b1/eda7f1cbdb98a712239ea1a5deb12821d07055e16c51e8c25167fc801578/pyobjc_framework_mediaplayer-12.2.1.tar.gz", hash = "sha256:6acead24bb8f12e202976142db656c553b4a25ca2348165c35ce02862a93757a", size = 42670, upload-time = "2026-06-19T16:21:04.973Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/58/c0/038ee3efd286c0fbc89c1e0cb688f4670ed0e5803aa36e739e79ffc91331/pyobjc_framework_mediaplayer-12.1-py2.py3-none-any.whl", hash = "sha256:85d9baec131807bfdf0f4c24d4b943e83cce806ab31c95c7e19c78e3fb7eefc8", size = 7120, upload-time = "2025-11-14T09:54:33.901Z" }, + { url = "https://files.pythonhosted.org/packages/ce/04/ca6e7b7c3827c3e835e81fadc7f3a26dd774a7a80fea7fce1cbd1ca044cd/pyobjc_framework_mediaplayer-12.2.1-py2.py3-none-any.whl", hash = "sha256:d37f5ede14fe70c547eac4abb49c9c96086f99acf8236985acadfcb07c851a5b", size = 7200, upload-time = "2026-06-19T16:13:37.48Z" }, ] [[package]] name = "pyobjc-framework-mediatoolbox" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/71/be5879380a161f98212a336b432256f307d1dcbaaaeb8ec988aea2ada2cd/pyobjc_framework_mediatoolbox-12.1.tar.gz", hash = "sha256:385b48746a5f08756ee87afc14037e552954c427ed5745d7ece31a21a7bad5ab", size = 22305, upload-time = "2025-11-14T10:17:22.501Z" } +sdist = { url = "https://files.pythonhosted.org/packages/03/61/4970e65b4efa1aac9493dbf8420fcc5d053433bf21c19eeec6cc7c8f7fee/pyobjc_framework_mediatoolbox-12.2.1.tar.gz", hash = "sha256:f8757deb15870b7543e2880aa4e7bd248fbc92f6a55763a99fda3703b1b1327d", size = 22811, upload-time = "2026-06-19T16:21:05.897Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/f0/e567b73e1bf1740339114b71faf724859927e68b06ff6a5c6791e5b7d66a/pyobjc_framework_mediatoolbox-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91e039f988d2fd8cb852ee880e853a90902bb0fe9c337d1947241b79db145244", size = 12647, upload-time = "2025-11-14T09:54:35.832Z" }, - { url = "https://files.pythonhosted.org/packages/8f/7a/f20ebd3c590b2cc85cde3e608e49309bfccf9312e4aca7b7ea60908d36d7/pyobjc_framework_mediatoolbox-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74de0cb2d5aaa77e81f8b97eab0f39cd2fab5bf6fa7c6fb5546740cbfb1f8c1f", size = 12656, upload-time = "2025-11-14T09:54:39.215Z" }, - { url = "https://files.pythonhosted.org/packages/9c/94/d5ee221f2afbc64b2a7074efe25387cd8700e8116518904b28091ea6ad74/pyobjc_framework_mediatoolbox-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d7bcfeeff3fbf7e9e556ecafd8eaed2411df15c52baf134efa7480494e6faf6d", size = 12818, upload-time = "2025-11-14T09:54:41.251Z" }, - { url = "https://files.pythonhosted.org/packages/ca/30/79aa0010b30f3c54c68673d00f06f45ef28f5093ff1e927d68b5376ea097/pyobjc_framework_mediatoolbox-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1529a754cdb5b32797d297c0bf6279c7c14a3f7088f2dfbded09edcbfda19838", size = 12830, upload-time = "2025-11-14T09:54:43.191Z" }, - { url = "https://files.pythonhosted.org/packages/da/26/ae890f8ecce3fdda3e3a518426665467d36945c7c2729da1b073b1c44ff6/pyobjc_framework_mediatoolbox-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:13afec7d9f094ca5642e32b98680d1ee59aaa11a3d694cb1a6e454f72003f51c", size = 13420, upload-time = "2025-11-14T09:54:45.133Z" }, - { url = "https://files.pythonhosted.org/packages/bb/42/f0354b949f1eda6a57722a7450c77ff6689e53f9b2a933c4911e4385c2c8/pyobjc_framework_mediatoolbox-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:59921d4155a88d4acd04e80497707ac0208af3ff41574acba68214376e9fca23", size = 12808, upload-time = "2025-11-14T09:54:47.029Z" }, - { url = "https://files.pythonhosted.org/packages/74/1e/7d9ffccd2053cd540e45e24aec03b70ac3d93d8bd99c8005b468a260c8a2/pyobjc_framework_mediatoolbox-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d99bf31c46b382f466888d1d80f738309916cbb83be0b4f1ccab5200de8f06c9", size = 13411, upload-time = "2025-11-14T09:54:49.228Z" }, + { url = "https://files.pythonhosted.org/packages/6b/63/d51291e61565615633d88e8f11eb33875659100415a6b35147bd32cc370b/pyobjc_framework_mediatoolbox-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:17a55fe0a542cd8ed6f7f1d19cec4865fe92d83a8157653586bdf52f2d247d90", size = 12671, upload-time = "2026-06-19T16:13:38.496Z" }, + { url = "https://files.pythonhosted.org/packages/c8/25/3b6de68c2441d9b5abe78af9ffdaf581a2f4c7858c28516950c14bb73c28/pyobjc_framework_mediatoolbox-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0c5047e20a1affe434a4aa073ae9c1baea2b677f37a537b2dbe4d8552bc8f82d", size = 12686, upload-time = "2026-06-19T16:13:39.785Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8f/9ae04dce47a830fa5c0a43b23188f482694f1bc74bf77381888c08ac731e/pyobjc_framework_mediatoolbox-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2b4f70f6760c5cf49654f6256877e9875423507764af787c770e09214e5891dd", size = 12841, upload-time = "2026-06-19T16:13:40.832Z" }, + { url = "https://files.pythonhosted.org/packages/b4/f8/f17e483d63424753f56b397bc93d510035b3bc5a579896eb127cf817b7a3/pyobjc_framework_mediatoolbox-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:53be99e67f027fedac2045c4efac256725e1f6a122830aa4733fc3db608fe014", size = 12854, upload-time = "2026-06-19T16:13:41.706Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d4/afc7d67f91f5efbc3d4885ad2e4ddc2598a500f15973936df47e6c8f4b88/pyobjc_framework_mediatoolbox-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6f3a58249339267f5c40dad9bc945642bf5fa167a9be74353f5c76ef42ed4510", size = 13440, upload-time = "2026-06-19T16:13:42.607Z" }, + { url = "https://files.pythonhosted.org/packages/6d/6c/ecef85b04732a39865a61687c42537ae1d27119f91c586197d196db1cc89/pyobjc_framework_mediatoolbox-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:082c54423dd1080ce7cf9595e73997c6a0ef2e38c15be34ecb1e9a81163e9180", size = 12829, upload-time = "2026-06-19T16:13:43.722Z" }, + { url = "https://files.pythonhosted.org/packages/50/bc/97daef9ff7c2d8bcd67c4b8b52f117872f7e9b5e3c4cd561a0c2eebb770d/pyobjc_framework_mediatoolbox-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:88618fa1c1f0f5cd03ec349eb6e864b88fa3f7b88014299cc77cf56de5d4b3f4", size = 13438, upload-time = "2026-06-19T16:13:45.711Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f9/73bac498691fbb324cc4eedd6ada07e19f4d3f57c45211a7ce54f56a4989/pyobjc_framework_mediatoolbox-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:f3b71021c5416c58d756441eeb52f5153ae696fe7ff50b1e470934ff947c1a05", size = 12843, upload-time = "2026-06-19T16:13:47.373Z" }, + { url = "https://files.pythonhosted.org/packages/f9/37/b6799c5892247a6c5f3b2be13cb8c90ae3cd8d8d115c764f159714ad3689/pyobjc_framework_mediatoolbox-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:a2ff7b9ada192d3d949ddcc2d52f2149ce812ceb97fb5f1f67016cbfc7929d03", size = 13463, upload-time = "2026-06-19T16:13:48.496Z" }, ] [[package]] name = "pyobjc-framework-metal" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e7/06/a84f7eb8561d5631954b9458cfca04b690b80b5b85ce70642bc89335f52a/pyobjc_framework_metal-12.1.tar.gz", hash = "sha256:bb554877d5ee2bf3f340ad88e8fe1b85baab7b5ec4bd6ae0f4f7604147e3eae7", size = 181847, upload-time = "2025-11-14T10:17:34.157Z" } +sdist = { url = "https://files.pythonhosted.org/packages/15/46/5920d6cb66cbbe298744889b10b3266b1408ad823855f55cdcb967c0d51d/pyobjc_framework_metal-12.2.1.tar.gz", hash = "sha256:cd362194bdb7fd2a9116b8dc1e6b14ce19629136304cdf6b88d105a969fda72c", size = 238139, upload-time = "2026-06-19T16:21:06.897Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/be/6edbdb4ef80fa1806562294bd7afe607d09f1b3e83291d9fa2f85c7a8457/pyobjc_framework_metal-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d563b540ef659176938f0eaa7d8353d5b7d4235aa76fc7fddb1beb00391c0905", size = 75919, upload-time = "2025-11-14T09:54:55.241Z" }, - { url = "https://files.pythonhosted.org/packages/1d/cf/edbb8b6dd084df3d235b74dbeb1fc5daf4d063ee79d13fa3bc1cb1779177/pyobjc_framework_metal-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:59e10f9b36d2e409f80f42b6175457a07b18a21ca57ff268f4bc519cd30db202", size = 75920, upload-time = "2025-11-14T09:55:01.048Z" }, - { url = "https://files.pythonhosted.org/packages/d0/48/9286d06e1b14c11b65d3fea1555edc0061d9ebe11898dff8a14089e3a4c9/pyobjc_framework_metal-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:38ab566b5a2979a43e13593d3eb12000a45e574576fe76996a5e1eb75ad7ac78", size = 75841, upload-time = "2025-11-14T09:55:06.801Z" }, - { url = "https://files.pythonhosted.org/packages/1c/aa/caa900c1fdb9a3b7e48946c5206171a7adcf3b5189bcdb535cf899220909/pyobjc_framework_metal-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2f04a1a687cc346d23f3baf1ec56e3f42206709b590058d9778b52d45ca1c8ab", size = 75871, upload-time = "2025-11-14T09:55:13.008Z" }, - { url = "https://files.pythonhosted.org/packages/9c/a9/a42a173ea2d94071bc0f3112006a5d6ba7eaf0df9c48424f99b3e867e02d/pyobjc_framework_metal-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:3f3aa0848f4da46773952408b4814a440b210dc3f67f5ec5cfc0156ca2c8c0b6", size = 76420, upload-time = "2025-11-14T09:55:18.985Z" }, - { url = "https://files.pythonhosted.org/packages/88/8a/890dbc66bdae2ec839e28a15f16696ed1ab34b3cf32d58ed4dcd76183f25/pyobjc_framework_metal-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:2440db9b7057b6bafbabe8a2c5dde044865569176058ee34a7d138df0fc96c8c", size = 75876, upload-time = "2025-11-14T09:55:24.905Z" }, - { url = "https://files.pythonhosted.org/packages/4d/73/df12913fa33b52ff0e2c3cb7d578849a198b2a141d6e07e8930856a40851/pyobjc_framework_metal-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:476eeba3bebc2b3010e352b6bd28e3732432a3d5a8d5c3fb1cebd257dc7ea41e", size = 76483, upload-time = "2025-11-14T09:55:30.656Z" }, + { url = "https://files.pythonhosted.org/packages/1f/4b/c439eabbe786f42fb727b3536159eef24ee5135f1a3d2368a7165039d711/pyobjc_framework_metal-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f58d492c0667c95dcec503f75380a526d55e5797da5d05eb7776817bed19e2c", size = 76010, upload-time = "2026-06-19T16:13:49.333Z" }, + { url = "https://files.pythonhosted.org/packages/e9/bc/fa4ddb10fbce1cdceb5fab0e1f2d5ceae90385f3ded8aec940da49e226f1/pyobjc_framework_metal-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:add59e48ca823d60bcf7e026baa2a95203b88c796b5febc140924fd9e5eb5821", size = 76004, upload-time = "2026-06-19T16:13:50.495Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b4/a56f0b69cd0ba016da9f2ab64776950a7ca8d7dbf7a4e03b1bdb2fffa947/pyobjc_framework_metal-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:92b1f2e8f4a86bd9ecf307144e1f2c5ccc451fc760534833ddcde58d7624c695", size = 75923, upload-time = "2026-06-19T16:13:51.542Z" }, + { url = "https://files.pythonhosted.org/packages/7d/97/6a6d547f11ea81d4ee1570d4092947d264e60f02288c119f14332ea3298c/pyobjc_framework_metal-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f39a87b50408afbfc8a78be0c0d164016f114c2f807eb35506051660b386931f", size = 75952, upload-time = "2026-06-19T16:13:52.471Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d5/0001ccf5217cde46c61140f062be050d6760359d2c4bd652b619cbd4361f/pyobjc_framework_metal-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:eb247c22391706162bcc14e6fd52101e632a51f3c0aeccb59cea549c019303ce", size = 76508, upload-time = "2026-06-19T16:13:53.398Z" }, + { url = "https://files.pythonhosted.org/packages/ac/02/e2d652df1f5604549a346f5048243d627cadae3a18e7491999a045f08f28/pyobjc_framework_metal-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a842860779c2a1c82d80b3800009b4604448d9635f1a69300393184b378cb6af", size = 75961, upload-time = "2026-06-19T16:13:54.466Z" }, + { url = "https://files.pythonhosted.org/packages/74/e6/fdf12a3cc476a540e064f409ccef28db80fe6bd01b652c9a7c9529869517/pyobjc_framework_metal-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6bbfe26a9d4683d776aeed28ba725102328ef5072eedde4c4df15df67b541c80", size = 76569, upload-time = "2026-06-19T16:13:55.386Z" }, + { url = "https://files.pythonhosted.org/packages/d8/21/236462c59d7b781705bdaa79307be1d952fed48d6ad561278167b62d5712/pyobjc_framework_metal-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:e1be254400cc4038474466ff343093f8955925281f1e1209054d2fff9d6b362d", size = 76073, upload-time = "2026-06-19T16:13:56.311Z" }, + { url = "https://files.pythonhosted.org/packages/32/8e/8deb988841348b718b9168559b393a25e60a3c70d6f5ca0efbaa6285ad38/pyobjc_framework_metal-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:9f69b2c846014b19f1a7180b33fed0acf670d72b7e44290018b6bb7fb39d607e", size = 76710, upload-time = "2026-06-19T16:13:57.391Z" }, ] [[package]] name = "pyobjc-framework-metalfx" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-metal" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f1/09/ce5c74565677fde66de3b9d35389066b19e5d1bfef9d9a4ad80f0c858c0c/pyobjc_framework_metalfx-12.1.tar.gz", hash = "sha256:1551b686fb80083a97879ce0331bdb1d4c9b94557570b7ecc35ebf40ff65c90b", size = 29470, upload-time = "2025-11-14T10:17:37.16Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/64/e64620b99d5fae9aa933e4d8189889275a89e4918773af64ea30b202aa89/pyobjc_framework_metalfx-12.2.1.tar.gz", hash = "sha256:c146060268f8c2941dba7695bb1511145035a8468b65d88a668b2eeb4ac8ea07", size = 33394, upload-time = "2026-06-19T16:21:07.855Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/f6/c179930896e287ec9b4498154b00eeb3ecb9c9d4fe7e55a4e02f5532b7fa/pyobjc_framework_metalfx-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c28d875f8817cb80c44afaebdbc2960af66ea8eef63e0236fab3dde9be685d50", size = 15021, upload-time = "2025-11-14T09:55:33.081Z" }, - { url = "https://files.pythonhosted.org/packages/8b/e5/5494639c927085bbba1a310e73662e0bda44b90cdff67fa03a4e1c24d4c4/pyobjc_framework_metalfx-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ec3f7ab036eae45e067fbf209676f98075892aa307d73bb9394304960746cd2", size = 15026, upload-time = "2025-11-14T09:55:35.239Z" }, - { url = "https://files.pythonhosted.org/packages/2a/0b/508e3af499694f4eec74cc3ab0530e38db76e43a27db9ecb98c50c68f5f9/pyobjc_framework_metalfx-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a4418ae5c2eb77ec00695fa720a547638dc252dfd77ecb6feb88f713f5a948fd", size = 15062, upload-time = "2025-11-14T09:55:37.352Z" }, - { url = "https://files.pythonhosted.org/packages/02/b6/baa6071a36962e11c8834d8d13833509ce7ecb63e5c79fe2718d153a8312/pyobjc_framework_metalfx-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d443b0ee06de1b21a3ec5adab315840e71d52a74f8585090200228ab2fa1e59d", size = 15073, upload-time = "2025-11-14T09:55:39.436Z" }, - { url = "https://files.pythonhosted.org/packages/42/d1/b4ea7e6c0c66710db81f315c48dca0252ed81bbde4a41de21b8d54ff2241/pyobjc_framework_metalfx-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:dcd334b42c5c50ec88e049f1b0bf43544b52e3ac09fd57b712fec8f63507190e", size = 15286, upload-time = "2025-11-14T09:55:41.642Z" }, - { url = "https://files.pythonhosted.org/packages/ae/a6/fe7108290f798f79f2efbcf511fdb605b834f3616496fae8bec0c719ba65/pyobjc_framework_metalfx-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b5c4d81ebe71be69db838041ec93c12fb0458fe68a06f61f87a4d892135953dc", size = 16349, upload-time = "2025-11-14T09:55:44.009Z" }, - { url = "https://files.pythonhosted.org/packages/cd/4b/2c782b429baed0cc545154c9b4f866eb86aa2d74977452e2c9c2157daef8/pyobjc_framework_metalfx-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:795f081c558312f51079de2d739412d286229f421282cfab36e195fef557f2ca", size = 16588, upload-time = "2025-11-14T09:55:46.128Z" }, + { url = "https://files.pythonhosted.org/packages/48/2c/27e924fc07a6ced448fc928b1ddcbe0d56ac1f5e3fe732457b199ca085aa/pyobjc_framework_metalfx-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb1bbb52c1e1f771c7169bdec7639f75c034ee652362b9e8c969ba092d1c31a7", size = 15049, upload-time = "2026-06-19T16:13:58.335Z" }, + { url = "https://files.pythonhosted.org/packages/de/73/1a5a3b967513abf7b270b83c7e3e8cef511f45f7d3805341fcba653ddcd1/pyobjc_framework_metalfx-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c8b17729b04033eedc6fd915f0d1ae09d7065d1e1301857b6e2e56f97270c8f5", size = 15047, upload-time = "2026-06-19T16:13:59.481Z" }, + { url = "https://files.pythonhosted.org/packages/67/bd/8d0fe1c96e795e6ec5a78ffeb445e3651a3caed505d7c10e2eea67444306/pyobjc_framework_metalfx-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8c1b2b84cb3a2481d95c324cda82ecac6fbe7f6c6a2c05c960b956449cdad1ac", size = 15082, upload-time = "2026-06-19T16:14:00.439Z" }, + { url = "https://files.pythonhosted.org/packages/92/69/97594e1276302d41ffbbdf065e4e4663fa34487749544f2a19ce3aa5eb46/pyobjc_framework_metalfx-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a4e6a33836b384f2627bb8c68f60365d85d135b6ecc4e3c63392c9ac30a597fa", size = 15096, upload-time = "2026-06-19T16:14:01.69Z" }, + { url = "https://files.pythonhosted.org/packages/50/b4/796b5f98983ab5354a145069dbaa3622cf57ffa238ce071ee75f717977de/pyobjc_framework_metalfx-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c213297c6738e92e2805ff6029c9b70c882c67d5799e574900941af2d02c13d6", size = 15308, upload-time = "2026-06-19T16:14:03.063Z" }, + { url = "https://files.pythonhosted.org/packages/75/00/5e6263f04a058fac0a27bb3afdfae925b77c83aba8f116a06f4e6f2d0d98/pyobjc_framework_metalfx-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:74ead433904ab845442eeb992a4eb13274e8c411d4c2605b62f623c8b6f04f53", size = 16370, upload-time = "2026-06-19T16:14:03.867Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b4/5f0f0937fc1ccf42651356bdd8c437468b6b312b6a3cc775c1e3293dfaab/pyobjc_framework_metalfx-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:976c04c1f76ae75f7556c0a82082948f6879ff4f6d026a644b507ececc2c52ab", size = 16615, upload-time = "2026-06-19T16:14:04.814Z" }, + { url = "https://files.pythonhosted.org/packages/3e/af/a635b139be334cca2c488dae9c45cba5b242cbb33d4962432a99b3aa5d45/pyobjc_framework_metalfx-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:f2f7f4c01571031e289d3ad1d9c5b5eb5cebfd7909cb4cc17fc39b696b97fa39", size = 16372, upload-time = "2026-06-19T16:14:05.936Z" }, + { url = "https://files.pythonhosted.org/packages/46/2a/d0fa22d9a13784626e4cb51d0d331766beb2cf9df7fc003acd17ca3f34a4/pyobjc_framework_metalfx-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:b4a8a02d165df8be14ad51b8503415bc5d70ecffe3c8f86536166490781e5f93", size = 16605, upload-time = "2026-06-19T16:14:06.977Z" }, ] [[package]] name = "pyobjc-framework-metalkit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-metal" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/14/15/5091147aae12d4011a788b93971c3376aaaf9bf32aa935a2c9a06a71e18b/pyobjc_framework_metalkit-12.1.tar.gz", hash = "sha256:14cc5c256f0e3471b412a5b3582cb2a0d36d3d57401a8aa09e433252d1c34824", size = 25473, upload-time = "2025-11-14T10:17:39.721Z" } +sdist = { url = "https://files.pythonhosted.org/packages/35/9e/18cd38c650176a9e4895f5b461c843e90fd85004a379fac799b710e813e4/pyobjc_framework_metalkit-12.2.1.tar.gz", hash = "sha256:f2f8e02f4ddeb1d49a5b3def09eddcb8a718289b6ed635fa5f1807165969e798", size = 28180, upload-time = "2026-06-19T16:21:08.766Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/fa/aa992323c039a68afb211196200f6e2b7331c2a8e515986ba6bf4df48791/pyobjc_framework_metalkit-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e8d2cfdf2dc4e8fc229dafe7c76787caa5430a0588d205bc9b61affd2d35d26a", size = 8734, upload-time = "2025-11-14T09:55:47.963Z" }, - { url = "https://files.pythonhosted.org/packages/10/c5/f72cbc3a5e83211cbfa33b60611abcebbe893854d0f2b28ff6f444f97549/pyobjc_framework_metalkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:28636454f222d9b20cb61f6e8dc1ebd48237903feb4d0dbdf9d7904c542475e5", size = 8735, upload-time = "2025-11-14T09:55:50.053Z" }, - { url = "https://files.pythonhosted.org/packages/bf/c0/c8b5b060895cd51493afe3f09909b7e34893b1161cf4d93bc8e3cd306129/pyobjc_framework_metalkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1c4869076571d94788fe539fabfdd568a5c8e340936c7726d2551196640bd152", size = 8755, upload-time = "2025-11-14T09:55:51.683Z" }, - { url = "https://files.pythonhosted.org/packages/2b/cd/f04e991f4db4512e64ea7611796141c316506e733d75c468512df0e8fda4/pyobjc_framework_metalkit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4dec94431ee888682115fe88ae72fca8bffc5df0957e3c006777c1d8267f65c3", size = 8769, upload-time = "2025-11-14T09:55:53.318Z" }, - { url = "https://files.pythonhosted.org/packages/b7/b8/6f2fc56b6f8aee222d584edbdef4cf300e90782813e315418eba6d395533/pyobjc_framework_metalkit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:d16958c0d4e2a75e1ea973de8951c775da1e39e378a7a7762fbce1837bf3179c", size = 8922, upload-time = "2025-11-14T09:55:55.016Z" }, - { url = "https://files.pythonhosted.org/packages/d4/52/84c2829df343322025d3ad474153359c850c3189555c0819155044b8777d/pyobjc_framework_metalkit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a1b8ac9582b65d2711836b56dd24ce450aa740b0c478da9ee0621cc4c64e64cb", size = 8824, upload-time = "2025-11-14T09:55:56.672Z" }, - { url = "https://files.pythonhosted.org/packages/09/e9/ca6433dbdee520b8e3be3383b2b350692af4366f03842f6d79510a87c33c/pyobjc_framework_metalkit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:3d41ab59184d1a79981c5fb15d042750047a1a73574efa26179d7e174ddeaca6", size = 8972, upload-time = "2025-11-14T09:55:58.662Z" }, + { url = "https://files.pythonhosted.org/packages/e1/bf/fdcc5fb3f3fa7d635b5e5b0c3e6f00f9785a9a4a8b5cb745bc47f865d813/pyobjc_framework_metalkit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:386a0de1a71589cabf182f6e29cb9cba2ff6afeda95303079ea044e65b72464c", size = 8783, upload-time = "2026-06-19T16:14:07.984Z" }, + { url = "https://files.pythonhosted.org/packages/cd/be/a318901b833d9c84d2aa93ed46cd7efde582be33de1286f96ce6d57b99d2/pyobjc_framework_metalkit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9ee027e7dfa9d5146a4032323f025c66051d0487523457975e05d97e62fae263", size = 8779, upload-time = "2026-06-19T16:14:08.907Z" }, + { url = "https://files.pythonhosted.org/packages/90/e4/4b96ce6a2e396c4ff3d509d0b823a23765b03b5bf3608308b21023308822/pyobjc_framework_metalkit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d81f90237743cfb7a65e92d8581d0298feb4d59a20b3995321bfac9eca458f6f", size = 8800, upload-time = "2026-06-19T16:14:09.924Z" }, + { url = "https://files.pythonhosted.org/packages/64/0b/73b19059dd6b638a9850f2e2d350e3dfc7244fef9d89f84fc079ae06c558/pyobjc_framework_metalkit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd09232de0cdca092a79efa4e73ea12357b1db862527c9ce8172d0c2b688c5c7", size = 8812, upload-time = "2026-06-19T16:14:10.681Z" }, + { url = "https://files.pythonhosted.org/packages/29/20/1df8c5a560709a81848b6098c99a761813aac3f29defd4847766649dbb25/pyobjc_framework_metalkit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a90f5ade5946194025bbfd2981a4099062d671ab2667c95d6ce218c13cc3d032", size = 8969, upload-time = "2026-06-19T16:14:11.501Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e9/2ef3900e36b165267007204bccd093389ba373439699c6bff85e6f9000eb/pyobjc_framework_metalkit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:4666993edc519a447e7c2dba75a90afee9597d6e583e088c80beae725669e5c0", size = 8870, upload-time = "2026-06-19T16:14:12.295Z" }, + { url = "https://files.pythonhosted.org/packages/9a/6d/4fe11d7c809a5a019d048f0f5ba36222fe6d137c44859c7558d3b8f04a08/pyobjc_framework_metalkit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1e549bfce38973abbb8df8332696d0bbea1fe990068ffa96e300c45fe6375ee4", size = 9014, upload-time = "2026-06-19T16:14:13.095Z" }, + { url = "https://files.pythonhosted.org/packages/94/dc/af9224e90acc2a463dae9e3242fe75d5097e40771803914844cb97c77be0/pyobjc_framework_metalkit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:21010a4f50e58f2a494df6325027539efdde53ff211ef5dc9f1873282652c117", size = 8869, upload-time = "2026-06-19T16:14:13.863Z" }, + { url = "https://files.pythonhosted.org/packages/05/53/e3c2fdce9766d684152093cc86449268030cfbd8e23224dec1dfaece0d44/pyobjc_framework_metalkit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:837ab5e81b79db0e7c01763a9ed1a21fa57a27ee4ecb7376b944a92036733e09", size = 9006, upload-time = "2026-06-19T16:14:14.744Z" }, ] [[package]] name = "pyobjc-framework-metalperformanceshaders" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-metal" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c5/68/58da38e54aa0d8c19f0d3084d8c84e92d54cc8c9254041f07119d86aa073/pyobjc_framework_metalperformanceshaders-12.1.tar.gz", hash = "sha256:b198e755b95a1de1525e63c3b14327ae93ef1d88359e6be1ce554a3493755b50", size = 137301, upload-time = "2025-11-14T10:17:49.554Z" } +sdist = { url = "https://files.pythonhosted.org/packages/50/ff/6938291dd5a71e39f6948037dbb271993d86a3ecd7706e7cc38034feeaea/pyobjc_framework_metalperformanceshaders-12.2.1.tar.gz", hash = "sha256:a4395f8619ad6f1d382aab5cf116e058b18d3646bec6b730c77daa8f692b5de4", size = 190474, upload-time = "2026-06-19T16:21:09.743Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/8f/6bf7d8e5ce093463e70d73dfad1cc2d100a0dd8bd8cfd6a1c1d3365902d0/pyobjc_framework_metalperformanceshaders-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b7a3a0dbe2a26c2c579e28124ec6ddd0d664a9557cbf64029d48b766d5af0209", size = 32994, upload-time = "2025-11-14T09:56:02.146Z" }, - { url = "https://files.pythonhosted.org/packages/00/0f/6dc06a08599a3bc211852a5e6dcb4ed65dfbf1066958feb367ba7702798a/pyobjc_framework_metalperformanceshaders-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0159a6f731dc0fd126481a26490683586864e9d47c678900049a8ffe0135f56", size = 32988, upload-time = "2025-11-14T09:56:05.323Z" }, - { url = "https://files.pythonhosted.org/packages/62/84/d505496fca9341e0cb11258ace7640cd986fe3e831f8b4749035e9f82109/pyobjc_framework_metalperformanceshaders-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c00e786c352b3ff5d86cf0cf3a830dc9f6fc32a03ae1a7539d20d11324adb2e8", size = 33242, upload-time = "2025-11-14T09:56:09.354Z" }, - { url = "https://files.pythonhosted.org/packages/e9/6c/8f3d81905ce6b0613fe364a6dd77bf4ed85a6350f867b40a5e99b69e8d07/pyobjc_framework_metalperformanceshaders-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:240321f2fad1555b5ede3aed938c9f37da40a57fc3e7e9c96a45658dc12c3771", size = 33269, upload-time = "2025-11-14T09:56:12.527Z" }, - { url = "https://files.pythonhosted.org/packages/58/44/4813f8606a91a88f67a0b0c02ed9e2449cbfd5b701f7ca61cf9ce3fe0769/pyobjc_framework_metalperformanceshaders-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0aa287ee357fe5bd5660b3d0688f947a768cda8565dbbca3b876307b9876639e", size = 33457, upload-time = "2025-11-14T09:56:15.72Z" }, - { url = "https://files.pythonhosted.org/packages/b4/d7/1177d8815549c90d8ddb0764b62c17bdaca6d6e03b8b54f3e7137167d8f3/pyobjc_framework_metalperformanceshaders-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:5d5a0a5c859c5493d597842f3d011c59bf7c10d04a29852016298364fca9e16e", size = 33324, upload-time = "2025-11-14T09:56:18.802Z" }, - { url = "https://files.pythonhosted.org/packages/4b/35/35302a62ae81e3b31c84bc1a2fc6fd0ad80a43b7edee9ef9bca482d55edd/pyobjc_framework_metalperformanceshaders-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:c23b3a0f869c730e50851468a082014f1b0b3d4433d5d15ac28d6a736084026c", size = 33534, upload-time = "2025-11-14T09:56:21.984Z" }, + { url = "https://files.pythonhosted.org/packages/49/01/1bf01010aafecd9c41576edc8aa166351a2f57e541942d34bd6b5212262d/pyobjc_framework_metalperformanceshaders-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6167eb5cbc9bde6690509b578b2f2754fac588d214bc5a01b8406f63de7a4766", size = 33924, upload-time = "2026-06-19T16:14:15.709Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b0/ed310b3a4cf06a16ca1e71c943c01955d734db172df043debf850ee719c6/pyobjc_framework_metalperformanceshaders-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:521d6a3ab80d76138754e7a1eaf02412d0d7502f5d486b4c3b7548d193b90457", size = 33927, upload-time = "2026-06-19T16:14:16.686Z" }, + { url = "https://files.pythonhosted.org/packages/5c/9a/1d95d7a2c2855f9afb7a27378940f52ff87894d98e8bac6e98cb3a0099f2/pyobjc_framework_metalperformanceshaders-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6c679e85a4197302bdd7f9f9b17448c4df5c9a66e3ddb2d371814eb84a45261f", size = 34184, upload-time = "2026-06-19T16:14:17.743Z" }, + { url = "https://files.pythonhosted.org/packages/9f/6e/40590459842bd635d0f5e77a520aca827af1331ef70e02955965f5122749/pyobjc_framework_metalperformanceshaders-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:856aa1b620ec04e64870694627435547affe350bda9e00e877e72ab518aec76e", size = 34198, upload-time = "2026-06-19T16:14:18.583Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0f/f311b511eea76eaa7195164ea82133591c614bdbcf6efc069ef863dc0fa1/pyobjc_framework_metalperformanceshaders-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c1b094b81d0ed3f72b9345e7e4fbc47604934325d3695b4b8f2457701cf90fad", size = 34398, upload-time = "2026-06-19T16:14:19.437Z" }, + { url = "https://files.pythonhosted.org/packages/1e/d2/999932228dcae4c691b295a5fb4bcd71abd16ac8dfbe67ceab629eb69582/pyobjc_framework_metalperformanceshaders-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b96cac9d50a9bf72e2bc3e006eac5ec0e7aeaabb65eac620df830c35af9b1fef", size = 34264, upload-time = "2026-06-19T16:14:20.364Z" }, + { url = "https://files.pythonhosted.org/packages/9c/46/96536afd54579814f2ceaf5a91ff16ae38e20cde7d630e45623171e7164f/pyobjc_framework_metalperformanceshaders-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1449836ff40cbb04a00c72b1c6219571914d895695d6d310bc1433b0181110d6", size = 34469, upload-time = "2026-06-19T16:14:21.265Z" }, + { url = "https://files.pythonhosted.org/packages/58/2b/4913eaf6eb59f20566f73b9f1b2fe0559aa610c6f8027e6ad20e9fa306f2/pyobjc_framework_metalperformanceshaders-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:db8048e5d7cb8a94b352f3902e330cf0f4de3c103ea07c261bcb495ee2b4ae7d", size = 34285, upload-time = "2026-06-19T16:14:22.184Z" }, + { url = "https://files.pythonhosted.org/packages/26/fe/db17f4997f342d645b9ae30ec4f542f02612bd56496184a9294fa697c70a/pyobjc_framework_metalperformanceshaders-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:76e1b5f6733f14eed200631f6ba0af187fa89623cb1dcb126bf55dc4eddeba86", size = 34500, upload-time = "2026-06-19T16:14:23.12Z" }, ] [[package]] name = "pyobjc-framework-metalperformanceshadersgraph" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-metalperformanceshaders" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a7/56/7ad0cd085532f7bdea9a8d4e9a2dfde376d26dd21e5eabdf1a366040eff8/pyobjc_framework_metalperformanceshadersgraph-12.1.tar.gz", hash = "sha256:b8fd017b47698037d7b172d41bed7a4835f4c4f2a288235819d200005f89ee35", size = 42992, upload-time = "2025-11-14T10:17:53.502Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/11/15e3acf0636f9418384cd3e213296ad9882f546889865913cfaee2339ed6/pyobjc_framework_metalperformanceshadersgraph-12.2.1.tar.gz", hash = "sha256:656e70c86645814ef1d02bac74933eebc5ff6427100bee4a3bbffde921020ab6", size = 60199, upload-time = "2026-06-19T16:21:10.716Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/c9/5e7fd0d4bc9bdf7b442f36e020677c721ba9b4c1dc1fa3180085f22a4ef9/pyobjc_framework_metalperformanceshadersgraph-12.1-py2.py3-none-any.whl", hash = "sha256:85a1c7a6114ada05c7924b3235a1a98c45359410d148097488f15aee5ebb6ab9", size = 6481, upload-time = "2025-11-14T09:56:23.66Z" }, + { url = "https://files.pythonhosted.org/packages/43/d5/6aa84d1d976ba8b97cdc6486f09aab85f20ea6ba5079086f1de4426cbb2e/pyobjc_framework_metalperformanceshadersgraph-12.2.1-py2.py3-none-any.whl", hash = "sha256:9ac2270c573e9399c02196ea1725bd3c7ed3699d71ee70286b1f78beb8afaf5e", size = 7134, upload-time = "2026-06-19T16:14:24.104Z" }, ] [[package]] name = "pyobjc-framework-metrickit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ba/13/5576ddfbc0b174810a49171e2dbe610bdafd3b701011c6ecd9b3a461de8a/pyobjc_framework_metrickit-12.1.tar.gz", hash = "sha256:77841daf6b36ba0c19df88545fd910c0516acf279e6b7b4fa0a712a046eaa9f1", size = 27627, upload-time = "2025-11-14T10:17:56.353Z" } +sdist = { url = "https://files.pythonhosted.org/packages/63/5d/1e0662fc1af513a08474ab7b9193012899e6930a490aefd9c2c531862a91/pyobjc_framework_metrickit-12.2.1.tar.gz", hash = "sha256:096878f3e750d12a7018b07ff3468d405ab3ac108f1aa92bc3123fd06934e344", size = 30581, upload-time = "2026-06-19T16:21:11.423Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/25/7396861f228697190b3bde09341761c75e4fcc96eba0456cf459286e7652/pyobjc_framework_metrickit-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c074060cef54004451a1f919dfc6ef46b5479ead464ae791480f8d1d044a8dc", size = 8096, upload-time = "2025-11-14T09:56:25.266Z" }, - { url = "https://files.pythonhosted.org/packages/5e/b0/e57c60af3e9214e05309dca201abb82e10e8cf91952d90d572b641d62027/pyobjc_framework_metrickit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da6650afd9523cf7a9cae177f4bbd1ad45cc122d97784785fa1482847485142c", size = 8102, upload-time = "2025-11-14T09:56:27.194Z" }, - { url = "https://files.pythonhosted.org/packages/b7/04/8da5126e47306438c99750f1dfed430d7cc388f6b7f420ae748f3060ab96/pyobjc_framework_metrickit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3ec96e9ec7dc37fbce57dae277f0d89c66ffe1c3fa2feaca1b7125f8b2b29d87", size = 8120, upload-time = "2025-11-14T09:56:28.73Z" }, - { url = "https://files.pythonhosted.org/packages/f1/e0/8b379325acb39e0966f818106b3c3c8e3966bf87a7ab5c2d0e89753b0d1f/pyobjc_framework_metrickit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:884afb6ec863883318975fda38db9d741b8da5f64a2b8c34bf8edc5ff56019d4", size = 8131, upload-time = "2025-11-14T09:56:30.524Z" }, - { url = "https://files.pythonhosted.org/packages/86/67/dcd2b18a787d3fec89e372aadb83c01879dda24fe1ed2a333a5e1d388591/pyobjc_framework_metrickit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:37674b0e049035d8b32d0221d0afbfedd3f643e4a2ee74b9a0e4e6d1b94fcd69", size = 8273, upload-time = "2025-11-14T09:56:32.128Z" }, - { url = "https://files.pythonhosted.org/packages/d6/8b/a97a1463fc4453e5b1c157816a8356d800c4d66d5624154dc6dbdd7f52c0/pyobjc_framework_metrickit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:f6cde78ba1a401660fe0e3a945d1941efef255c1021a8772a838aceb31bd74e6", size = 8190, upload-time = "2025-11-14T09:56:33.911Z" }, - { url = "https://files.pythonhosted.org/packages/ec/8b/a61b0fb889a2833b23fe2d4439d910a3d24a7eab83abc15c82f1fa1541a7/pyobjc_framework_metrickit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:8f407172e1ecc8ee63afadda477a0f1c633c09be761edcadab8a9d1eebddd27c", size = 8333, upload-time = "2025-11-14T09:56:35.511Z" }, + { url = "https://files.pythonhosted.org/packages/5f/b1/66aaf6a8d224e981c6233d8b19c97f95ba0e014759a7df9e2a04ff422b79/pyobjc_framework_metrickit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0cb7e940c41823e3df1211dc7f81531e98fc20d15639436e2d09c0c75c4451c", size = 8121, upload-time = "2026-06-19T16:14:25.072Z" }, + { url = "https://files.pythonhosted.org/packages/ff/17/2a09f83953f702afa498032f22fa710bc17ae8055a6a82403e83c53f53b4/pyobjc_framework_metrickit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1167de9dfc42fddcdb0529b419d499b7c3e68c3f8f2828f93218360c9aaa0cd1", size = 8123, upload-time = "2026-06-19T16:14:26.1Z" }, + { url = "https://files.pythonhosted.org/packages/97/cd/e70cbd2ade0daf4e8130af6bb4a45793a077d7b064bb3fa04d4f65349936/pyobjc_framework_metrickit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ee7253c6451b99f43588be83460bdce90c1c2f94e10fe6cef294d0d44af771a7", size = 8141, upload-time = "2026-06-19T16:14:26.994Z" }, + { url = "https://files.pythonhosted.org/packages/6c/9c/651e524176ebce5eff66bc237212949ea247478849ef53308cae5ed8eb75/pyobjc_framework_metrickit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:62f6c2cc22017377e984fbea0ea6ee15b3c0fecda7fcc42e88d0beb5387ad3a4", size = 8150, upload-time = "2026-06-19T16:14:27.732Z" }, + { url = "https://files.pythonhosted.org/packages/9b/14/96162ec3e8d8a8131e86ea8459cd8a2c7ec6a28a6ac703d3cf7d6d11b710/pyobjc_framework_metrickit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:1357f74151b851760b2520f20ffab69a6a85ed3c1aebd61164bb67e0be417375", size = 8292, upload-time = "2026-06-19T16:14:28.558Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4a/6c6fef775e75785277f2511bb76f95b4b97edba7389bcbe1fbff68b526ef/pyobjc_framework_metrickit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:dbabe6b44292b0dad3998921c46a4292e328b4ac0ee26e25f34a2eb9fead452c", size = 8208, upload-time = "2026-06-19T16:14:29.334Z" }, + { url = "https://files.pythonhosted.org/packages/d3/bd/68ced20b5005f8856fabd93c65bc878e041a99c155377461519b4027742f/pyobjc_framework_metrickit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6ad3a56006b975e0165eb34853e30d6d7b68814b39e7a5bfd02a20cd47234535", size = 8353, upload-time = "2026-06-19T16:14:30.134Z" }, + { url = "https://files.pythonhosted.org/packages/72/3e/a399de9fbbd3f58f6b2b63648c455225f6777182456ebf489fc60b36970e/pyobjc_framework_metrickit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:cd58c762d163a557d18b23dd6e856086902258afb64064b3a72fa8237144b3f0", size = 8203, upload-time = "2026-06-19T16:14:30.893Z" }, + { url = "https://files.pythonhosted.org/packages/01/4f/81f2d04fdf116182364e69c2b11f65e4dbef0e95f26150225efce9622297/pyobjc_framework_metrickit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:72b0e04337f4de0a5bf29aae719713265f255d3440dbe7338b6aae92cfa414fa", size = 8343, upload-time = "2026-06-19T16:14:31.707Z" }, ] [[package]] name = "pyobjc-framework-mlcompute" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/69/15f8ce96c14383aa783c8e4bc1e6d936a489343bb197b8e71abb3ddc1cb8/pyobjc_framework_mlcompute-12.1.tar.gz", hash = "sha256:3281db120273dcc56e97becffd5cedf9c62042788289f7be6ea067a863164f1e", size = 40698, upload-time = "2025-11-14T10:17:59.792Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/2d/16412fc8454bf987c64d7eb18ee0548198b35aad03b4d3cad6047fd18545/pyobjc_framework_mlcompute-12.2.1.tar.gz", hash = "sha256:4ee00b70d549619d63961864d9c2dd93b6db18d1c920242ae961517be7074f84", size = 55020, upload-time = "2026-06-19T16:21:12.198Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/f7/4614b9ccd0151795e328b9ed881fbcbb13e577a8ec4ae3507edb1a462731/pyobjc_framework_mlcompute-12.1-py2.py3-none-any.whl", hash = "sha256:4f0fc19551d710a03dfc4c7129299897544ff8ea76db6c7539ecc2f9b2571bde", size = 6744, upload-time = "2025-11-14T09:56:36.973Z" }, + { url = "https://files.pythonhosted.org/packages/75/9a/f7c94f4ca691822916509a260d9a18c58224b14845ae7dc7c2b56eb0b376/pyobjc_framework_mlcompute-12.2.1-py2.py3-none-any.whl", hash = "sha256:71517c5afdba1213aa4a832fcf3bc1cbe0efac7f4a42ea10cca33bbd4ad1db45", size = 9644, upload-time = "2026-06-19T16:14:32.481Z" }, ] [[package]] name = "pyobjc-framework-modelio" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-quartz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b4/11/32c358111b623b4a0af9e90470b198fffc068b45acac74e1ba711aee7199/pyobjc_framework_modelio-12.1.tar.gz", hash = "sha256:d041d7bca7c2a4526344d3e593347225b7a2e51a499b3aa548895ba516d1bdbb", size = 66482, upload-time = "2025-11-14T10:18:04.92Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/a5/fbd7e593d0bf0f611b91758a855d1cad14b08f81609a588ce354b5677795/pyobjc_framework_modelio-12.2.1.tar.gz", hash = "sha256:d3706f803dc325c38536fb43fbd4174e958a95f92312a684ae152186661dff2b", size = 83795, upload-time = "2026-06-19T16:21:13.136Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/84/76322f49840776f7255d7bdf9a548925fd8a6ba0efc50e4aef3e6d6f4667/pyobjc_framework_modelio-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e25d153ab231ca95954e3d12082f48aea4ec72db48269421011679f926638d07", size = 20183, upload-time = "2025-11-14T09:56:39.377Z" }, - { url = "https://files.pythonhosted.org/packages/35/c0/c67b806f3f2bb6264a4f7778a2aa82c7b0f50dfac40f6a60366ffc5afaf5/pyobjc_framework_modelio-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1c2c99d47a7e4956a75ce19bddbe2d8ada7d7ce9e2f56ff53fc2898367187749", size = 20180, upload-time = "2025-11-14T09:56:41.924Z" }, - { url = "https://files.pythonhosted.org/packages/f6/0e/b8331100f0d658ecb3e87e75c108e2ae8ac7c78b521fd5ad0205b60a2584/pyobjc_framework_modelio-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:68d971917c289fdddf69094c74915d2ccb746b42b150e0bdc16d8161e6164022", size = 20193, upload-time = "2025-11-14T09:56:44.296Z" }, - { url = "https://files.pythonhosted.org/packages/db/fa/f111717fd64015fc3906b7c36dcfca4dda1d31916251c9640a8c70ff611a/pyobjc_framework_modelio-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dad6e914b6efe8ea3d2cd10029c4eb838f1ad6a12344787e8db70c4149df8cfc", size = 20208, upload-time = "2025-11-14T09:56:46.627Z" }, - { url = "https://files.pythonhosted.org/packages/58/d3/6f3131a16694684f3dfa6b2845054941dfb69a63f18980eea02a25c06f6d/pyobjc_framework_modelio-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f00b739f9333d611e7124acf95491bdf025dd32ba7c48b7521f6845b92e2dcce", size = 20448, upload-time = "2025-11-14T09:56:49.184Z" }, - { url = "https://files.pythonhosted.org/packages/14/14/52b19e6ba86de2d38aed69a091c5d0c436c007ddf73441cbcc0a217db1d4/pyobjc_framework_modelio-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:5250e7f58cc71ca8928b33a00ac0dc56ca0eead97507f4bfcf777582a4b05e39", size = 20183, upload-time = "2025-11-14T09:56:51.861Z" }, - { url = "https://files.pythonhosted.org/packages/e9/2c/13a22d22ffb1c175db9c23bea5f26dc3002c72056b68a362c04697778914/pyobjc_framework_modelio-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:aa76942301b2115c8904bcb10c73b19d10d7731ea35e6155cbfd6934d7c91e4b", size = 20426, upload-time = "2025-11-14T09:56:54.191Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c8/b25cd2709c5142d43c144db66853a92cf851bb1bbe1eec5afe0f887a8e75/pyobjc_framework_modelio-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:59253c507f9512a7b2424135bb3bbf610ed5455b79d58cd89ef614d05d3ad27c", size = 20495, upload-time = "2026-06-19T16:14:33.461Z" }, + { url = "https://files.pythonhosted.org/packages/51/8d/64adc4f64bbb3517fe6951d9928aaf9fd4875e1f71b170de5b3348a4cece/pyobjc_framework_modelio-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5c035eb4598b61508d7180e8326f426918028ef7c8ba2ad933d29f343e9860a5", size = 20499, upload-time = "2026-06-19T16:14:34.513Z" }, + { url = "https://files.pythonhosted.org/packages/2c/9b/acaef170056476aca099d56ec837f6f7bdc73c52507f14e577c8f14cb922/pyobjc_framework_modelio-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:82343edada3bb065317774a0f58c65f516fec6247113091da7029d06ad7e8431", size = 20511, upload-time = "2026-06-19T16:14:35.344Z" }, + { url = "https://files.pythonhosted.org/packages/c8/a7/6c23682a7b986165d6949e70d0cf9c30d1ff22ae369711b23699f22fef37/pyobjc_framework_modelio-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:68b53d1aea59dcbe5682f753ded2d3dc4d54598f58a0defe9a1d3219c7a801b0", size = 20522, upload-time = "2026-06-19T16:14:36.155Z" }, + { url = "https://files.pythonhosted.org/packages/e2/1f/d3c3c89eb0d10b00ed139a4308a3d5e353663c16310628d69d1fae2b774f/pyobjc_framework_modelio-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:1714a43c35bd4254f5fb51bc5d6b67dfcb2a6b20ff309d34ddd22f03bb1709b8", size = 20759, upload-time = "2026-06-19T16:14:38.045Z" }, + { url = "https://files.pythonhosted.org/packages/d3/d8/a50f8972c14c06a39bd25f28d87490ac6c8bcc1ca81f2237859d784200be/pyobjc_framework_modelio-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0dbf08b900703cc0a6d4e53b10da5d334dd5fed26be010aeda5eb53f93287ccd", size = 20494, upload-time = "2026-06-19T16:14:38.873Z" }, + { url = "https://files.pythonhosted.org/packages/af/40/89e31e57a16cf9c11757f310e7cc2ae031008fe9aee5baca89f17c521335/pyobjc_framework_modelio-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:32b361d7b80c9b63271c205860bb1961b530f1e79b43eec21e970bdc48497633", size = 20743, upload-time = "2026-06-19T16:14:39.754Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/52310f780e2564103cea0d9681291529b588cf7b0065bb43bfda9dd239c8/pyobjc_framework_modelio-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:c3d780628dc0ec585ecf845a8271b0843c619d50b6dae8335cdeb4c0a4054daa", size = 20483, upload-time = "2026-06-19T16:14:40.585Z" }, + { url = "https://files.pythonhosted.org/packages/78/d8/bf3bf64b81f53f604d94bf190c2b3c2868636d6dcf28ca49bb08cd34dd38/pyobjc_framework_modelio-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:476aadad72bebb9df752651ee2097e620a52e2293dbcd83dd9c0e424d7f401f7", size = 20749, upload-time = "2026-06-19T16:14:41.441Z" }, ] [[package]] name = "pyobjc-framework-multipeerconnectivity" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/87/35/0d0bb6881004cb238cfd7bf74f4b2e42601a1accdf27b2189ec61cf3a2dc/pyobjc_framework_multipeerconnectivity-12.1.tar.gz", hash = "sha256:7123f734b7174cacbe92a51a62b4645cc9033f6b462ff945b504b62e1b9e6c1c", size = 22816, upload-time = "2025-11-14T10:18:07.363Z" } +sdist = { url = "https://files.pythonhosted.org/packages/40/5a/cf3fad5f570ad3aa43010ef03e948d4ac1f0a531d3b7d822f1c19004f59a/pyobjc_framework_multipeerconnectivity-12.2.1.tar.gz", hash = "sha256:06f9a354ef0ef77c45c98ba9ef92bc48961522d7b3ba322e56b4ee3d4a46413c", size = 26450, upload-time = "2026-06-19T16:21:14.028Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/da/3f6ab6d80c1cf1deb23df34ccb16b3e94ff634454dd7b9cceecffa1cd57c/pyobjc_framework_multipeerconnectivity-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:39eff9abbd40cb7306cfbc6119a95ce583074102081571c6c90569968e655787", size = 11978, upload-time = "2025-11-14T09:56:56.049Z" }, - { url = "https://files.pythonhosted.org/packages/12/eb/e3e4ba158167696498f6491f91a8ac7e24f1ebbab5042cd34318e5d2035c/pyobjc_framework_multipeerconnectivity-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7372e505ed050286aeb83d7e158fda65ad379eae12e1526f32da0a260a8b7d06", size = 11981, upload-time = "2025-11-14T09:56:58.858Z" }, - { url = "https://files.pythonhosted.org/packages/33/8d/0646ff7db36942829f0e84be18ba44bc5cd96d6a81651f8e7dc0974821c1/pyobjc_framework_multipeerconnectivity-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1c3bd254a16debed321debf4858f9c9b7d41572ddf1058a4bacf6a5bcfedeeff", size = 12001, upload-time = "2025-11-14T09:57:01.027Z" }, - { url = "https://files.pythonhosted.org/packages/93/65/589cf3abaec888878d9b86162e5e622d4d467fd88a5f55320f555484dd54/pyobjc_framework_multipeerconnectivity-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:25169a2fded90d13431db03787ac238b4ed551c44f7656996f8dfb6b6986b997", size = 12019, upload-time = "2025-11-14T09:57:02.86Z" }, - { url = "https://files.pythonhosted.org/packages/0e/77/c184a36ba61d803d482029021410568b0a2155b5bf0dd2def4256ab58a1e/pyobjc_framework_multipeerconnectivity-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:3a6c2d233ecda3127bd6b6ded289ef0d1fa6ddc3acbab7f8af996c96090f7bfc", size = 12194, upload-time = "2025-11-14T09:57:04.63Z" }, - { url = "https://files.pythonhosted.org/packages/d6/64/fd5932ab32bec0e340b60ca87f57c07a9d963b56ab5f857787efcec236e4/pyobjc_framework_multipeerconnectivity-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:014f92d7e176154531c3173cf7113b6be374c041646c4b86d93afb84d2ea334c", size = 11989, upload-time = "2025-11-14T09:57:06.451Z" }, - { url = "https://files.pythonhosted.org/packages/99/1d/a7d2d26a081d5b9328a99865424078d9f9981e35c8e38a71321252e529f5/pyobjc_framework_multipeerconnectivity-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6490651224d1403d96e52ca3aed041b79b5456e3261abd9cb225c1fbc1893a69", size = 12210, upload-time = "2025-11-14T09:57:08.244Z" }, + { url = "https://files.pythonhosted.org/packages/ab/8f/dbf5de928ad0bdff5817c6baef08e29fc1a11ba669b9efdeee2f9a6e7250/pyobjc_framework_multipeerconnectivity-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:826e3bc11cb6f8993bfa5992860f8ebda303887925eae4412b1ed9f7746960b1", size = 12008, upload-time = "2026-06-19T16:14:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/89/a3/5edf042e97873d983da1509957667557aacf1750afdbd60d6e4dda055b51/pyobjc_framework_multipeerconnectivity-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:449c3e35eba88fc58e38fdf44e1afc41449eb7eea8c2c1120d19491579be1a70", size = 12012, upload-time = "2026-06-19T16:14:43.31Z" }, + { url = "https://files.pythonhosted.org/packages/3e/2c/3b85949975b600497c7f1f928e2f35344b2b2654550abac1f07d83c0ee89/pyobjc_framework_multipeerconnectivity-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3180c51bb68ed87f53e7b805f3443a510d7db00a1c0344223eabb384123fa0cb", size = 12028, upload-time = "2026-06-19T16:14:44.062Z" }, + { url = "https://files.pythonhosted.org/packages/ce/6b/546137c9aa171232ef6912f90bcd2cb2b4705d8ccc81cd498885d284d862/pyobjc_framework_multipeerconnectivity-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e566a2dd4ac9b95cc8b4739092501413a1d697a1c34126dce489cf55c0c71a53", size = 12047, upload-time = "2026-06-19T16:14:44.828Z" }, + { url = "https://files.pythonhosted.org/packages/51/cc/519061d373de8c779487b978eaa48cf60d9ec588f6dd2924dc9faefe8ebc/pyobjc_framework_multipeerconnectivity-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:87049bc0af02bb623bdd90573a6a2abaca7c967520db81477b05d0c38860aa37", size = 12229, upload-time = "2026-06-19T16:14:45.57Z" }, + { url = "https://files.pythonhosted.org/packages/3f/b0/1899d7b277df12d9c1ff6e21465881ec85e7ccafd76da2fbe4440a7d888b/pyobjc_framework_multipeerconnectivity-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b1f0f3bc2dbd15e2231d9b5427c4224428660c1a96a66f2521a859b0c5888aea", size = 12024, upload-time = "2026-06-19T16:14:46.37Z" }, + { url = "https://files.pythonhosted.org/packages/49/55/df2767d489c6083b11e1e3e4f4d60a3993649d378e876a551c7e911f1918/pyobjc_framework_multipeerconnectivity-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:f22051fcccbbd16b9a1d4e2f161dc06304f13444ee65035e62ca6ed58a5150b1", size = 12237, upload-time = "2026-06-19T16:14:47.129Z" }, + { url = "https://files.pythonhosted.org/packages/8a/e1/7a6f71aae1c8dfbb87baf9d1072df69570604ae735d7b3140ce00e4a68d6/pyobjc_framework_multipeerconnectivity-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:92f2a113f8ef13b6faa6aa9c95e01b29f10bd30c4652dc074fff747f8b29bdf9", size = 12016, upload-time = "2026-06-19T16:14:48.015Z" }, + { url = "https://files.pythonhosted.org/packages/63/2b/e9b6864ead5ef7435e30628185ab6908ebb85cd63c3c2b3f7fd788035912/pyobjc_framework_multipeerconnectivity-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:3c49e75d0dc605394422ce9e869784c3f911551733044db09c77659de1280880", size = 12231, upload-time = "2026-06-19T16:14:48.946Z" }, ] [[package]] name = "pyobjc-framework-naturallanguage" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8a/d1/c81c0cdbb198d498edc9bc5fbb17e79b796450c17bb7541adbf502f9ad65/pyobjc_framework_naturallanguage-12.1.tar.gz", hash = "sha256:cb27a1e1e5b2913d308c49fcd2fd04ab5ea87cb60cac4a576a91ebf6a50e52f6", size = 23524, upload-time = "2025-11-14T10:18:09.883Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/12/de013ac3cdbdb3cc616dd373399df4eb34b4459f668456d81f7062bd49c4/pyobjc_framework_naturallanguage-12.2.1.tar.gz", hash = "sha256:fa2d9c7040dcbbe4c7bc83ddfb9e3da20edb49824de8c78d3574aec7065c4043", size = 27243, upload-time = "2026-06-19T16:21:15.105Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/d8/715a11111f76c80769cb267a19ecf2a4ac76152a6410debb5a4790422256/pyobjc_framework_naturallanguage-12.1-py2.py3-none-any.whl", hash = "sha256:a02ef383ec88948ca28f03ab8995523726b3bc75c49f593b5c89c218bcbce7ce", size = 5320, upload-time = "2025-11-14T09:57:10.294Z" }, + { url = "https://files.pythonhosted.org/packages/a6/5c/3c0692cc72de5b9cc6e55d262df6630f43040374eb7454382473067d3379/pyobjc_framework_naturallanguage-12.2.1-py2.py3-none-any.whl", hash = "sha256:5eed3750f7cbd6a9584f2b9942c4b26b7134ed15dc044815ac79841403048be9", size = 5460, upload-time = "2026-06-19T16:14:49.857Z" }, ] [[package]] name = "pyobjc-framework-netfs" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/68/4bf0e5b8cc0780cf7acf0aec54def58c8bcf8d733db0bd38f5a264d1af06/pyobjc_framework_netfs-12.1.tar.gz", hash = "sha256:e8d0c25f41d7d9ced1aa2483238d0a80536df21f4b588640a72e1bdb87e75c1e", size = 14799, upload-time = "2025-11-14T10:18:11.85Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/ad/28624d75b8339f70fc51bbac131d160a92b87c41ba5684a904304f8a6a09/pyobjc_framework_netfs-12.2.1.tar.gz", hash = "sha256:312b3a6ebcba6b3a03bdc7412560d8ddb5ac336c37a1205e32c6b58d831191f2", size = 15153, upload-time = "2026-06-19T16:21:15.911Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/6b/8c2f223879edd3e3f030d0a9c9ba812775519c6d0c257e3e7255785ca6e7/pyobjc_framework_netfs-12.1-py2.py3-none-any.whl", hash = "sha256:0021f8b141e693d3821524c170e9c645090eb320e80c2935ddb978a6e8b8da81", size = 4163, upload-time = "2025-11-14T09:57:11.845Z" }, + { url = "https://files.pythonhosted.org/packages/a2/e7/6396d25cbfa2237784ada5bcb870531be597b9dbf1a5c5d41d007921d2cc/pyobjc_framework_netfs-12.2.1-py2.py3-none-any.whl", hash = "sha256:04f8f1743f98af10d005f42ec30b26f147d3ee0365b1ff3df2babaf456c75e07", size = 4182, upload-time = "2026-06-19T16:14:50.78Z" }, ] [[package]] name = "pyobjc-framework-network" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/13/a71270a1b0a9ec979e68b8ec84b0f960e908b17b51cb3cac246a74d52b6b/pyobjc_framework_network-12.1.tar.gz", hash = "sha256:dbf736ff84d1caa41224e86ff84d34b4e9eb6918ae4e373a44d3cb597648a16a", size = 56990, upload-time = "2025-11-14T10:18:16.714Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/32/6a69c5ccbaf38557f6a090b565ea12a773a64f3f29e87e625c03fa46c183/pyobjc_framework_network-12.2.1.tar.gz", hash = "sha256:0cbb405f304f25617f138a2556433e22d4f706e558a78201957f9e2ca3c9ae21", size = 62791, upload-time = "2026-06-19T16:21:16.907Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/b7/8c29d66920d026532b4acb4ed4e608fd1ee41db602217d6abf2c5f9ea14f/pyobjc_framework_network-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:07264f1dc5d7c437dfbbbf9302a60ead87bbce14692c4cc20b2a259a9fe20b3d", size = 19591, upload-time = "2025-11-14T09:57:14.127Z" }, - { url = "https://files.pythonhosted.org/packages/e3/7c/4f9fc6b94be3e949b7579128cbb9171943e27d1d7841db12d66b76aeadc3/pyobjc_framework_network-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d1ad948b9b977f432bf05363381d7d85a7021246ebf9d50771b35bf8d4548d2b", size = 19593, upload-time = "2025-11-14T09:57:17.027Z" }, - { url = "https://files.pythonhosted.org/packages/9d/ef/a53f04f43e93932817f2ea71689dcc8afe3b908d631c21d11ec30c7b2e87/pyobjc_framework_network-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5e53aad64eae2933fe12d49185d66aca62fb817abf8a46f86b01e436ce1b79e4", size = 19613, upload-time = "2025-11-14T09:57:19.571Z" }, - { url = "https://files.pythonhosted.org/packages/d1/f5/612539c2c0c7ce1160bd348325747f3a94ea367901965b217af877a556a1/pyobjc_framework_network-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e341beb32c7f95ed3e38f00cfed0a9fe7f89b8d80679bf2bd97c1a8d2280180a", size = 19632, upload-time = "2025-11-14T09:57:21.762Z" }, - { url = "https://files.pythonhosted.org/packages/c6/ff/6a1909206f6d840ebcf40c9ea5de9a9ee07e7bb1ffa4fe573da7f90fac12/pyobjc_framework_network-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:8344e3b57afccc762983e4629ec5eff72a3d7292afa8169a3e2aada3348848a8", size = 19696, upload-time = "2025-11-14T09:57:23.948Z" }, - { url = "https://files.pythonhosted.org/packages/e0/6d/a7fb29708f2797fa96bfa6ae740b8154ac719e150939393453073121b7c9/pyobjc_framework_network-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:25e20ec81e23699e1182808384b8e426cb3ae9adaf639684232fc205edb48183", size = 19361, upload-time = "2025-11-14T09:57:26.565Z" }, - { url = "https://files.pythonhosted.org/packages/40/54/9cb89d6fac3e2e8d34107fa6de36ab7890844428b3d4fb4a9692f3cc4926/pyobjc_framework_network-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:39be2f25b13d2d530e893f06ddd3f277b83233020a0ab58413554fe8e0496624", size = 19406, upload-time = "2025-11-14T09:57:28.765Z" }, + { url = "https://files.pythonhosted.org/packages/ed/11/86e09decfdfc0888ac24ea0c04f78174642493f734d9dff27f35e0dc2b30/pyobjc_framework_network-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0f075709b66a7979046cb8122b7324f9d9225b62c192508afdaa80cda3709816", size = 19627, upload-time = "2026-06-19T16:14:51.792Z" }, + { url = "https://files.pythonhosted.org/packages/0b/83/b1ca408d1057c7e84e791ead11a365a110f3872d83c8f19ce1857994e908/pyobjc_framework_network-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2009d4aacca836bd6b6f0aa0b503cfe593488ed55b62d1e5d6a051b7f843f9e8", size = 19627, upload-time = "2026-06-19T16:14:52.858Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c8/098f41c92c847c4c7f0f3efa1bd243555926c34df95dc39d2103bc178710/pyobjc_framework_network-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45d0ad8977ac71624bb6bbd929df8768a39fc8c13e97712bd7d37a4b9b4061e2", size = 19652, upload-time = "2026-06-19T16:14:53.759Z" }, + { url = "https://files.pythonhosted.org/packages/12/1a/4580c0fc4433a9761812cf392b93d7c874be1cb2b34f52c6169621bc284b/pyobjc_framework_network-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ff15672b23decfd9ec9a0051ccd15f4abf098020c4700ff8d2466e597e747eb1", size = 19666, upload-time = "2026-06-19T16:14:54.783Z" }, + { url = "https://files.pythonhosted.org/packages/bb/00/724d2761f1af78b5f108804bdd68896a6d260fc472856c444dd495afef5d/pyobjc_framework_network-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9d2df906f6cb262c74ee6b28f80856eea52d07723c325a04e804357b77c9a335", size = 19735, upload-time = "2026-06-19T16:14:55.656Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ca/36fde1adeb34d7e5000808fcbcab985b78810dce8ee803edf9fb4a73bf45/pyobjc_framework_network-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8d84a21895c3ecbbc48d683c7210c0bd12259a3e97cbab16af125248abb3fab4", size = 19396, upload-time = "2026-06-19T16:14:56.623Z" }, + { url = "https://files.pythonhosted.org/packages/39/bc/882f1c507d512b7cffa39af8bfe4556d2b83bebf5be025e8fdcf21752aec/pyobjc_framework_network-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:3a27d2276da4e3bb1e826defddf6b09bd8fa80fd045d3a711bd429785df8f5fd", size = 19453, upload-time = "2026-06-19T16:14:57.467Z" }, + { url = "https://files.pythonhosted.org/packages/17/33/f7f7a3de02f8369070ebfb79261fdf1df3c1857476cd9b10a43c1b73e50f/pyobjc_framework_network-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:a9484c5acd6507a7924307239de798aa1d56d35abb5d08664e9767411f40f9c0", size = 19405, upload-time = "2026-06-19T16:14:58.262Z" }, + { url = "https://files.pythonhosted.org/packages/11/da/7bdbe57f1f288929502226afbdc7bb505ddc49c53fbcfbf47352fc77ba36/pyobjc_framework_network-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:3bcc2d70564c0d3c290be0f2be4c129bbeda18ac963f0513f6b5d8e482e1d2c4", size = 19462, upload-time = "2026-06-19T16:14:59.187Z" }, ] [[package]] name = "pyobjc-framework-networkextension" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bf/3e/ac51dbb2efa16903e6af01f3c1f5a854c558661a7a5375c3e8767ac668e8/pyobjc_framework_networkextension-12.1.tar.gz", hash = "sha256:36abc339a7f214ab6a05cb2384a9df912f247163710741e118662bd049acfa2e", size = 62796, upload-time = "2025-11-14T10:18:21.769Z" } +sdist = { url = "https://files.pythonhosted.org/packages/69/38/7fdd6bce0c65c4ec01662e4489950b712034faa5adb59428a13ce9d56b0c/pyobjc_framework_networkextension-12.2.1.tar.gz", hash = "sha256:7858164a3e28dc81d317412123fcd664424da9afae63036e31979668ecd5972d", size = 81345, upload-time = "2026-06-19T16:21:17.804Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/60/2d/e67ba8031d4cd819e1c3a961da6602390f55111df3dcf1ba5b429d6594e8/pyobjc_framework_networkextension-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd96684be3a942a301eb38b5f46236091c87ec9830ed8d56be434e420af45387", size = 14365, upload-time = "2025-11-14T09:57:30.797Z" }, - { url = "https://files.pythonhosted.org/packages/6e/4e/aa34fc983f001cdb1afbbb4d08b42fd019fc9816caca0bf0b166db1688c1/pyobjc_framework_networkextension-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c3082c29f94ca3a05cd1f3219999ca3af9b6dece1302ccf789f347e612bb9303", size = 14368, upload-time = "2025-11-14T09:57:33.748Z" }, - { url = "https://files.pythonhosted.org/packages/f6/14/4934b10ade5ad0518001bfc25260d926816b9c7d08d85ef45e8a61fdef1b/pyobjc_framework_networkextension-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:adc9baacfc532944d67018e381c7645f66a9fa0064939a5a841476d81422cdcc", size = 14376, upload-time = "2025-11-14T09:57:36.132Z" }, - { url = "https://files.pythonhosted.org/packages/cb/a8/5d847dd3ffea913597342982614eb17bad4c29c07fac3447b56c9c5136ab/pyobjc_framework_networkextension-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:63453b38e5a795f9ff950397e5a564071c2b4fd3360d79169ab017755bbb932a", size = 14399, upload-time = "2025-11-14T09:57:38.178Z" }, - { url = "https://files.pythonhosted.org/packages/b4/a8/8d56c6ca7826633f856924256761338094eeab1ae40783c29c14b9746bc9/pyobjc_framework_networkextension-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e21d8ec762ded95afaff41b68425219df55ca8c3f777b810238441a4f7c221e3", size = 14539, upload-time = "2025-11-14T09:57:40.222Z" }, - { url = "https://files.pythonhosted.org/packages/b6/00/460b9ef440663299153ac0c165a56916620016435d402e4cf4cfdc74b521/pyobjc_framework_networkextension-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21076ec44790023b579f21f6b88e13388d353de98658dbb50369df53e6a9c967", size = 14453, upload-time = "2025-11-14T09:57:42.556Z" }, - { url = "https://files.pythonhosted.org/packages/4d/ee/c9ea9e426b169d3ae54ddcad46828a6236168cfadbab37abc892d07a75ce/pyobjc_framework_networkextension-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:06d78bab27d4a7c51c9787b1f4cfcfed4d85488fcd96d93bac400bb2690ddceb", size = 14589, upload-time = "2025-11-14T09:57:45.012Z" }, + { url = "https://files.pythonhosted.org/packages/94/90/00bf41b1cce7458ce850161e62acd536f4b798ecc494cda872ca5cc10c77/pyobjc_framework_networkextension-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:715472ba0cd2a0021c779aa9c0c70fc6b9c19ca123bced972d480970ad544ef8", size = 14466, upload-time = "2026-06-19T16:14:59.984Z" }, + { url = "https://files.pythonhosted.org/packages/bf/7c/5f300ebc1d2bde42af0fad1830cf221ee5ccebba0eba3e568daec1cbc352/pyobjc_framework_networkextension-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:190db4aee1316c9d3d4da8e7a8fb44b98b70a6f6930f69baf7c9258082f7fc63", size = 14457, upload-time = "2026-06-19T16:15:01.021Z" }, + { url = "https://files.pythonhosted.org/packages/42/e9/084b993f44295a3c7a95434d4e655050655a6fae17d8488aea6ab9c65bec/pyobjc_framework_networkextension-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fb4c79aae8bd30099a2373d379a7d3cbfc6c210fbcdcb766c58d209dfe710062", size = 14476, upload-time = "2026-06-19T16:15:01.848Z" }, + { url = "https://files.pythonhosted.org/packages/b1/26/09becc50d9ca9a00dde6ec835b3b49249b006bd162bf40beb05661897330/pyobjc_framework_networkextension-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:94e851bdcd902dfac889daa5017bb9e3bc7086332956d9ecc53ef087d332d1f3", size = 14490, upload-time = "2026-06-19T16:15:02.7Z" }, + { url = "https://files.pythonhosted.org/packages/a5/f9/f1aa8f1ec246ee79018d068beb38a25af39287c671f471545216e338d695/pyobjc_framework_networkextension-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:812cc5a68e464b1a0047048a46e0f1d44718cc39c37ab9d8d2827c34a37cc2a1", size = 14637, upload-time = "2026-06-19T16:15:03.586Z" }, + { url = "https://files.pythonhosted.org/packages/9c/8b/4e19d49b25a7797c0720755dc7cdc4e54098fe92b128401f34c31ab59a65/pyobjc_framework_networkextension-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e7c43d18132a7140bd4cd8ff81977a3844579020dd22604f06684d0aad667c45", size = 14552, upload-time = "2026-06-19T16:15:04.462Z" }, + { url = "https://files.pythonhosted.org/packages/50/e5/17af4957cc34ba654d61a435bd028cc95253c45d6e1e480cb9b294a75089/pyobjc_framework_networkextension-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d99ac04d1fb84ef4593ee4eb9a18d80e9f7dc687ed642ead6a67ec4cd1ac9fcf", size = 14692, upload-time = "2026-06-19T16:15:05.342Z" }, + { url = "https://files.pythonhosted.org/packages/15/4b/ae571a55431fb97f7930c42a62fd84aba12ceabf7fa504b01d18fc5f9abb/pyobjc_framework_networkextension-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:e639073b2d2a825af35ff7db046e0fdd969707710b4a77c184e203fee65e5c12", size = 14544, upload-time = "2026-06-19T16:15:06.177Z" }, + { url = "https://files.pythonhosted.org/packages/f8/08/ebb145dd982e95cddbcb66c9e2bb9e4a238212f67cb9e6005b2d3570eee9/pyobjc_framework_networkextension-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:2bf7bcd4712ca3636933f64e5c8fb2f72c5854a7cd6c20738ab88831e37c5922", size = 14680, upload-time = "2026-06-19T16:15:07.03Z" }, ] [[package]] name = "pyobjc-framework-notificationcenter" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c6/12/ae0fe82fb1e02365c9fe9531c9de46322f7af09e3659882212c6bf24d75e/pyobjc_framework_notificationcenter-12.1.tar.gz", hash = "sha256:2d09f5ab9dc39770bae4fa0c7cfe961e6c440c8fc465191d403633dccc941094", size = 21282, upload-time = "2025-11-14T10:18:24.51Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/22/6e5b84c0b0b187525fe655508adba71fb208abb747326f2a9da84dd2d6b3/pyobjc_framework_notificationcenter-12.2.1.tar.gz", hash = "sha256:952d0bfff1653f16f9e79336c8eeb928ed517f0212c914191a925a85523b5af6", size = 22159, upload-time = "2026-06-19T16:21:18.786Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/fb/b2a9c66467ccd36137d77240939332308f847ffa7e2c00cade6da3604f9e/pyobjc_framework_notificationcenter-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f227d4b2e197f614b64302faea974f25434827da30f6d46b3a8d73cb3788cf69", size = 9874, upload-time = "2025-11-14T09:57:47.098Z" }, - { url = "https://files.pythonhosted.org/packages/47/aa/03526fc0cc285c0f8cf31c74ce3a7a464011cc8fa82a35a1637d9878c788/pyobjc_framework_notificationcenter-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:84e254f2a56ff5372793dea938a2b2683dd0bc40c5107fede76f9c2c1f6641a2", size = 9871, upload-time = "2025-11-14T09:57:49.208Z" }, - { url = "https://files.pythonhosted.org/packages/d8/05/3168637dd425257df5693c2ceafecf92d2e6833c0aaa6594d894a528d797/pyobjc_framework_notificationcenter-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:82a735bd63f315f0a56abd206373917b7d09a0ae35fd99f1639a0fac4c525c0a", size = 9895, upload-time = "2025-11-14T09:57:51.151Z" }, - { url = "https://files.pythonhosted.org/packages/44/9a/f2b627dd4631a0756ee3e99b57de1e78447081d11f10313ed198e7521a31/pyobjc_framework_notificationcenter-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:06470683f568803f55f1646accfbf5eaa3fda56d15f27fca31bdbff4eaa8796c", size = 9917, upload-time = "2025-11-14T09:57:53.001Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f5/5fff664571dc48eea9246d31530fc564c654af827bfca1ddab47b72dc344/pyobjc_framework_notificationcenter-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:bdf87e5f027bec727b24bb1764a9933af9728862f6a0e9a7f4a1835061f283dd", size = 10110, upload-time = "2025-11-14T09:57:55.015Z" }, - { url = "https://files.pythonhosted.org/packages/da/0a/621ed53aa7521d534275b8069c0f0d5e6517d772808a49add8476ad5c86d/pyobjc_framework_notificationcenter-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9495b1b0820a3e82bfcd0331b92bc29e4e4ca3a4e58d6ec0e1eda6c301ec4460", size = 9980, upload-time = "2025-11-14T09:57:56.666Z" }, - { url = "https://files.pythonhosted.org/packages/78/1a/b427a2316fb783a7dc58b12ce4d58de3263927614a9ff04934aeb10d8b8a/pyobjc_framework_notificationcenter-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1aca78efbf3ceab878758ec11dacef0c85629f844eee9e21645319dd98fd3673", size = 10186, upload-time = "2025-11-14T09:57:58.317Z" }, + { url = "https://files.pythonhosted.org/packages/63/46/47885e2e78d638bc7e06481a1b6b04f0f47f682ee060ea70a6dc98ae5225/pyobjc_framework_notificationcenter-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f21c5c26627de04362c9ce0aa556a77e0bdaf64cc8925e45495254f49135e06", size = 9880, upload-time = "2026-06-19T16:15:07.886Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f6/1f09a8c541da1050c911bb292574cefa60deb23d29c0663badc6b7718f8b/pyobjc_framework_notificationcenter-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:40a1a0bf0057d6ec1567b58947e6b3550b13ea5cffa721ad63b1c713a73276aa", size = 9881, upload-time = "2026-06-19T16:15:08.914Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f3/2e870eb44592692ff85e829b28f2004eece7e1b1c4578dfd715251649b5a/pyobjc_framework_notificationcenter-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0e86691f099a752a625b1a03a42f8cd8b28705bae5cb9924c988a7a8bc429d5c", size = 9900, upload-time = "2026-06-19T16:15:09.73Z" }, + { url = "https://files.pythonhosted.org/packages/ad/27/a56dece1c44638a8736a0878c8b52b153940183781a6065ca80fd38644b3/pyobjc_framework_notificationcenter-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:72187126a81e4a5f1fecab21b4740628dc6fa75a6a0471a0c115adfb49c04893", size = 9916, upload-time = "2026-06-19T16:15:10.75Z" }, + { url = "https://files.pythonhosted.org/packages/e8/37/1acb093763b4b83de911f53f28363bd4a81190511f7794235dd279d850d8/pyobjc_framework_notificationcenter-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c61e2dd16fe34410d972c57e62c6c4949f86e97b12fd1f1079b75ba409df7837", size = 10114, upload-time = "2026-06-19T16:15:11.543Z" }, + { url = "https://files.pythonhosted.org/packages/82/1a/fb2f8fe66035c42b05900c8533ab4674c56696d180ef6a6f84387cea95dd/pyobjc_framework_notificationcenter-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:f91d2354beb4c928f4feda65bc2d73166aff1865a5bdccab2719744398c8d2cc", size = 9983, upload-time = "2026-06-19T16:15:12.391Z" }, + { url = "https://files.pythonhosted.org/packages/54/fe/ea0cb8dc6771235db520c8ebe2e5899b6c45a835cfeacd82b324b6a5130e/pyobjc_framework_notificationcenter-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:65de365859ab6e87763df7fce64f141d1850e5f23ec88649cf4b0071900a8183", size = 10188, upload-time = "2026-06-19T16:15:13.312Z" }, + { url = "https://files.pythonhosted.org/packages/06/52/6692a17ccb5f9b124f1c2a1f9dc6a4cb42d267d5478c0e8f4dd5a930538a/pyobjc_framework_notificationcenter-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:a3c19ff09ecb73f55a94a45b4004795d60027e0a05dbfb7212e5e8c2855aa9d6", size = 9976, upload-time = "2026-06-19T16:15:14.127Z" }, + { url = "https://files.pythonhosted.org/packages/b5/5b/4fd8da648e6712d4c3558042c285cd1bf36b333dcc40529725487220145b/pyobjc_framework_notificationcenter-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:047ff96770aaa238bdce7f5ce73d954a1b0cab83cbff64188f92e852ad46c112", size = 10177, upload-time = "2026-06-19T16:15:14.953Z" }, ] [[package]] name = "pyobjc-framework-opendirectory" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5b/11/bc2f71d3077b3bd078dccad5c0c5c57ec807fefe3d90c97b97dd0ed3d04b/pyobjc_framework_opendirectory-12.1.tar.gz", hash = "sha256:2c63ce5dd179828ef2d8f9e3961da3bfa971a57db07a6c34eedc296548a928bb", size = 61049, upload-time = "2025-11-14T10:18:29.336Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/02/df1979038cdc426990b364b2307ef33f5a6d915e70eda4791daf692024e7/pyobjc_framework_opendirectory-12.2.1.tar.gz", hash = "sha256:1b6dc2eea7857f05063f22e746ae66e8a2a135e41c62b7f3ca7c91f8a5ec5de0", size = 69907, upload-time = "2026-06-19T16:21:19.553Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/e7/3c2dece9c5b28af28a44d72a27b35ea5ffac31fed7cbd8d696ea75dc4a81/pyobjc_framework_opendirectory-12.1-py2.py3-none-any.whl", hash = "sha256:b5b5a5cf3cc2fb25147b16b79f046b90e3982bf3ded1b210a993d8cfdba737c4", size = 11845, upload-time = "2025-11-14T09:58:00.175Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2a/e4bd17504ad233a38ea2d73a2b9914310c489a34f71418962bf99ff35799/pyobjc_framework_opendirectory-12.2.1-py2.py3-none-any.whl", hash = "sha256:2604cd01e236a1237ee6e52c689e60fb380807c5bda6981dab37dd14d89dd254", size = 11939, upload-time = "2026-06-19T16:15:15.771Z" }, ] [[package]] name = "pyobjc-framework-osakit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cb/b9/bf52c555c75a83aa45782122432fa06066bb76469047f13d06fb31e585c4/pyobjc_framework_osakit-12.1.tar.gz", hash = "sha256:36ea6acf03483dc1e4344a0cce7250a9656f44277d12bc265fa86d4cbde01f23", size = 17102, upload-time = "2025-11-14T10:18:31.354Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/41/0a010e6e48e5bb248a8c4d6b7f71ecb1cf17c92b194db512b536f60a54a8/pyobjc_framework_osakit-12.2.1.tar.gz", hash = "sha256:6656e6dab5eb2b571cdcd0d68c0084fa2ac5f85f2f06423e132b410c44e87187", size = 18924, upload-time = "2026-06-19T16:21:20.394Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/99/10/30a15d7b23e6fcfa63d41ca4c7356c39ff81300249de89c3ff28216a9790/pyobjc_framework_osakit-12.1-py2.py3-none-any.whl", hash = "sha256:c49165336856fd75113d2e264a98c6deb235f1bd033eae48f661d4d832d85e6b", size = 4162, upload-time = "2025-11-14T09:58:01.953Z" }, + { url = "https://files.pythonhosted.org/packages/54/ff/ba06033f6b8b5a43a188c810c4439a35cbc0baf90bfa90e4a1682260c1d7/pyobjc_framework_osakit-12.2.1-py2.py3-none-any.whl", hash = "sha256:00c1996bde228324665795f9fe9e129eccfbeab1c10d8ba34e1b1adae379b482", size = 4166, upload-time = "2026-06-19T16:15:16.725Z" }, ] [[package]] name = "pyobjc-framework-oslog" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, @@ -3316,708 +3500,758 @@ dependencies = [ { name = "pyobjc-framework-coremedia" }, { name = "pyobjc-framework-quartz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/12/42/805c9b4ac6ad25deb4215989d8fc41533d01e07ffd23f31b65620bade546/pyobjc_framework_oslog-12.1.tar.gz", hash = "sha256:d0ec6f4e3d1689d5e4341bc1130c6f24cb4ad619939f6c14d11a7e80c0ac4553", size = 21193, upload-time = "2025-11-14T10:18:33.645Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/3b/8a38104775d9472cc360018ca358325135779037122813f5d4cf0f1ce4f6/pyobjc_framework_oslog-12.2.1.tar.gz", hash = "sha256:423e19e08d3f01f3b0d53f2b4503322c0ef9d116c0a1f91fe36273232cf0ff22", size = 22322, upload-time = "2026-06-19T16:21:21.153Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/ed/51e0dd2cfbd29b053d345e87965e5c15ff01d6925f5523a15d1fc9740b42/pyobjc_framework_oslog-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4bdcba214ca33563408b7703282f9a99ca61d04bcc34d5474abc68621fd44c48", size = 7795, upload-time = "2025-11-14T09:58:03.695Z" }, - { url = "https://files.pythonhosted.org/packages/d9/d5/8d37c2e733bd8a9a16546ceca07809d14401a059f8433cdc13579cd6a41a/pyobjc_framework_oslog-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8dd03386331fbb6b39df8941d99071da0bfeda7d10f6434d1daa1c69f0e7bb14", size = 7802, upload-time = "2025-11-14T09:58:05.619Z" }, - { url = "https://files.pythonhosted.org/packages/ee/60/0b742347d484068e9d6867cd95dedd1810c790b6aca45f6ef1d0f089f1f5/pyobjc_framework_oslog-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:072a41d36fcf780a070f13ac2569f8bafbb5ae4792fab4136b1a4d602dd9f5b4", size = 7813, upload-time = "2025-11-14T09:58:07.768Z" }, - { url = "https://files.pythonhosted.org/packages/89/ad/719d65e7202623da7a3f22225e7f2b736f38cd6d3e0d87253b7f74f5b9c0/pyobjc_framework_oslog-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d26ce39be2394695cf4c4c699e47f9b85479cf1ccb0472614bb88027803a8986", size = 7834, upload-time = "2025-11-14T09:58:09.586Z" }, - { url = "https://files.pythonhosted.org/packages/86/f0/a042b06f47d11bdad58d5c0cec9fe3dc4dc12ed9e476031cd4c0f08c6f18/pyobjc_framework_oslog-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6925e6764c6f293b69fbd4f5fd32a9810fca07d63e782c41cb4ebf05dc42977", size = 8016, upload-time = "2025-11-14T09:58:11.431Z" }, - { url = "https://files.pythonhosted.org/packages/f4/c1/7a7742fc81708c53a0f736ce883069b3c1797440d691a7ed7b8e29e8dbbd/pyobjc_framework_oslog-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:16d98c49698da839b79904a2c63fee658fd4a8c4fa9223e5694270533127e8d4", size = 7875, upload-time = "2025-11-14T09:58:13.202Z" }, - { url = "https://files.pythonhosted.org/packages/09/d2/c5703c03d6b57a3c729e211556c88e44ca4bfbe45bcbf5d6f4843095fdeb/pyobjc_framework_oslog-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:302956914b0d28dc9d8e27c2428d46c89cde8e2c64a426cda241d4b0c64315fd", size = 8075, upload-time = "2025-11-14T09:58:14.723Z" }, + { url = "https://files.pythonhosted.org/packages/26/91/ba00583c3b1bdbcf8f259e1dee5cf7aaac3663d9fadb64e96fd366d03184/pyobjc_framework_oslog-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7fc09577bbcd66ea2395956b929ab3847d73aaf4ed94a2a08c5715396c234fc1", size = 7887, upload-time = "2026-06-19T16:15:17.612Z" }, + { url = "https://files.pythonhosted.org/packages/99/15/94c87fdaacac513dd737d86873e5c85683a4b2d247b86b778b73c11e533f/pyobjc_framework_oslog-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f54740a9a37deaa197b0af793a7fa0ecb909131199278ea00e833b2350516f06", size = 7889, upload-time = "2026-06-19T16:15:18.584Z" }, + { url = "https://files.pythonhosted.org/packages/09/c8/834e7bef1853a936a4bdb019f6e55f985ee3bd04ef6e7dc2a6f1a964e144/pyobjc_framework_oslog-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6cb3738d55676285692108f6cd43773ad9589030bda1077e8ff7080e44e635d0", size = 7910, upload-time = "2026-06-19T16:15:19.452Z" }, + { url = "https://files.pythonhosted.org/packages/91/32/f6dc6ca6d9487e9be095193740263e59b700a239dac6b6a3bd75c30d7449/pyobjc_framework_oslog-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a0ab4755e4b31d304d8eb04291470a7381d69b0381cec02242bdf37e2a492438", size = 7922, upload-time = "2026-06-19T16:15:20.191Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b7/937982d62f343fa41a3b0ccc3b481b73f804a04e185ebfd15044d21529ca/pyobjc_framework_oslog-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:bf2a170e8fcef12de89749f085d3ef52f0a3a7084a566f244377012301d91c63", size = 8104, upload-time = "2026-06-19T16:15:21.196Z" }, + { url = "https://files.pythonhosted.org/packages/67/c1/12795679095752db88da5d3ac93d7e767b8e35b8a487a64405232fbf49f9/pyobjc_framework_oslog-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:d16161bf907569eb02be4e0e4b515cd1ec52da4aaac7e4bf825d2853fd2fc2de", size = 7965, upload-time = "2026-06-19T16:15:21.978Z" }, + { url = "https://files.pythonhosted.org/packages/07/82/5e6342650a407fab78cd20bdd4f94ae165eac8b951263f34a79c544999a0/pyobjc_framework_oslog-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1253dda3bce3ab7174ba215f011cad47e11b43c8406edb09e99bb122b34af021", size = 8168, upload-time = "2026-06-19T16:15:22.836Z" }, + { url = "https://files.pythonhosted.org/packages/92/56/a62a21f3125117ba54c2da16db061bc48037387ccea2a9f962f065293985/pyobjc_framework_oslog-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:c2fb370aa829850de13643191ff26a1a23b2d5b77649e883b274607866e417af", size = 7963, upload-time = "2026-06-19T16:15:23.859Z" }, + { url = "https://files.pythonhosted.org/packages/91/b5/47330df78d9ca18fe2ce003ca1a76ca4341be98b263d879c80683c980c7b/pyobjc_framework_oslog-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:5b4bda2651fc0a5ceeb28e8a82ddf5ad23bbe86b9feaf9df8f10c3d283d4f9a2", size = 8167, upload-time = "2026-06-19T16:15:24.677Z" }, ] [[package]] name = "pyobjc-framework-passkit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6c/d4/2afb59fb0f99eb2f03888850887e536f1ef64b303fd756283679471a5189/pyobjc_framework_passkit-12.1.tar.gz", hash = "sha256:d8c27c352e86a3549bf696504e6b25af5f2134b173d9dd60d66c6d3da53bb078", size = 53835, upload-time = "2025-11-14T10:18:37.906Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/b9/5762ada91652118bd08b4bd236e4cff00edf5211403ee49cd8563a2330c2/pyobjc_framework_passkit-12.2.1.tar.gz", hash = "sha256:28de8925d89b705b9344e59498d15e5a10935e982b19eed10f0d498c5e670e7b", size = 68251, upload-time = "2026-06-19T16:21:21.983Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/d5/0af77cf3af6ab475e5ea301afb4085902e4a09cf8c0b64793e8958170f22/pyobjc_framework_passkit-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b8383b36a99abea9f8d9fe05f89abca34e57d604664adc4964b1a8d6b9a0d04a", size = 14084, upload-time = "2025-11-14T09:58:16.73Z" }, - { url = "https://files.pythonhosted.org/packages/25/e6/dabd6b99bdadc50aa0306495d8d0afe4b9b3475c2bafdad182721401a724/pyobjc_framework_passkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cb5c8f0fdc46db6b91c51ee1f41a2b81e9a482c96a0c91c096dcb78a012b740a", size = 14087, upload-time = "2025-11-14T09:58:18.991Z" }, - { url = "https://files.pythonhosted.org/packages/d8/dc/9cb27e8b7b00649af5e802815ffa8928bd8a619f2984a1bea7dabd28f741/pyobjc_framework_passkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7e95a484ec529dbf1d44f5f7f1406502a77bda733511e117856e3dca9fa29c5c", size = 14102, upload-time = "2025-11-14T09:58:20.903Z" }, - { url = "https://files.pythonhosted.org/packages/7c/e2/6135402be2151042b234ea241e89f4b8984f6494fd11d9f56b4a56a9d7d4/pyobjc_framework_passkit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:64287e6dc54ab4c0aa8ba80a7a51762e36591602c77c6a803aee690e7464b6b2", size = 14110, upload-time = "2025-11-14T09:58:23.107Z" }, - { url = "https://files.pythonhosted.org/packages/23/f3/ff6f81206eca1e1fb49c5a516d5eb15f143b38c5adee5b0c24076be02be9/pyobjc_framework_passkit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a360e98b29eee8642f3e7d973c636284c24fb2ec2c3ee56022eeae6270943be", size = 14277, upload-time = "2025-11-14T09:58:25.338Z" }, - { url = "https://files.pythonhosted.org/packages/dc/71/bde73bb39a836fb07c10fbdc60f38a3bd436c0aada1de0f4140737813930/pyobjc_framework_passkit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e28dcf1074cddd82c2bd3ee5c3800952ac59850578b1135b38871ff584ea9d41", size = 14118, upload-time = "2025-11-14T09:58:27.353Z" }, - { url = "https://files.pythonhosted.org/packages/c1/13/f2a4fe4fb6ce91689f16c577089fe19748b3be322a28099543a89ee6c0fb/pyobjc_framework_passkit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a8782f31254016a9b152a9d1dc7ea18187729221f6ca175927be99a65b97640e", size = 14280, upload-time = "2025-11-14T09:58:29.374Z" }, + { url = "https://files.pythonhosted.org/packages/fe/4e/b04b8e6c404188b2dc94ebf2770aec072035565ac942ff37963a13d14747/pyobjc_framework_passkit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:34d26b879140c75f0609304d98ac660dafa354c77277c897213f85743f919204", size = 14454, upload-time = "2026-06-19T16:15:25.466Z" }, + { url = "https://files.pythonhosted.org/packages/92/65/8e8a038c97895a1f88593894b238663df1d21a4563a06fdc531e53914c04/pyobjc_framework_passkit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:730c14e62f822053ed4502407da3b563bdaae0b55d1177ae2b12526fc2be90cc", size = 14456, upload-time = "2026-06-19T16:15:26.519Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b4/6f1fa4a3cfa23baef9eac38cf2df7b8dc8f64f99e0796c77cb909a6d95bf/pyobjc_framework_passkit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:85df88aeba17f3aaa86f4eae1f0cba99ada8c18df7fd1ca49548276399417e26", size = 14472, upload-time = "2026-06-19T16:15:27.47Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/3500af51c2758b71cd2e3835a08df33cdce6b5c72b8275f7e1d776c0b610/pyobjc_framework_passkit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:28e1c22d9476cdafe7839f368fd6e8c99f4925aa85e4f9126e5921f4c7e834a1", size = 14485, upload-time = "2026-06-19T16:15:28.371Z" }, + { url = "https://files.pythonhosted.org/packages/df/2c/addcb03ccdd59685043af645974d56aeb9867ac87a9e29acb0cbfca88d97/pyobjc_framework_passkit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e51509baa9f4883b1583c410dfcf8b68372a4457e13cd57f4c9490c6e9895e06", size = 14651, upload-time = "2026-06-19T16:15:29.194Z" }, + { url = "https://files.pythonhosted.org/packages/4c/7f/85a5e3c85c1e152d73b8e820d2ddcd3d9bc7d9aa339a32f2e25a14892862/pyobjc_framework_passkit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6edade68d8c530e7fdaa7cb888899e8e7466b976e5736980136caf6b39903e2a", size = 14492, upload-time = "2026-06-19T16:15:30.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/eb/1c21b7018b1a62ff4c99498253f1a0e9d4d9fe28c43ce8c1c5fc713c0d46/pyobjc_framework_passkit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:872c816839d89097fcdbf60c5a22ed277277d299d0ebc4db3b598702c750f22a", size = 14650, upload-time = "2026-06-19T16:15:31.242Z" }, + { url = "https://files.pythonhosted.org/packages/5a/eb/eb100daa0f738eea6e89efa365af68ca2d45934d1dd11a383c31b61cade5/pyobjc_framework_passkit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:71805f7f9ee46976136b11f4563d490dd47219472f1648c3aba8a13e826ec1de", size = 14483, upload-time = "2026-06-19T16:15:32.312Z" }, + { url = "https://files.pythonhosted.org/packages/19/43/c696150723da6916f03172c3cbdd1bec6e3876844de913f92f698dcaafdc/pyobjc_framework_passkit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:50baf3bad86dd86c8f9e081df408cffe0830b18446775afc32acd69146560500", size = 14648, upload-time = "2026-06-19T16:15:33.285Z" }, ] [[package]] name = "pyobjc-framework-pencilkit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7e/43/859068016bcbe7d80597d5c579de0b84b0da62c5c55cdf9cc940e9f9c0f8/pyobjc_framework_pencilkit-12.1.tar.gz", hash = "sha256:d404982d1f7a474369f3e7fea3fbd6290326143fa4138d64b6753005a6263dc4", size = 17664, upload-time = "2025-11-14T10:18:40.045Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/6a/f8831e5b5bbdc3e999f3bd95c7e297bd8d8641ec3c1fcd153c77616e0e2f/pyobjc_framework_pencilkit-12.2.1.tar.gz", hash = "sha256:2545561beece43d63c745b6bbc4503cc7c55ad0792d4206916c75da26a4dca49", size = 20110, upload-time = "2026-06-19T16:21:22.756Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/26/daf47dcfced8f7326218dced5c68ed2f3b522ec113329218ce1305809535/pyobjc_framework_pencilkit-12.1-py2.py3-none-any.whl", hash = "sha256:33b88e5ed15724a12fd8bf27a68614b654ff739d227e81161298bc0d03acca4f", size = 4206, upload-time = "2025-11-14T09:58:30.814Z" }, + { url = "https://files.pythonhosted.org/packages/30/c6/eb126b4b6e93ce53ba1451b04fb5a7724c9569e8ff185d34c56ebec869bb/pyobjc_framework_pencilkit-12.2.1-py2.py3-none-any.whl", hash = "sha256:d8214b76615d7f0fb6295ee21c679edeb889a03f355c1774f792dfb93bd313a5", size = 4266, upload-time = "2026-06-19T16:15:34.139Z" }, ] [[package]] name = "pyobjc-framework-phase" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-avfoundation" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/96/51/3b25eaf7ca85f38ceef892fdf066b7faa0fec716f35ea928c6ffec6ae311/pyobjc_framework_phase-12.1.tar.gz", hash = "sha256:3a69005c572f6fd777276a835115eb8359a33673d4a87e754209f99583534475", size = 32730, upload-time = "2025-11-14T10:18:43.102Z" } +sdist = { url = "https://files.pythonhosted.org/packages/10/61/1ecd9506eee8698ab1156a7d99980d668e22b73cde9638cec98cd3d610f4/pyobjc_framework_phase-12.2.1.tar.gz", hash = "sha256:31392185ec0d3b0c5974c9b71f0c540843b1bdd884819484e627c6c0d592388a", size = 40754, upload-time = "2026-06-19T16:21:23.51Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/9f/1ae45db731e8d6dd3e0b408c3accd0cf3236849e671f95c7c8cf95687240/pyobjc_framework_phase-12.1-py2.py3-none-any.whl", hash = "sha256:99a1c1efc6644f5312cce3693117d4e4482538f65ad08fe59e41e2579b67ab17", size = 6902, upload-time = "2025-11-14T09:58:32.436Z" }, + { url = "https://files.pythonhosted.org/packages/34/b9/a09f9ec64fc04cbaf2070055fa44d03454f6592328493b940fd1496f4560/pyobjc_framework_phase-12.2.1-py2.py3-none-any.whl", hash = "sha256:426b407f9ff0fe2b55128e3c0e9a949db4b0a909ca095d65303c247865961dc2", size = 7211, upload-time = "2026-06-19T16:15:35.089Z" }, ] [[package]] name = "pyobjc-framework-photos" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b8/53/f8a3dc7f711034d2283e289cd966fb7486028ea132a24260290ff32d3525/pyobjc_framework_photos-12.1.tar.gz", hash = "sha256:adb68aaa29e186832d3c36a0b60b0592a834e24c5263e9d78c956b2b77dce563", size = 47034, upload-time = "2025-11-14T10:18:47.27Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/cf/af57f3b72daec93cd92b091473cac35f7616aeb2db5e4ca3a25c984aa5d1/pyobjc_framework_photos-12.2.1.tar.gz", hash = "sha256:e405e612c48563609fe32da9aec9286ed8cab10e226dc58ae6910e141fc46f44", size = 58673, upload-time = "2026-06-19T16:21:24.323Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/4b/7157ab4ed148aea40af5a8c02856672a576fe4ba471c0efa61f94d5ca21f/pyobjc_framework_photos-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ed6ca0ace0d12b469f68d35dddbede445350afd13b3c582e3297792fd08ad5f8", size = 12325, upload-time = "2025-11-14T09:58:34.33Z" }, - { url = "https://files.pythonhosted.org/packages/e4/e0/8824f7cb167934a8aa1c088b7e6f1b5a9342b14694e76eda95fc736282b2/pyobjc_framework_photos-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f28db92602daac9d760067449fc9bf940594536e65ad542aec47d52b56f51959", size = 12319, upload-time = "2025-11-14T09:58:36.324Z" }, - { url = "https://files.pythonhosted.org/packages/13/38/e6f25aec46a1a9d0a310795606cc43f9823d41c3e152114b814b597835a8/pyobjc_framework_photos-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eda8a584a851506a1ebbb2ee8de2cb1ed9e3431e6a642ef6a9543e32117d17b9", size = 12358, upload-time = "2025-11-14T09:58:38.131Z" }, - { url = "https://files.pythonhosted.org/packages/71/5a/3c4e2af8d17e62ecf26e066fbb9209aacccfaf691f5faa42e3fd64b2b9f2/pyobjc_framework_photos-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bd7906d8662af29f91c71892ae0b0cab4682a3a7ef5be1a2277d881d7b8d37d3", size = 12367, upload-time = "2025-11-14T09:58:42.328Z" }, - { url = "https://files.pythonhosted.org/packages/fb/24/566de3200d4aa05ca75b0150e5d031d2384a388f9126a4fef62a8f53818f/pyobjc_framework_photos-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c822d81c778dd2a789f15d0f329cee633391c5ad766482ffbaf40d3dc57584a3", size = 12552, upload-time = "2025-11-14T09:58:44.134Z" }, - { url = "https://files.pythonhosted.org/packages/c2/5c/47b9e1f6ac61a80b6544091dffe42dc883217d6e670ddc188968988ba7f6/pyobjc_framework_photos-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:95d5036bdaf1c50559adfa60fd715b57c68577d2574241ed1890e359849f923f", size = 12422, upload-time = "2025-11-14T09:58:46.072Z" }, - { url = "https://files.pythonhosted.org/packages/b4/33/48cc5ca364e62d08296de459e86daa538291b895b5d1abb670053263e0c4/pyobjc_framework_photos-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:77f181d3cb3fde9c04301c9a96693d02a139d478891e49ed76573dedf0437f49", size = 12607, upload-time = "2025-11-14T09:58:48.084Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b8/e2823462cf3f6f99037e422df8368ebb47727491b7e2031cbbddf98e0248/pyobjc_framework_photos-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:37cc7f86acbe830fd4256164f51b70b167ba3edaa9342c36defa5fc8992d5539", size = 12511, upload-time = "2026-06-19T16:15:35.947Z" }, + { url = "https://files.pythonhosted.org/packages/86/0c/6a0ba1d041a1efa978244a6f74aa03247ec73621728c0df77f49a822044a/pyobjc_framework_photos-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6f90b2ae5080a8626d13e995334ed2e7fa0cf04bdb479d50f18398baa657c701", size = 12513, upload-time = "2026-06-19T16:15:36.972Z" }, + { url = "https://files.pythonhosted.org/packages/08/cb/b141cb95f75e94578e28062a033b4ae3bacb5a0660452e04df728a8b8d25/pyobjc_framework_photos-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:516320e2d3ab7c73bb196f7eb46afa289654829013ae79a51d663700e15ddab0", size = 12549, upload-time = "2026-06-19T16:15:37.983Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2f/599c2bcddbd5514b0a670a022a27b60c04198eec91eec025b25738ae71a4/pyobjc_framework_photos-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8be8e21b65627b4c55367c51258c22f7d73d3dbf7412a341e49684a1da1b60bc", size = 12561, upload-time = "2026-06-19T16:15:38.754Z" }, + { url = "https://files.pythonhosted.org/packages/93/cc/a1c26f3dcc216f3973c1fa7bf19d05f995db70a783790ff682ef96f77c83/pyobjc_framework_photos-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:060ff64355662599bdc1413601c04f1e7d3b8b318d92dec9ab72bdf1cebc08fd", size = 12743, upload-time = "2026-06-19T16:15:39.565Z" }, + { url = "https://files.pythonhosted.org/packages/44/c1/b17e9fed6c94b52bb00d0916638be9e47b4e32eecffb33431f89951ce6ce/pyobjc_framework_photos-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:423d8cc595912f3e3d7dd0b24809e171ba51d5f359f05e604d1bbc1aed7808a7", size = 12610, upload-time = "2026-06-19T16:15:40.445Z" }, + { url = "https://files.pythonhosted.org/packages/96/73/0e8de5913643a0370c9089e9ce7cbd5f84da384bcec9426d164f0d92aa67/pyobjc_framework_photos-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:7438abb6354b1f34c58804048ac26fd57a8084ee6be283d6e99827c940c31b7a", size = 12795, upload-time = "2026-06-19T16:15:41.282Z" }, + { url = "https://files.pythonhosted.org/packages/a9/57/6780525cd80cf915aa958ba917da2f1ffaf27c8964add3548db6deee1b8a/pyobjc_framework_photos-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:c49b8515e34b685c18862b68d04978378cfe29dadcc72d2d9af92054758ee629", size = 12607, upload-time = "2026-06-19T16:15:42.195Z" }, + { url = "https://files.pythonhosted.org/packages/9c/16/f102112441b5ff4f8abd4ef023a9d09f0de9e5ed8a31b8f7b86b73147d5a/pyobjc_framework_photos-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:9a575225535e90715488e75c343183044459dba13920651e283ea52f102715c6", size = 12793, upload-time = "2026-06-19T16:15:43.008Z" }, ] [[package]] name = "pyobjc-framework-photosui" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/40/a5/14c538828ed1a420e047388aedc4a2d7d9292030d81bf6b1ced2ec27b6e9/pyobjc_framework_photosui-12.1.tar.gz", hash = "sha256:9141234bb9d17687f1e8b66303158eccdd45132341fbe5e892174910035f029a", size = 29886, upload-time = "2025-11-14T10:18:50.238Z" } +sdist = { url = "https://files.pythonhosted.org/packages/03/e0/5e8aa49c3fe59572f9097bcea75844352c33c88fbdfcc518c6eef7657c88/pyobjc_framework_photosui-12.2.1.tar.gz", hash = "sha256:cb37100f2c75640d036a9fecf476a6fb68d1cafb5878c0e3f7c47054a63218a1", size = 33856, upload-time = "2026-06-19T16:21:25.249Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/41/2b2e17bd4a07cd399a9031356a98390d403709b53a1e5f7f16b6b79cac43/pyobjc_framework_photosui-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:14a088aeb67232a2e8f8658bd52fa0ccb896a2fe7c4e580299ec2da486c597fa", size = 11692, upload-time = "2025-11-14T09:58:49.911Z" }, - { url = "https://files.pythonhosted.org/packages/64/6c/d678767bbeafa932b91c88bc8bb3a586a1b404b5564b0dc791702eb376c3/pyobjc_framework_photosui-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:02ca941187b2a2dcbbd4964d7b2a05de869653ed8484dc059a51cc70f520cd07", size = 11688, upload-time = "2025-11-14T09:58:51.84Z" }, - { url = "https://files.pythonhosted.org/packages/16/a2/b5afca8039b1a659a2a979bb1bdbdddfdf9b1d2724a2cc4633dca2573d5f/pyobjc_framework_photosui-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:713e3bb25feb5ea891e67260c2c0769cab44a7f11b252023bfcf9f8c29dd1206", size = 11714, upload-time = "2025-11-14T09:58:53.674Z" }, - { url = "https://files.pythonhosted.org/packages/d6/cd/204298e136ff22d3502f0b66cda1d36df89346fa2b20f4a3a681c2c96fee/pyobjc_framework_photosui-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5fa3ca2bc4c8609dee46e3c8fb5f3fbfb615f39fa3d710a213febec38e227758", size = 11725, upload-time = "2025-11-14T09:58:56.694Z" }, - { url = "https://files.pythonhosted.org/packages/f6/5e/492007c629844666e8334e535471c5492e93715965fdffe4f75227f47fac/pyobjc_framework_photosui-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:713ec72b13d8399229d285ccd1e94e5ea2627cf88858977a2a91cc94d1affcd6", size = 11921, upload-time = "2025-11-14T09:58:58.477Z" }, - { url = "https://files.pythonhosted.org/packages/33/4e/d45cae151b0b46ab4110b6ea7d689af9480a07ced3dbf5f0860b201a542a/pyobjc_framework_photosui-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a8e0320908f497d1e548336569f435afd27ed964e65b2aefa3a2d2ea4c041da2", size = 11722, upload-time = "2025-11-14T09:59:00.326Z" }, - { url = "https://files.pythonhosted.org/packages/5c/a3/c46998d5e96d38c04af9465808dba035fe3338d49092d8b887cc3f1c9f3d/pyobjc_framework_photosui-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1b3e9226601533843d6764a7006c2f218123a9c22ac935345c6fb88691b9f78b", size = 11908, upload-time = "2025-11-14T09:59:02.103Z" }, + { url = "https://files.pythonhosted.org/packages/a2/f2/2812759736e586851a2ca0a710ff40da922cca7c8cc2639152dd0f7c9a17/pyobjc_framework_photosui-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8d238d087467da534df666fc05189b6552a784f477458ebbfac69694cd3758f", size = 11782, upload-time = "2026-06-19T16:15:43.791Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4b/e3f11f8cfa520535f3c2d62c43bf5a6492669ee49f8136d87f591de62ab7/pyobjc_framework_photosui-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3589035bf9ca764f5fa1d09bdec976dc5e2f25956ab4ac080a453ec93896fc52", size = 11782, upload-time = "2026-06-19T16:15:44.851Z" }, + { url = "https://files.pythonhosted.org/packages/9c/8e/66323978657e7cb281aae60eb7d4cd1738c495b04b1e8a435cbbbd02dc00/pyobjc_framework_photosui-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a8f0723e75c73d1ed3a29f3a6d374ce2275cf95d32aa8880e1bafe90a0354462", size = 11804, upload-time = "2026-06-19T16:15:45.671Z" }, + { url = "https://files.pythonhosted.org/packages/a9/d1/8ea0b4977612dfa496c73a08feeb197b9016c702e3cbf19360cb0268a9d4/pyobjc_framework_photosui-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:03ab0b9d9dcf3e50b62f8579ee11592d0049044044ec1a2b5eebce3e75d56893", size = 11816, upload-time = "2026-06-19T16:15:46.486Z" }, + { url = "https://files.pythonhosted.org/packages/06/8e/d68e34582ba82ff27c4d7e1b64332bd7b76c51eafed29efaf7f8944a941c/pyobjc_framework_photosui-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:872af4d088ded84a2a4cbc07aea10d98746221409d3179474e84c1a45f5f871c", size = 12013, upload-time = "2026-06-19T16:15:47.391Z" }, + { url = "https://files.pythonhosted.org/packages/f1/9e/12605c68e8dc2bd5564d2d5122eef304158f078247e51cf6b666d9420bbd/pyobjc_framework_photosui-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e9f68423c00f418c1d0a27ca6c86f81da509d25490efd17f274d2d37ee569999", size = 11812, upload-time = "2026-06-19T16:15:48.271Z" }, + { url = "https://files.pythonhosted.org/packages/16/cc/8ac06f3f49b82e23e3bf37fb4bb9838cee7b99d44496d524f0cc29ccedd1/pyobjc_framework_photosui-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:bcdcae68c773736c8171486c608df427f0bedfd6e80ec81005cadef28ae8b349", size = 12001, upload-time = "2026-06-19T16:15:49.157Z" }, + { url = "https://files.pythonhosted.org/packages/e1/f6/34665725e0f6fab6875213abf4ac6267db3ac1511951388ce84d63790baa/pyobjc_framework_photosui-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:e3d2b4446a9b818abddad6d201212ee8fbaf699632e857bc255dd98143cbafb4", size = 11811, upload-time = "2026-06-19T16:15:50.073Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ea/4acf8e674823bf6f01f89599a50cae87249398ddd1d173979a23ce5440a0/pyobjc_framework_photosui-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:5f8560bbcec3b81755be8b4cf78fd38d81914ef6f7cd2d4e74c77f8ee3848a88", size = 11997, upload-time = "2026-06-19T16:15:50.9Z" }, ] [[package]] name = "pyobjc-framework-preferencepanes" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/90/bc/e87df041d4f7f6b7721bf7996fa02aa0255939fb0fac0ecb294229765f92/pyobjc_framework_preferencepanes-12.1.tar.gz", hash = "sha256:b2a02f9049f136bdeca7642b3307637b190850e5853b74b5c372bc7d88ef9744", size = 24543, upload-time = "2025-11-14T10:18:53.259Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/f2/788a198c7b8c44a27c2b358c2cd846bbd97bb6d9c8c457cb248c3c0a8958/pyobjc_framework_preferencepanes-12.2.1.tar.gz", hash = "sha256:1b8e839b364b792441201c1112e17cd6d42f493987169da04ec65eda365d2ede", size = 25113, upload-time = "2026-06-19T16:21:26.178Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/36/7b/8ceec1ab0446224d685e243e2770c5a5c92285bcab0b9324dbe7a893ae5a/pyobjc_framework_preferencepanes-12.1-py2.py3-none-any.whl", hash = "sha256:1b3af9db9e0cfed8db28c260b2cf9a22c15fda5f0ff4c26157b17f99a0e29bbf", size = 4797, upload-time = "2025-11-14T09:59:03.998Z" }, + { url = "https://files.pythonhosted.org/packages/0d/b1/bc42da075faf6e4201bfe2ec4bfda95b71877a15df4a501977ef739b7128/pyobjc_framework_preferencepanes-12.2.1-py2.py3-none-any.whl", hash = "sha256:026ff87afba2d381e3d9dadc58957580558e1a705291da6794107220a3cd8102", size = 4829, upload-time = "2026-06-19T16:15:51.915Z" }, ] [[package]] name = "pyobjc-framework-pushkit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a9/45/de756b62709add6d0615f86e48291ee2bee40223e7dde7bbe68a952593f0/pyobjc_framework_pushkit-12.1.tar.gz", hash = "sha256:829a2fc8f4780e75fc2a41217290ee0ff92d4ade43c42def4d7e5af436d8ae82", size = 19465, upload-time = "2025-11-14T10:18:57.727Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/dd/d0ad735548407e862717b7ef08a29dd51b3b9c1c1987ce43f76c25d72c34/pyobjc_framework_pushkit-12.2.1.tar.gz", hash = "sha256:12800cf33aadfdda5df3e487d4ce3d8a79a2a0efcebbd5b1e11a1ff8a1a4067c", size = 20464, upload-time = "2026-06-19T16:21:28.183Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/a8/c46a22c2341724114dc19bb71485998c127c1c801ea449c2dadd7c7db0cc/pyobjc_framework_pushkit-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1d6cb54971c7ed73ce1d13b683d117d4aa34415563c9ca2437dcffefd489940", size = 8159, upload-time = "2025-11-14T09:59:07.366Z" }, - { url = "https://files.pythonhosted.org/packages/a1/b2/d92045e0d4399feda83ee56a9fd685b5c5c175f7ac8423e2cd9b3d52a9da/pyobjc_framework_pushkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:03f41be8b27d06302ea487a6b250aaf811917a0e7d648cd4043fac759d027210", size = 8158, upload-time = "2025-11-14T09:59:09.593Z" }, - { url = "https://files.pythonhosted.org/packages/b9/01/74cf1dd0764c590de05dc1e87d168031e424f834721940b7bb02c67fe821/pyobjc_framework_pushkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7bdf472a55ac65154e03f54ae0bcad64c4cf45e9b1acba62f15107f2bc994d69", size = 8177, upload-time = "2025-11-14T09:59:11.155Z" }, - { url = "https://files.pythonhosted.org/packages/1b/79/00368a140fe4a14e92393da25ef5a3037a09bb0024d984d7813e7e3fa11c/pyobjc_framework_pushkit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f3751276cb595a9f886ed6094e06004fd11932443e345760eade09119f8e0181", size = 8193, upload-time = "2025-11-14T09:59:13.23Z" }, - { url = "https://files.pythonhosted.org/packages/57/29/dccede214ef1835662066c74138978629d92b6a9f723e28670cfb04f3ce7/pyobjc_framework_pushkit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:64955af6441635449c2af6c6f468c9ba5e413e1494b87617bc1e9fbd8be7e5bf", size = 8339, upload-time = "2025-11-14T09:59:14.754Z" }, - { url = "https://files.pythonhosted.org/packages/16/09/9ba944e1146308460bf7474cdc2a0844682862f9850576494035a7653f4a/pyobjc_framework_pushkit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:de82e1f6e01444582ad2ca6a76aeee1524c23695f0e4f56596f9db3e9d635623", size = 8254, upload-time = "2025-11-14T09:59:16.672Z" }, - { url = "https://files.pythonhosted.org/packages/79/be/9220099adb71ec5ae374d2b5b6c3b34e8c505e42fcd090c73e53035a414f/pyobjc_framework_pushkit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:69c7a03a706bc7fb24ca69a9f79d030927be1e5166c0d2a5a9afc1c5d82a07ec", size = 8388, upload-time = "2025-11-14T09:59:18.707Z" }, + { url = "https://files.pythonhosted.org/packages/c8/01/e9fe9fcb03b7d00960f5c1fce92d4bac1c72acf145dd957c54b990cf28d0/pyobjc_framework_pushkit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5a3ff3f2a069da35d90f01618616de39d95a392d09db3f91416f54802e8942b9", size = 8285, upload-time = "2026-06-19T16:15:53.772Z" }, + { url = "https://files.pythonhosted.org/packages/5b/79/a3a754ffee2e97b20fbd0956fd3cb686b38eec771c6434d2311cf10631af/pyobjc_framework_pushkit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:49262afcec0cedd434cd3d5215d343bead702706115ea00892f776777aef2cc1", size = 8290, upload-time = "2026-06-19T16:15:54.784Z" }, + { url = "https://files.pythonhosted.org/packages/52/80/366a0f1210c433857f0639b040e643fc31accfaef38ab2ab5f6ee93733a1/pyobjc_framework_pushkit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:675f65d1ce111debdd3e371a6e50296ef9345d8123311c658f16f3e9bf8cb106", size = 8305, upload-time = "2026-06-19T16:15:55.606Z" }, + { url = "https://files.pythonhosted.org/packages/c8/bf/69a637549c72fc4da253a1319e1cbeb13b0abcbdb24d09a3c21ddcb031a8/pyobjc_framework_pushkit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c3feb1c38ccde8e24548e5a5032d5adc2cb354f1aaa0c7ebed6babd2fdb86cda", size = 8326, upload-time = "2026-06-19T16:15:56.429Z" }, + { url = "https://files.pythonhosted.org/packages/de/f9/28b5c789ec9167435f0a095cecab27230e41496c33c0a458df546754a2eb/pyobjc_framework_pushkit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9dd060c626f17365803f5dbffe0ceb3062e3710886ee9a7c887f3f70b3285802", size = 8460, upload-time = "2026-06-19T16:15:57.351Z" }, + { url = "https://files.pythonhosted.org/packages/68/68/8eea4540ff3201b1fb0e6301655af3d8e92293e3be23343cdbcf77b7bc2e/pyobjc_framework_pushkit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8ff35a63097b6b7268cadc7cf7754f5d966296847e0b084a8aa9716f86d05cbc", size = 8381, upload-time = "2026-06-19T16:15:58.276Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ca/4ece7bbf8f62acae3f4d0ad46f2b5867f74b8d1c04e4d136b4a2b5058ab6/pyobjc_framework_pushkit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:53928c675b70e71f2e68cb7732e7cf114c1f17ef80bcdb0fdf8060e69ca7239d", size = 8515, upload-time = "2026-06-19T16:15:59.086Z" }, + { url = "https://files.pythonhosted.org/packages/81/aa/18ae91101a5138b19b8773e1d5ceba9d58f9e1964e75cbf9ec917db8a4d7/pyobjc_framework_pushkit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:60457988a79883028b3ca5f6e8cebc7051f553f34e98ed16c8a1885d01c46de6", size = 8372, upload-time = "2026-06-19T16:15:59.974Z" }, + { url = "https://files.pythonhosted.org/packages/5a/3e/9ee2507c94e80d05de593cee7e0198027b066f255af8701fd4481b178aa5/pyobjc_framework_pushkit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:92413a410fb59f8e6b2c7a3c2c4b35721c2245fe4d5afe94cf5c7db065372916", size = 8520, upload-time = "2026-06-19T16:16:00.885Z" }, ] [[package]] name = "pyobjc-framework-quartz" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/94/18/cc59f3d4355c9456fc945eae7fe8797003c4da99212dd531ad1b0de8a0c6/pyobjc_framework_quartz-12.1.tar.gz", hash = "sha256:27f782f3513ac88ec9b6c82d9767eef95a5cf4175ce88a1e5a65875fee799608", size = 3159099, upload-time = "2025-11-14T10:21:24.31Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/f6/2a8b84dbf1fe7c04dd96ea73d991678d4e09a909f51971ecc51629bb2ab4/pyobjc_framework_quartz-12.2.1.tar.gz", hash = "sha256:b3b8b6f71e66147f8ff9e6213864cc8527e3a0b1ee90835b93ce221f4802d9b0", size = 3215521, upload-time = "2026-06-19T16:21:30.199Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/f4/50c42c84796886e4d360407fb629000bb68d843b2502c88318375441676f/pyobjc_framework_quartz-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c6f312ae79ef8b3019dcf4b3374c52035c7c7bc4a09a1748b61b041bb685a0ed", size = 217799, upload-time = "2025-11-14T09:59:32.62Z" }, - { url = "https://files.pythonhosted.org/packages/b7/ef/dcd22b743e38b3c430fce4788176c2c5afa8bfb01085b8143b02d1e75201/pyobjc_framework_quartz-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:19f99ac49a0b15dd892e155644fe80242d741411a9ed9c119b18b7466048625a", size = 217795, upload-time = "2025-11-14T09:59:46.922Z" }, - { url = "https://files.pythonhosted.org/packages/e9/9b/780f057e5962f690f23fdff1083a4cfda5a96d5b4d3bb49505cac4f624f2/pyobjc_framework_quartz-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7730cdce46c7e985535b5a42c31381af4aa6556e5642dc55b5e6597595e57a16", size = 218798, upload-time = "2025-11-14T10:00:01.236Z" }, - { url = "https://files.pythonhosted.org/packages/ba/2d/e8f495328101898c16c32ac10e7b14b08ff2c443a756a76fd1271915f097/pyobjc_framework_quartz-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:629b7971b1b43a11617f1460cd218bd308dfea247cd4ee3842eb40ca6f588860", size = 219206, upload-time = "2025-11-14T10:00:15.623Z" }, - { url = "https://files.pythonhosted.org/packages/67/43/b1f0ad3b842ab150a7e6b7d97f6257eab6af241b4c7d14cb8e7fde9214b8/pyobjc_framework_quartz-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:53b84e880c358ba1ddcd7e8d5ea0407d760eca58b96f0d344829162cda5f37b3", size = 224317, upload-time = "2025-11-14T10:00:30.703Z" }, - { url = "https://files.pythonhosted.org/packages/4a/00/96249c5c7e5aaca5f688ca18b8d8ad05cd7886ebd639b3c71a6a4cadbe75/pyobjc_framework_quartz-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:42d306b07f05ae7d155984503e0fb1b701fecd31dcc5c79fe8ab9790ff7e0de0", size = 219558, upload-time = "2025-11-14T10:00:45.476Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a6/708a55f3ff7a18c403b30a29a11dccfed0410485a7548c60a4b6d4cc0676/pyobjc_framework_quartz-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0cc08fddb339b2760df60dea1057453557588908e42bdc62184b6396ce2d6e9a", size = 224580, upload-time = "2025-11-14T10:01:00.091Z" }, + { url = "https://files.pythonhosted.org/packages/e7/8f/7b2abbade50ed918e59c3dfe14aece0f0f8a4dc43a3884fd4f2508c7787a/pyobjc_framework_quartz-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c8fed57ac8a1927e4fe4f48ab4042cbc1087d881743182b6f3f2e6e227a9209f", size = 218013, upload-time = "2026-06-19T16:16:01.768Z" }, + { url = "https://files.pythonhosted.org/packages/b9/08/527d1ff856e2f2446b5887be01989cc08f9adaf3de7d4eb13d07826c362f/pyobjc_framework_quartz-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60f29408b4f9ed5391a29c6b63e2aa56ddfb8b66b3fb47962930427981e14462", size = 217998, upload-time = "2026-06-19T16:16:02.978Z" }, + { url = "https://files.pythonhosted.org/packages/14/fc/d7c7b3134cdbd1a487f3f77b5be125d87a6c9e7d9411035739d99335cc0c/pyobjc_framework_quartz-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:de9c8cca7e95290c8d540466af11c7cdfe3a5458e6f56c34006d5b45243f9ed9", size = 219000, upload-time = "2026-06-19T16:16:04.29Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4b/861f91a1565d3189ee899e177b915551fb9a7e2ca25414025a8974f04e74/pyobjc_framework_quartz-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:54c9bc7f507192691841ee4eba5bf36990b259df83ac728efed2d7ea1cd021e4", size = 219403, upload-time = "2026-06-19T16:16:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b5/b27010d2f288737f627f74be6d5549f49c841542365c84b9a3011fe39ce7/pyobjc_framework_quartz-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:bfc0d2badd819823d21df8069dcf9544ce360ed747a8895c51bdb25d8d125f45", size = 224458, upload-time = "2026-06-19T16:16:07.252Z" }, + { url = "https://files.pythonhosted.org/packages/8b/5d/85ffd9d433989205d572a50d625c63b29c05e0c5235a725f15ae1023672c/pyobjc_framework_quartz-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ceb56939c337b36d9d81185ade31f77dc52c85cf79bb16e53e9b32f54b6bb3f5", size = 219769, upload-time = "2026-06-19T16:16:08.814Z" }, + { url = "https://files.pythonhosted.org/packages/e2/d6/b917e4b63d72ea84a27121076f3033f23f6497c0e6ce8d304766c899897f/pyobjc_framework_quartz-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:8105c98b798f2bf81c05c54bddeeadbf62f0b5dfec13bd6e719dd2cdf7e1cddf", size = 224717, upload-time = "2026-06-19T16:16:10.215Z" }, + { url = "https://files.pythonhosted.org/packages/04/e2/f3c1ed3228f7430ef5ade23db6f1fcbae99290f177ce5653348fd9e05f4d/pyobjc_framework_quartz-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:bbc214f1a216b5d3651bc832d0ac4589f029f3f37cd6cbb370aac12a7c77942c", size = 219825, upload-time = "2026-06-19T16:16:11.433Z" }, + { url = "https://files.pythonhosted.org/packages/66/2a/2c99a5ad2fe0a11600ea123b8e9a08ff138fcb2ad1e13e376f4bd4aa1d96/pyobjc_framework_quartz-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:ca61624a0b0e6286d8a0f97f47eb9011e4e81e9a339db436d48af527e7065bb1", size = 224770, upload-time = "2026-06-19T16:16:13.035Z" }, ] [[package]] name = "pyobjc-framework-quicklookthumbnailing" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-quartz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/97/1a/b90539500e9a27c2049c388d85a824fc0704009b11e33b05009f52a6dc67/pyobjc_framework_quicklookthumbnailing-12.1.tar.gz", hash = "sha256:4f7e09e873e9bda236dce6e2f238cab571baeb75eca2e0bc0961d5fcd85f3c8f", size = 14790, upload-time = "2025-11-14T10:21:26.442Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/49/41d06038c50a750d6172b875d45ec8a180650b98c6e0d8cdce43b4120ef1/pyobjc_framework_quicklookthumbnailing-12.2.1.tar.gz", hash = "sha256:1b348b674569b8df40ef6acebbcdc4e7e8b347b0a437c824b35a6d6f91acc398", size = 15765, upload-time = "2026-06-19T16:21:31.579Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/22/7bd07b5b44bf8540514a9f24bc46da68812c1fd6c63bb2d3496e5ea44bf0/pyobjc_framework_quicklookthumbnailing-12.1-py2.py3-none-any.whl", hash = "sha256:5efe50b0318188b3a4147681788b47fce64709f6fe0e1b5d020e408ef40ab08e", size = 4234, upload-time = "2025-11-14T10:01:02.209Z" }, + { url = "https://files.pythonhosted.org/packages/e7/1f/1a92542d6b2d879422a20c99124aad3d9e176a9ff65bdf0d1cecb381cbd1/pyobjc_framework_quicklookthumbnailing-12.2.1-py2.py3-none-any.whl", hash = "sha256:747a99db601e0a7ca48fbd32909c803f47211adc194a4c41a5b5430738b3464c", size = 4329, upload-time = "2026-06-19T16:16:15.183Z" }, ] [[package]] name = "pyobjc-framework-replaykit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/35/f8/b92af879734d91c1726227e7a03b9e68ab8d9d2bb1716d1a5c29254087f2/pyobjc_framework_replaykit-12.1.tar.gz", hash = "sha256:95801fd35c329d7302b2541f2754e6574bf36547ab869fbbf41e408dfa07268a", size = 23312, upload-time = "2025-11-14T10:21:29.18Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/a0/2d5903651b581cc9c64cd08d67088310807a169e60465c46d016ac53e2dd/pyobjc_framework_replaykit-12.2.1.tar.gz", hash = "sha256:c5a712ff52ab58c58a53a27e47dba2d3823b13f2587a395855e9c4f0510af6a1", size = 27213, upload-time = "2026-06-19T16:21:32.411Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/de/e8ebcbd80210e8be3b08d6a8404f6b102cb6ebd0c8434daf717f35442958/pyobjc_framework_replaykit-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05c8e4cbda2ff22cd5180bee4a892306a4004127365b15e18335ab39e577faa8", size = 10093, upload-time = "2025-11-14T10:01:04.49Z" }, - { url = "https://files.pythonhosted.org/packages/10/b1/fab264c6a82a78cd050a773c61dec397c5df7e7969eba3c57e17c8964ea3/pyobjc_framework_replaykit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3a2f9da6939d7695fa40de9c560c20948d31b0cc2f892fdd611fc566a6b83606", size = 10090, upload-time = "2025-11-14T10:01:06.321Z" }, - { url = "https://files.pythonhosted.org/packages/6b/fc/c68d2111b2655148d88574959d3d8b21d3a003573013301d4d2a7254c1af/pyobjc_framework_replaykit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b0528c2a6188440fdc2017f0924c0a0f15d0a2f6aa295f1d1c2d6b3894c22f1d", size = 10120, upload-time = "2025-11-14T10:01:08.397Z" }, - { url = "https://files.pythonhosted.org/packages/22/f1/95d3cf08a5b747e15dfb45f4ad23aeae566e75e6c54f3c58caf59b99f4d9/pyobjc_framework_replaykit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:18af5ab59574102978790ce9ccc89fe24be9fa57579f24ed8cfc2b44ea28d839", size = 10141, upload-time = "2025-11-14T10:01:10.366Z" }, - { url = "https://files.pythonhosted.org/packages/4e/78/fac397700f62fdb73161e04affd608678883e9476553fd99e9d65db51f79/pyobjc_framework_replaykit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:31c826a71b76cd7d12c3f30956c202116b0c985a19eb420e91fc1f51bedd2f72", size = 10319, upload-time = "2025-11-14T10:01:12.058Z" }, - { url = "https://files.pythonhosted.org/packages/f7/e7/e3efd189fbaf349962a98db3d63b3ba30fd5f27e249cc933993478421ebc/pyobjc_framework_replaykit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:d6d8046825149f7f2627987a1b48ac7e4c9747a15e263054de0dfde1926a0f42", size = 10194, upload-time = "2025-11-14T10:01:13.754Z" }, - { url = "https://files.pythonhosted.org/packages/2b/52/7564ac0133033853432f3a3abf30fb98f820461c147c904cc8ed6c779d85/pyobjc_framework_replaykit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9f77dc914d5aabcd9273c39777a3372175aa839a3bd7f673a0ead4b7f2cf4211", size = 10383, upload-time = "2025-11-14T10:01:15.673Z" }, + { url = "https://files.pythonhosted.org/packages/b5/63/d4bcb79878b47501d09b7cc76e6367996a6fbdc5360a0259cc19ad6b65d6/pyobjc_framework_replaykit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:80b1bf299ba875e41bb83a44d3d4cbc65628ca6ccd5070bdfacc47b64866b03e", size = 10143, upload-time = "2026-06-19T16:16:16.459Z" }, + { url = "https://files.pythonhosted.org/packages/f7/6f/2319235ab2626251692091ebdfe9838863447014fca25a6e26677fab41a1/pyobjc_framework_replaykit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:07b4c27d67dec0dc68b0a352810d80fd95bd474a940bd0304ac182d39f9df664", size = 10143, upload-time = "2026-06-19T16:16:17.544Z" }, + { url = "https://files.pythonhosted.org/packages/d5/c2/9e1c99f86d7a11925b0009fb9051bbd4f7e2cf512e8bb470131b9c87f79b/pyobjc_framework_replaykit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b682657b641df5a78195a8f8a7abc6be04aa445dfb73814c96a12d3f252fa5d8", size = 10176, upload-time = "2026-06-19T16:16:18.34Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/834b5e916fdcdccde827e2d5bb4137f5ae34b7e225cde1ed845f848f0336/pyobjc_framework_replaykit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:18d5cbdc48436f5f612f37163ab2315518220f88aae39d81c7fd1460da8ad510", size = 10190, upload-time = "2026-06-19T16:16:19.16Z" }, + { url = "https://files.pythonhosted.org/packages/f5/7f/9b55c3b25cea2ee2506980b626256beff02541c978f55caf91f3643bff3f/pyobjc_framework_replaykit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:daaca45e9b94b2cdf6f76ecfdcccb51faf5342cbf8d7a4741ef361406376740e", size = 10368, upload-time = "2026-06-19T16:16:20.08Z" }, + { url = "https://files.pythonhosted.org/packages/ae/4a/00c76c2b2274f96057b5abf8c4de903b2b358dd0d8a0c7a14a6b75cc0ebd/pyobjc_framework_replaykit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:70c68b9394ff0aa4c9bff16ce13fdef81bf778b07d85e6f01d8a39945d7d320c", size = 10243, upload-time = "2026-06-19T16:16:21.216Z" }, + { url = "https://files.pythonhosted.org/packages/9f/f3/ee0da9e4923cc54068acdddec46f440c9627124deca9939cf2d506cb613c/pyobjc_framework_replaykit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:efaa9d6285774d69469b00fac76bed50cd3e300d46388329a67fffb5bbc745d8", size = 10438, upload-time = "2026-06-19T16:16:22.171Z" }, + { url = "https://files.pythonhosted.org/packages/a8/d6/fb3425e69aaa8e6d9c46182220e5ed131d7ce0b11f708fd307779bb36f69/pyobjc_framework_replaykit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:d48c5467c5b71d038b00e04106576741da6a38894b9bbb4e1f5b58f4116b7683", size = 10243, upload-time = "2026-06-19T16:16:23.151Z" }, + { url = "https://files.pythonhosted.org/packages/e5/51/96c871e6fa2e978cf0bc1ec06cf8eefed6db8ec8dc72b0d1469578ce0f82/pyobjc_framework_replaykit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:0ea6641401a45197b6f5397f45009f504e15ce03c719c4455c38def2f915d86f", size = 10434, upload-time = "2026-06-19T16:16:24.103Z" }, ] [[package]] name = "pyobjc-framework-safariservices" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3e/4b/8f896bafbdbfa180a5ba1e21a6f5dc63150c09cba69d85f68708e02866ae/pyobjc_framework_safariservices-12.1.tar.gz", hash = "sha256:6a56f71c1e692bca1f48fe7c40e4c5a41e148b4e3c6cfb185fd80a4d4a951897", size = 25165, upload-time = "2025-11-14T10:21:32.041Z" } +sdist = { url = "https://files.pythonhosted.org/packages/03/8b/638f43f5f7936dc3919fbb7a76a1efb3826941e49df12bbbb4b95342a594/pyobjc_framework_safariservices-12.2.1.tar.gz", hash = "sha256:5da28790b389efa21a33d2d48d3322dc3670077ec9c8af9054824d42153e0298", size = 27306, upload-time = "2026-06-19T16:21:33.237Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/c4/f3076bf070f41712411afaca16c2ef545588521660c8524c1c278e151dec/pyobjc_framework_safariservices-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:571c3c65c30dd492e49d9e561f6ba847e0b847352aeb8db0317c5b9ef84f2c88", size = 7284, upload-time = "2025-11-14T10:01:17.193Z" }, - { url = "https://files.pythonhosted.org/packages/f1/bb/da1059bfad021c417e090058c0a155419b735b4891a7eedc03177b376012/pyobjc_framework_safariservices-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae709cf7a72ac7b95d2f131349f852d5d7a1729a8d760ea3308883f8269a4c37", size = 7281, upload-time = "2025-11-14T10:01:19.294Z" }, - { url = "https://files.pythonhosted.org/packages/67/3a/8c525562fd782c88bc44e8c07fc2c073919f98dead08fffd50f280ef1afa/pyobjc_framework_safariservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b475abc82504fc1c0801096a639562d6a6d37370193e8e4a406de9199a7cea13", size = 7281, upload-time = "2025-11-14T10:01:21.238Z" }, - { url = "https://files.pythonhosted.org/packages/b6/e7/fc984cf2471597e71378b4f82be4a1923855a4c4a56486cc8d97fdaf1694/pyobjc_framework_safariservices-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:592cf5080a9e7f104d6a8d338ebf2523a961f38068f238f11783e86dc105f9c7", size = 7304, upload-time = "2025-11-14T10:01:22.786Z" }, - { url = "https://files.pythonhosted.org/packages/6e/99/3d3062808a64422f39586519d38a52e73304ed60f45500b2c75b97fdd667/pyobjc_framework_safariservices-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:097a2166f79c60633e963913722a087a13b1c5849f3173655b24a8be47039ac4", size = 7308, upload-time = "2025-11-14T10:01:24.299Z" }, - { url = "https://files.pythonhosted.org/packages/99/c3/766dd0e14d61ed05d416bccc4435a977169d5256828ab31ba5939b2f953d/pyobjc_framework_safariservices-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:090afa066820de497d2479a1c5bd4c8ed381eb36a615e4644e12e347ec9d9a3e", size = 7333, upload-time = "2025-11-14T10:01:25.874Z" }, - { url = "https://files.pythonhosted.org/packages/80/8c/93bd8887d83c7f7f6d920495a185f2e4f7d2c41bad7b93652a664913b94d/pyobjc_framework_safariservices-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:3fc553396c51a7fd60c0a2e2b1cdb3fecab135881115adf2f1bbaeb64f801863", size = 7340, upload-time = "2025-11-14T10:01:27.726Z" }, + { url = "https://files.pythonhosted.org/packages/47/9a/63ca70c294e2ab00fe2ba2ed6683e1fc86ee3b301237b5b147faafd73b7b/pyobjc_framework_safariservices-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ed805cebe27ee36efa02ea0b6b348d13f1357109bea09ad57e8e5b286e739280", size = 7337, upload-time = "2026-06-19T16:16:25.163Z" }, + { url = "https://files.pythonhosted.org/packages/a4/30/32ac369c880023b529a617e69b831e912611888349d4b9f768fbf2ebc9d8/pyobjc_framework_safariservices-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:491a02a10332bffd2a081d52e3bb788819d411433ef03103cea144390025f56b", size = 7339, upload-time = "2026-06-19T16:16:26.048Z" }, + { url = "https://files.pythonhosted.org/packages/1f/78/c60149609a5f7dd91e35d43a3317ab9bfcceffba6780254533d42945422f/pyobjc_framework_safariservices-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:46779f1c6a53fe561bafb95379a7bb66535938e622a23b8308f4125f443fa81a", size = 7344, upload-time = "2026-06-19T16:16:26.825Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7b/15dbacb24573d8fbdbc80685c440c5506829c9e36dc48b32d4f515a0190e/pyobjc_framework_safariservices-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:210ae2a93175fff88ccbc6c50a58ff981158a2d6bfe4edaa1880aa2fc5f4ccc4", size = 7362, upload-time = "2026-06-19T16:16:28.023Z" }, + { url = "https://files.pythonhosted.org/packages/58/d0/ee671fef9dfba5fb54cb8f00dd8f80b907ffe39acbba7e4b89d122a7b420/pyobjc_framework_safariservices-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:98dfc05920eff91fa5db9cc64cf764be8b588ae179ecf33985fa7a9f81e044ba", size = 7368, upload-time = "2026-06-19T16:16:29.179Z" }, + { url = "https://files.pythonhosted.org/packages/b5/d7/5edb0bd367c8dfd787359cc989a18e9224cfeef979808f61bad92a20249a/pyobjc_framework_safariservices-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:2dbf52cd5c33e04b65f48891dbb1627612bb23e381d91161dcd53dc76d31e70d", size = 7394, upload-time = "2026-06-19T16:16:29.929Z" }, + { url = "https://files.pythonhosted.org/packages/95/1c/eeab5f53ba9d3901ab7ac045e6e4988a8b3284f2e87f8f3317eb82736136/pyobjc_framework_safariservices-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:05591b89870f2fbe77c57185eec3b21f08583193628e00c88b7173ba35a6c965", size = 7402, upload-time = "2026-06-19T16:16:30.896Z" }, + { url = "https://files.pythonhosted.org/packages/9f/74/f323de081cf336d5889a5ec79611073c3be3f3e0f0819214d7d9a9e59cee/pyobjc_framework_safariservices-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:d852eaa503a9031f5232418b38dc439013eeccd77131e3fddccf9e580be5214a", size = 7411, upload-time = "2026-06-19T16:16:31.745Z" }, + { url = "https://files.pythonhosted.org/packages/cf/ef/c3e91e0829d045240b6ae00664da5346cfa26b98c3d3ac6a3ecd27a21032/pyobjc_framework_safariservices-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:53b7d50145705b7de5e19f44185a7f20f716921be2f7ed2cecc000cd773c16c8", size = 7419, upload-time = "2026-06-19T16:16:32.566Z" }, ] [[package]] name = "pyobjc-framework-safetykit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-quartz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f4/bf/ad6bf60ceb61614c9c9f5758190971e9b90c45b1c7a244e45db64138b6c2/pyobjc_framework_safetykit-12.1.tar.gz", hash = "sha256:0cd4850659fb9b5632fd8ad21f2de6863e8303ff0d51c5cc9c0034aac5db08d8", size = 20086, upload-time = "2025-11-14T10:21:34.212Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/57/ec33de6440eec0c805643dec1115d7a0cc21be8e8e13dfff52e66de6a0e3/pyobjc_framework_safetykit-12.2.1.tar.gz", hash = "sha256:33defe7e4155dff6abf0a2990bb2a918447b9339199ca92c2f3f219d48189ebf", size = 20855, upload-time = "2026-06-19T16:21:34.08Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/00/6682d8b39b5e65188e3c5b560aa3dbd4322f400d2acbaad020edb6cef55c/pyobjc_framework_safetykit-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cbb7bcacc88aab1ab4d8dacedc9569be00e26bb7e761b7759dc4d4a2c2656586", size = 8537, upload-time = "2025-11-14T10:01:29.424Z" }, - { url = "https://files.pythonhosted.org/packages/94/68/77f17fba082de7c65176e0d74aacbce5c9c9066d6d6edcde5a537c8c140a/pyobjc_framework_safetykit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6c63bcd5d571bba149e28c49c8db06073e54e073b08589e94b850b39a43e52b0", size = 8539, upload-time = "2025-11-14T10:01:31.201Z" }, - { url = "https://files.pythonhosted.org/packages/b7/0c/08a20fb7516405186c0fe7299530edd4aa22c24f73290198312447f26c8c/pyobjc_framework_safetykit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e4977f7069a23252053d1a42b1a053aefc19b85c960a5214b05daf3c037a6f16", size = 8550, upload-time = "2025-11-14T10:01:32.885Z" }, - { url = "https://files.pythonhosted.org/packages/02/c5/0e8961e48a2e5942f3f4fad46be5a7b47e17792d89f4c2405b065c1241b5/pyobjc_framework_safetykit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:20170b4869c4ee5485f750ad02bbfcb25c53bbfe86892e5328096dc3c6478b83", size = 8564, upload-time = "2025-11-14T10:01:34.934Z" }, - { url = "https://files.pythonhosted.org/packages/48/3f/fdadc2b992cb3e08269fc75dec3128f8153dd833715b9fbfb975c193c4d2/pyobjc_framework_safetykit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a935c55ae8e731a44c3cb74324da7517634bfc0eca678b6d4b2f9fe04ff53d8", size = 8720, upload-time = "2025-11-14T10:01:36.564Z" }, - { url = "https://files.pythonhosted.org/packages/d9/ec/759117239a3edbd8994069f1f595e4fbc72fa60fa7ebb4aeb4fd47265e7c/pyobjc_framework_safetykit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:1b0e8761fd53e6a83a48dbd93961434b05fe17658478b9001c65627da46ba02b", size = 8616, upload-time = "2025-11-14T10:01:38.616Z" }, - { url = "https://files.pythonhosted.org/packages/43/fd/72e9d6703a0281ffc086b3655c63ca2502ddaff52b3b82e9eb1c9a206493/pyobjc_framework_safetykit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:b3ea88d1de4be84f630e25856abb417f3b19c242038ac061cca85a9a9e3dc61b", size = 8778, upload-time = "2025-11-14T10:01:40.968Z" }, + { url = "https://files.pythonhosted.org/packages/22/e6/211090537981f900bc0d347ecf85a787a587d31832ad26bf74cda49aaa5b/pyobjc_framework_safetykit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:100089568da4e674ef1f4bbd17ca09af29a408ca9642c482052fefa12b7448b0", size = 8593, upload-time = "2026-06-19T16:16:33.459Z" }, + { url = "https://files.pythonhosted.org/packages/e8/76/1291e7ae26ae34c1228b4f9f86fcc85a5171cb5abd6eb937f137db353f74/pyobjc_framework_safetykit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9cee8656c9c2867f4d972efde8dfaf308176fdd881f807b49b616d37a58daf8d", size = 8587, upload-time = "2026-06-19T16:16:34.656Z" }, + { url = "https://files.pythonhosted.org/packages/6a/41/ed6f62913c9b3bf2259de53caffd58758565625e0c04f1d970644452b53a/pyobjc_framework_safetykit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f5e757c9565d104c442b81babcbc4998daf0326b14d928dfae42c5459580a309", size = 8600, upload-time = "2026-06-19T16:16:35.639Z" }, + { url = "https://files.pythonhosted.org/packages/db/08/81833a95ca16ce0379bbbfb8f1524f2d862942457f9f6f05d0c5c1bf1b41/pyobjc_framework_safetykit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:eb272c4f8304fc2c8db9652020b01f2a6026c91fca59072189264d03894293fb", size = 8614, upload-time = "2026-06-19T16:16:36.872Z" }, + { url = "https://files.pythonhosted.org/packages/8f/9d/6ff0294bfe087f428dd28861da0953c8bc7d0f540caf8cc4e724bc8550dd/pyobjc_framework_safetykit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0122066ee790375e97ef888af8134c13f00c3b890d44769c6c74f3b126438506", size = 8775, upload-time = "2026-06-19T16:16:37.789Z" }, + { url = "https://files.pythonhosted.org/packages/d0/db/1563ae0f10f4f07a22298383c6e176e443cb9666ab57c27798a8738b0b03/pyobjc_framework_safetykit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:052e4ca43b48856e32876c433c38590e6e668bd72bdfc820772b76188bad80e5", size = 8665, upload-time = "2026-06-19T16:16:38.936Z" }, + { url = "https://files.pythonhosted.org/packages/bc/a9/5f74b8158bb7ae337f0d23e07303988120abf2c669bfdb4eaaff665f8436/pyobjc_framework_safetykit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:7f048af0941796ef80627b88c1411b376ff9c0217ebac402422e1f3fe3a0b644", size = 8832, upload-time = "2026-06-19T16:16:39.942Z" }, + { url = "https://files.pythonhosted.org/packages/21/94/b697512ea9e57ced080e2201cfe289dcd1c18e9975a1eb35353425f89639/pyobjc_framework_safetykit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:152d800a3b3755f45330582158e198edd6809be026b6e78aa1a858095edd3f69", size = 8666, upload-time = "2026-06-19T16:16:40.857Z" }, + { url = "https://files.pythonhosted.org/packages/e0/b1/c436e56d692afca134f641c4818f230e147c4bd16e3aee14fb9c6ac706fb/pyobjc_framework_safetykit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:dc1e4b65e87d6501c571752550d248853918f4141faf1d3c9e6dc41aca1b0862", size = 8822, upload-time = "2026-06-19T16:16:41.68Z" }, ] [[package]] name = "pyobjc-framework-scenekit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-quartz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/94/8c/1f4005cf0cb68f84dd98b93bbc0974ee7851bb33d976791c85e042dc2278/pyobjc_framework_scenekit-12.1.tar.gz", hash = "sha256:1bd5b866f31fd829f26feac52e807ed942254fd248115c7c742cfad41d949426", size = 101212, upload-time = "2025-11-14T10:21:41.265Z" } +sdist = { url = "https://files.pythonhosted.org/packages/26/4f/4c614327db5c8a9af6fd8995bacd5f4d3c331c1be66f29722bac3af594cb/pyobjc_framework_scenekit-12.2.1.tar.gz", hash = "sha256:9f5939ecdfa9c13347f6ab61173ba5eb386766cfebd82200fe7173f70aa34083", size = 132003, upload-time = "2026-06-19T16:21:35.115Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/47/2aac42526e55f490855db6bddba25edbf1764e175437d60235860856b92a/pyobjc_framework_scenekit-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:269760fa2ab44df11be1a7898907d2f01eb05d1d98a8997ae876ed49803be75b", size = 33539, upload-time = "2025-11-14T10:01:44.05Z" }, - { url = "https://files.pythonhosted.org/packages/a0/7f/eda261013dc41cc70f3157d1a750712dc29b64fc05be84232006b5cd57e5/pyobjc_framework_scenekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:01bf1336a7a8bdc96fabde8f3506aa7a7d1905e20a5c46030a57daf0ce2cbd16", size = 33542, upload-time = "2025-11-14T10:01:47.613Z" }, - { url = "https://files.pythonhosted.org/packages/d2/f1/4986bd96e0ba0f60bff482a6b135b9d6db65d56578d535751f18f88190f0/pyobjc_framework_scenekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:40aea10098893f0b06191f1e79d7b25e12e36a9265549d324238bdb25c7e6df0", size = 33597, upload-time = "2025-11-14T10:01:51.297Z" }, - { url = "https://files.pythonhosted.org/packages/4a/82/c728a025fd09cd259870d43b68ce8e7cffb639112033693ffa02d3d1eac0/pyobjc_framework_scenekit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a032377a7374320131768b6c8bf84589e45819d9e0fe187bd3f8d985207016b9", size = 33623, upload-time = "2025-11-14T10:01:54.878Z" }, - { url = "https://files.pythonhosted.org/packages/5e/ef/9cea4cc4ac7f43fa6fb60d0690d25b2da1d8e1cf42266316014d1bb43a11/pyobjc_framework_scenekit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:633909adff9b505b49c34307f507f4bd926b88a1482d8143655d5703481cbbf5", size = 33934, upload-time = "2025-11-14T10:01:57.994Z" }, - { url = "https://files.pythonhosted.org/packages/5a/0c/eb436dda11b6f950bff7f7d9af108970058f2fa9822a946a6982d74a64f8/pyobjc_framework_scenekit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:d4c8512c9186f12602ac19558072cdeec3a607d628c269317d5965341a14372c", size = 33728, upload-time = "2025-11-14T10:02:01.639Z" }, - { url = "https://files.pythonhosted.org/packages/52/20/2adb296dd6ac1619bf4e2e8a878be7e13b8ed362d9d649c88734998a5cf7/pyobjc_framework_scenekit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:b99a99edf37c8fe4194a9c0ab2092f57e564e07adb1ad54ef82b7213184be668", size = 34009, upload-time = "2025-11-14T10:02:05.107Z" }, + { url = "https://files.pythonhosted.org/packages/80/97/57bf56bb304000c1d1bdba7f55f1d6af897ff91b32a5c539f220ca2ce931/pyobjc_framework_scenekit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c2d9e91b9d7cf4ca61f56188dba88a2c0dee7e5137fda34a9c0f94cf9494f3a0", size = 34757, upload-time = "2026-06-19T16:16:42.512Z" }, + { url = "https://files.pythonhosted.org/packages/f0/27/ac8abf8c42aac47cf948dfec1746aad5e6b1110f2c11b6c45a82611b7f47/pyobjc_framework_scenekit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:41d1c000f2cce780fdc18419aad0b1cdad50f49317b38dedf9219ce5bcc5a659", size = 34761, upload-time = "2026-06-19T16:16:43.637Z" }, + { url = "https://files.pythonhosted.org/packages/74/99/4c880fe6fa3bfcf980566b8d6b1e608a586d97f43bbeaf658a2889cc1ef8/pyobjc_framework_scenekit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a79aabfbbaba44098e5cfaec2f87ae3d04a31f1e250283dcf7ab08cadceed1ab", size = 34816, upload-time = "2026-06-19T16:16:44.56Z" }, + { url = "https://files.pythonhosted.org/packages/8b/bd/e87bc4fce0ddc9806f31eedeffb5c8b9b309888026faf3ca4d1ae7414ee9/pyobjc_framework_scenekit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6de30a01c643892bc1fa5f3254c17351ec9c72c7ab9af9c895290e8191dc057d", size = 34842, upload-time = "2026-06-19T16:16:45.406Z" }, + { url = "https://files.pythonhosted.org/packages/b0/2e/abf47653356f62712729828ee06a9bc1ee8c67b1e4bba96a37f0cfae39b0/pyobjc_framework_scenekit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6cb7213c8f9c5d3a111b3850082f545001fbcea6173606e0986354350c83cbaa", size = 35158, upload-time = "2026-06-19T16:16:46.298Z" }, + { url = "https://files.pythonhosted.org/packages/43/8b/bd6d503b2d1b9809aa601424f9428bbc5814d19627e9bef24f9dd8d6669f/pyobjc_framework_scenekit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fd6c2b0b56a2487c7ea8f5e18cedeeef04e3c28b8fdd54917e8b8df45306d0c6", size = 34952, upload-time = "2026-06-19T16:16:47.176Z" }, + { url = "https://files.pythonhosted.org/packages/08/11/15cef017994c8ada57aff1ef855cbd1916c1fd40725621e48cd7235a8ce8/pyobjc_framework_scenekit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9e840a4b48bae1a135cbdce82f81ce1187ffd23e7eb2ffdfc84a2db5cb758fe2", size = 35241, upload-time = "2026-06-19T16:16:48.088Z" }, + { url = "https://files.pythonhosted.org/packages/a1/d9/4fd8bf2a03d37a1f5bd3a2d2e897405033339ae4bdd1524cfcacd8cfe7e9/pyobjc_framework_scenekit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:80b5768e346e5e2f9489a8851c0e867b4a8258092c2574e892993825763d3b4c", size = 34988, upload-time = "2026-06-19T16:16:49.044Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c6/9f70347af44836cc5e847be9597ce05c8a5f0a1423b58683d8f3567d4e96/pyobjc_framework_scenekit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:cc7769ebcfa1cc17f3046220d2a06927d2429a5f40ccf66bcdfe2c23868ca8ed", size = 35276, upload-time = "2026-06-19T16:16:50.09Z" }, ] [[package]] name = "pyobjc-framework-screencapturekit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-coremedia" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2d/7f/73458db1361d2cb408f43821a1e3819318a0f81885f833d78d93bdc698e0/pyobjc_framework_screencapturekit-12.1.tar.gz", hash = "sha256:50992c6128b35ab45d9e336f0993ddd112f58b8c8c8f0892a9cb42d61bd1f4c9", size = 32573, upload-time = "2025-11-14T10:21:44.497Z" } +sdist = { url = "https://files.pythonhosted.org/packages/64/a6/b7e72e32d3334e13eae592cbcd9f3c060c43adf78ddfebd0eb9c6be0ab05/pyobjc_framework_screencapturekit-12.2.1.tar.gz", hash = "sha256:e419cbf9c2f9cbd172d1c6e5bc69a44e0a7d9e45cf43058d48eeda4f785ce860", size = 37840, upload-time = "2026-06-19T16:21:36.099Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/01/df/5c2eee34ef88989da39830e83074028922a9150d601539217fd7ac6d3c06/pyobjc_framework_screencapturekit-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cf13285180e9acf8a6d0eff494dd7fb63296c648d4838f628c67be72b1af4725", size = 11474, upload-time = "2025-11-14T10:02:07.253Z" }, - { url = "https://files.pythonhosted.org/packages/79/92/fe66408f4bd74f6b6da75977d534a7091efe988301d13da4f009bf54ab71/pyobjc_framework_screencapturekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae412d397eedf189e763defe3497fcb8dffa5e0b54f62390cb33bf9b1cfb864a", size = 11473, upload-time = "2025-11-14T10:02:09.177Z" }, - { url = "https://files.pythonhosted.org/packages/05/a8/533acdbf26e0a908ff640d3a445481f3c948682ca887be6711b5fcf82682/pyobjc_framework_screencapturekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:27df138ce2dfa9d4aae5106d4877e9ed694b5a174643c058f1c48678ffc7001a", size = 11504, upload-time = "2025-11-14T10:02:11.36Z" }, - { url = "https://files.pythonhosted.org/packages/45/f9/ff713b8c4659f9ef1c4dbb8ca4b59c4b22d9df48471230979d620709e3b4/pyobjc_framework_screencapturekit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:168125388fb35c6909bec93b259508156e89b9e30fec5748d4a04fd0157f0e0d", size = 11523, upload-time = "2025-11-14T10:02:13.494Z" }, - { url = "https://files.pythonhosted.org/packages/f0/26/8bf1bacdb2892cf26d043c7f6e8788a613bbb2ccb313a5ea0634612cfc24/pyobjc_framework_screencapturekit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4fc2fe72c1da5ac1b8898a7b2082ed69803e6d9c11f414bb5a5ec94422a5f74f", size = 11701, upload-time = "2025-11-14T10:02:15.634Z" }, - { url = "https://files.pythonhosted.org/packages/be/b4/881e2ff0e11e7d705716f01f1bfd10232f7d21bda38d630c3fbe409b13a9/pyobjc_framework_screencapturekit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:be210ea5df36c1392425c026c59c5e0797b0d6e07ee9551d032e40bed95d2833", size = 11581, upload-time = "2025-11-14T10:02:17.467Z" }, - { url = "https://files.pythonhosted.org/packages/24/d0/69f295412d5dfacb6e6890ee128b9c80c8f4f584c20842c576ee154bfc0b/pyobjc_framework_screencapturekit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:534f3a433edf6417c3dd58ac52a69360e5a19c924d1cb389495c4d6cc13a875d", size = 11783, upload-time = "2025-11-14T10:02:19.257Z" }, + { url = "https://files.pythonhosted.org/packages/2b/45/cee44902d207ce51966712c7011ee273634ca059909c0132ad05791241d2/pyobjc_framework_screencapturekit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:18b6770aec984fb129fb1aa10d5d05cc43014b2a475f11302cb0df90a2910b27", size = 11566, upload-time = "2026-06-19T16:16:51Z" }, + { url = "https://files.pythonhosted.org/packages/f4/4e/020fdc67539a3b5879075b51ae052c97f9ce6ba4aaa4d37bc9be4d9fdf05/pyobjc_framework_screencapturekit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d9be9def5875e800581dade26b45ef66c735c12ceb4a17c5457bebac159903d9", size = 11566, upload-time = "2026-06-19T16:16:51.925Z" }, + { url = "https://files.pythonhosted.org/packages/55/eb/76a25a68695f8502ca17ad0f482ed3e8d1714a7fdaed86728388778510c7/pyobjc_framework_screencapturekit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:407dfcd80e493d11ce54163d4cc973119f6a507cd695875f4cdbfde6f2db42d8", size = 11596, upload-time = "2026-06-19T16:16:52.953Z" }, + { url = "https://files.pythonhosted.org/packages/f2/db/c65ea6d42f9873ee6a1d63d4db1db334d60536ba679382688647bd626cc0/pyobjc_framework_screencapturekit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d23bc1d0ac0068f328a97c1ed95d611cb2b8517c76345ddbbd21e5e8bc85a898", size = 11616, upload-time = "2026-06-19T16:16:53.772Z" }, + { url = "https://files.pythonhosted.org/packages/40/8f/b57ec1a7632d7d49f01fc4b322c2c81fe62757c5ad78bd19f0fe085b6aed/pyobjc_framework_screencapturekit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:3269ee9ecc1cde271cffb1e763672469b70375fb6aff00394a0387cc622070ae", size = 11794, upload-time = "2026-06-19T16:16:54.604Z" }, + { url = "https://files.pythonhosted.org/packages/30/1a/efebaf2d5f48ecbd231b2b79df2be4c25a7ebe1804a7c36c3902acd81457/pyobjc_framework_screencapturekit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fd1cc27066b907726389bde47aca1afd27c2af13c4eb88b40f3b813082987e01", size = 11673, upload-time = "2026-06-19T16:16:55.347Z" }, + { url = "https://files.pythonhosted.org/packages/4a/95/55ce785f8e619d4e953b3672ccb5b81b16b11c48509dbe38689426022b8a/pyobjc_framework_screencapturekit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9d4493a95a5eca7b8d5287fda6b2614a1d62def22b24f01eb743fc371c018ae6", size = 11876, upload-time = "2026-06-19T16:16:56.285Z" }, + { url = "https://files.pythonhosted.org/packages/72/11/f010d9fcb42fad7251a84b061f79b0347c23ee19789b28ac137373bf3579/pyobjc_framework_screencapturekit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:9f6041f81a7a9b4da29fb4ab1cd6912447ccf557cfdea294db3754898895d6b2", size = 11669, upload-time = "2026-06-19T16:16:57.114Z" }, + { url = "https://files.pythonhosted.org/packages/50/2f/720037603723ffeeb5ffbc1be47af1b1253647493ca245f01d457823b021/pyobjc_framework_screencapturekit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:075f1faf3fb6239b104b2ef6f78f9bf5b0bac062aa48c560c9abfeb629282196", size = 11869, upload-time = "2026-06-19T16:16:57.89Z" }, ] [[package]] name = "pyobjc-framework-screensaver" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/99/7cfbce880cea61253a44eed594dce66c2b2fbf29e37eaedcd40cffa949e9/pyobjc_framework_screensaver-12.1.tar.gz", hash = "sha256:c4ca111317c5a3883b7eace0a9e7dd72bc6ffaa2ca954bdec918c3ab7c65c96f", size = 22229, upload-time = "2025-11-14T10:21:47.299Z" } +sdist = { url = "https://files.pythonhosted.org/packages/88/32/a3826be36a6473c6bed8f3a7cbd879b8feadfd83463cb1ff615aba66e414/pyobjc_framework_screensaver-12.2.1.tar.gz", hash = "sha256:15eba02075a065283e763c8087b9c6fa565907c95e0575a383c1a6ef4c9b1868", size = 22814, upload-time = "2026-06-19T16:21:36.819Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/bc/3a0d0d3abda32e2f7dbad781b100e01f6fe2d40afc298d6d076478895bcb/pyobjc_framework_screensaver-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:58625f7d19d73b74521570ddd5b49bf5eeaf32bac6f2c39452594f020dda9b85", size = 8482, upload-time = "2025-11-14T10:02:20.94Z" }, - { url = "https://files.pythonhosted.org/packages/2d/8d/87ca0fa0a9eda3097a0f4f2eef1544bf1d984697939fbef7cda7495fddb9/pyobjc_framework_screensaver-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5bd10809005fbe0d68fe651f32a393ce059e90da38e74b6b3cd055ed5b23eaa9", size = 8480, upload-time = "2025-11-14T10:02:22.798Z" }, - { url = "https://files.pythonhosted.org/packages/5a/a4/2481711f2e9557b90bac74fa8bf821162cf7b65835732ae560fd52e9037e/pyobjc_framework_screensaver-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a3c90c2299eac6d01add81427ae2f90d7724f15d676261e838d7a7750f812322", size = 8422, upload-time = "2025-11-14T10:02:24.49Z" }, - { url = "https://files.pythonhosted.org/packages/7e/8a/2e0cb958e872896b67ae6d5877070867f4a845ea1010984ff887ad418396/pyobjc_framework_screensaver-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2a865b6dbb39fb92cdb67b13f68d594ab84d08a984cc3e9a39fab3386f431649", size = 8442, upload-time = "2025-11-14T10:02:26.135Z" }, - { url = "https://files.pythonhosted.org/packages/35/45/3eb9984119be3dcd90f4628ecc3964c1a394b702a71034af6d932f98de3a/pyobjc_framework_screensaver-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c249dffcb95d55fc6be626bf17f70b477e320c33d94e234597bc0074e302cfcd", size = 8450, upload-time = "2025-11-14T10:02:27.782Z" }, - { url = "https://files.pythonhosted.org/packages/c6/97/2fab7dfb449ccc49fb617ade97bfa35689572c71fff5885ea25705479a30/pyobjc_framework_screensaver-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:4744a01043a9c6b464f6a2230948812bf88bdd68f084b6f05b475b93093c3ea9", size = 8477, upload-time = "2025-11-14T10:02:29.424Z" }, - { url = "https://files.pythonhosted.org/packages/59/e1/605137cc679dbeddc08470397d05dfd7c20e4c626924d33030c3aa45c39a/pyobjc_framework_screensaver-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:c02ec9dccf49463056a438b7f8a6374dc2416d4a0672003382d50603aed9ab5d", size = 8501, upload-time = "2025-11-14T10:02:31.09Z" }, + { url = "https://files.pythonhosted.org/packages/fd/4e/ce5f92767acee59c2d9b50cca7c7994694de7a13e72ce53ea1770f771686/pyobjc_framework_screensaver-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0c14ff3556e67ef4cb0f665ec9a5fe0d17cb39d112938dd105e18ba98caef40f", size = 8567, upload-time = "2026-06-19T16:16:58.682Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c9/a2ad7ca6ad84d8379e1604718cd63205454d60f0978ad1aef5b585fad6f0/pyobjc_framework_screensaver-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be8df2ca5d4765d2d64f0c130f0a1d9a748c6d965e8a0b45fed2ff12681682aa", size = 8571, upload-time = "2026-06-19T16:16:59.74Z" }, + { url = "https://files.pythonhosted.org/packages/d6/3b/126bc97a45a4bc359e26e567da2518c6b891e842c1061ed1f942a1341b28/pyobjc_framework_screensaver-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8f43926bc686453af7798f460144beaaaea09ffc296bf20d1b38c9e420f94b4d", size = 8492, upload-time = "2026-06-19T16:17:00.568Z" }, + { url = "https://files.pythonhosted.org/packages/fb/06/33c369a75297e3b86ae977d473d24d2e141fe0e5c6663ab829b9e5960509/pyobjc_framework_screensaver-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7535dec51e71d41bbbf2100f49b26f8539e34fb34ec9943d7f075b8ebd46b26b", size = 8512, upload-time = "2026-06-19T16:17:01.332Z" }, + { url = "https://files.pythonhosted.org/packages/c6/f9/2617d0713f2b49a7acd2977f6f0b7f1cf7eb57e0ed6666b56a7e7bd1d5f6/pyobjc_framework_screensaver-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:3fe3232d9aebf919ca2413c373f96915e55b904a1ad362091e79d142e634841f", size = 8519, upload-time = "2026-06-19T16:17:02.142Z" }, + { url = "https://files.pythonhosted.org/packages/a2/b0/7651f7bf92c24a218afa69ff7dbb515b6354e9386a21957c619c5f7bd471/pyobjc_framework_screensaver-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:367b252aefba2856d51d6b7e899770a8c4fff5bee6cc5487eab03f317e1b5383", size = 8556, upload-time = "2026-06-19T16:17:02.898Z" }, + { url = "https://files.pythonhosted.org/packages/ae/06/7235f5c2f6aed47b61c642341444d531db54baa1fe49ac02cc1169c8aec2/pyobjc_framework_screensaver-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e0311a13bc1408c1fc9d62e2f905aa5809ecbdee67fa8e5381c86364e012163f", size = 8573, upload-time = "2026-06-19T16:17:03.717Z" }, + { url = "https://files.pythonhosted.org/packages/0e/37/d73f9f3c156c87b1c2643e446ccc7bfbb2467d48c06324935840c545d038/pyobjc_framework_screensaver-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:8f9769b5e5384bc812c87aa4346766f34408ec8f5ae94e0c8e14202a71f90f7f", size = 8588, upload-time = "2026-06-19T16:17:04.525Z" }, + { url = "https://files.pythonhosted.org/packages/8e/fa/adf2f598fbfd5073a3e3c45221e5c7bf6587e45b4a30194531c1f539c4ac/pyobjc_framework_screensaver-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:45c2943314245929e306bd38a8ff92dbb18a6a7c5326b0e10004555fc1f86a00", size = 8590, upload-time = "2026-06-19T16:17:05.501Z" }, ] [[package]] name = "pyobjc-framework-screentime" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/10/11/ba18f905321895715dac3cae2071c2789745ae13605b283b8114b41e0459/pyobjc_framework_screentime-12.1.tar.gz", hash = "sha256:583de46b365543bbbcf27cd70eedd375d397441d64a2cf43c65286fd9c91af55", size = 13413, upload-time = "2025-11-14T10:21:49.17Z" } +sdist = { url = "https://files.pythonhosted.org/packages/41/93/27470ca21f4c596a0692a3e9cfcd45d38c3bab6f0566bbb5af4e3cfb8b98/pyobjc_framework_screentime-12.2.1.tar.gz", hash = "sha256:c97e700995c03183a1a73f2eaaf90f5ed66d68e8c2e40ff7ed2fddbc77ff7b2f", size = 14074, upload-time = "2026-06-19T16:21:37.618Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/06/904174de6170e11b53673cc5844e5f13394eeeed486e0bcdf5288c1b0853/pyobjc_framework_screentime-12.1-py2.py3-none-any.whl", hash = "sha256:d34a068ec8ba2704987fcd05c37c9a9392de61d92933e6e71c8e4eaa4dfce029", size = 3963, upload-time = "2025-11-14T10:02:32.577Z" }, + { url = "https://files.pythonhosted.org/packages/43/75/d434c86454bf4c21855b69f145799699a2d1650067575c4eeeccc6738ff0/pyobjc_framework_screentime-12.2.1-py2.py3-none-any.whl", hash = "sha256:b252234f5f1e01d6f3dbfde44a6019169100c2337f24b65694ffdd6ddfaa3c4d", size = 4002, upload-time = "2026-06-19T16:17:06.29Z" }, ] [[package]] name = "pyobjc-framework-scriptingbridge" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0c/cb/adc0a09e8c4755c2281bd12803a87f36e0832a8fc853a2d663433dbb72ce/pyobjc_framework_scriptingbridge-12.1.tar.gz", hash = "sha256:0e90f866a7e6a8aeaf723d04c826657dd528c8c1b91e7a605f8bb947c74ad082", size = 20339, upload-time = "2025-11-14T10:21:51.769Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/97/908c6a8a4eb665c952dd8b6c670eb2ad40661c31721ed47470d1329115b3/pyobjc_framework_scriptingbridge-12.2.1.tar.gz", hash = "sha256:779b2238b33b61fb9ab4fc71d080e42ef7e27562506f5ca9d783effc4c769a5e", size = 21226, upload-time = "2026-06-19T16:21:38.347Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/df/18a894d0d720d370bf5351555ba18e48e1ab8153cb756a5d945c1c3d8637/pyobjc_framework_scriptingbridge-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:97acd79168892ba457bc472214851f4e4a2d40a8aae106fb07cc94417e1fc681", size = 8334, upload-time = "2025-11-14T10:02:34.478Z" }, - { url = "https://files.pythonhosted.org/packages/42/de/0943ee8d7f1a7d8467df6e2ea017a6d5041caff2fb0283f37fea4c4ce370/pyobjc_framework_scriptingbridge-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e6e37e69760d6ac9d813decf135d107760d33e1cdf7335016522235607f6f31b", size = 8335, upload-time = "2025-11-14T10:02:36.654Z" }, - { url = "https://files.pythonhosted.org/packages/51/46/e0b07d2b3ff9effb8b1179a6cc681a953d3dfbf0eb8b1d6a0e54cef2e922/pyobjc_framework_scriptingbridge-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8083cd68c559c55a3787b2e74fc983c8665e5078571475aaeabf4f34add36b62", size = 8356, upload-time = "2025-11-14T10:02:38.559Z" }, - { url = "https://files.pythonhosted.org/packages/1a/da/b11568f21924a994aa59272e2752e742f8380ab2cf88d111326ba7baede0/pyobjc_framework_scriptingbridge-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bddbd3a13bfaeaa38ab66e44f10446d5bc7d1110dbc02e59b80bcd9c3a60548a", size = 8371, upload-time = "2025-11-14T10:02:40.603Z" }, - { url = "https://files.pythonhosted.org/packages/77/eb/9bc3e6e9611d757fc80b4423cc28128750a72eae8241be8ae43e1d76c4cd/pyobjc_framework_scriptingbridge-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:148191010b4e10c3938cdb2dcecad43fa0884cefb5a78499a21bdaf5a78318b3", size = 8526, upload-time = "2025-11-14T10:02:42.298Z" }, - { url = "https://files.pythonhosted.org/packages/b1/bc/5f1d372bb1efa9cf1e3218e1831136f5548b9f5b12a4a6676bf8b37cca63/pyobjc_framework_scriptingbridge-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:48f4bc33b2cab6634f58f37549096bda9ec7d3ec664b4b40e7d3248d9f481f69", size = 8406, upload-time = "2025-11-14T10:02:43.979Z" }, - { url = "https://files.pythonhosted.org/packages/42/c2/c223ac13c69e99787301ad8e4be32fc192e067e4e2798e0e5cceabf1abbe/pyobjc_framework_scriptingbridge-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:81bf8b19cd7fd1db055530007bc724901fd61160823324ec2df0daa8e25b94f7", size = 8564, upload-time = "2025-11-14T10:02:45.629Z" }, + { url = "https://files.pythonhosted.org/packages/8c/13/73043b8df680a48c3c2fd4a3a327347b588254d820193c3240496d404f51/pyobjc_framework_scriptingbridge-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cbdd7fa533c9046b0040d2c56a7e9f05fdfb0713365a7157e1e5b5d2bf201eb8", size = 8369, upload-time = "2026-06-19T16:17:07.182Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ca/27d6e825fb9ff1069ef94e8770d0851e3bce654b507c79c7dde39f81538e/pyobjc_framework_scriptingbridge-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fbf6c59d27ee1b03ae14cdc3fb7330c2d5cc1a2cebbc438744550131b3e6f168", size = 8370, upload-time = "2026-06-19T16:17:08.124Z" }, + { url = "https://files.pythonhosted.org/packages/c8/aa/21a22afd311b14ea317c09aa6b238232f8d45e7795005f032c97bb9816db/pyobjc_framework_scriptingbridge-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:89153d5bbf44b2ab5a63b646712ecb86b1a64606c3082934ec5c6be4c8a192b4", size = 8381, upload-time = "2026-06-19T16:17:08.888Z" }, + { url = "https://files.pythonhosted.org/packages/15/3a/7e83276e69d5dca85597dbb5129fafd1f453bcd9c44f9ef1996292cc1fbe/pyobjc_framework_scriptingbridge-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:093ae30d045bd5348bed5fb34c826565d0d385305fb75159b46fc2bc1d7d226d", size = 8402, upload-time = "2026-06-19T16:17:09.701Z" }, + { url = "https://files.pythonhosted.org/packages/1c/44/64b578077c04d505849bed5ce06af49ebb3a8cb69fd4d2eb0b97b0c9243a/pyobjc_framework_scriptingbridge-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:2b620a2c3eccc446c21be0d93a343cdf709b1c300ccc8e83a4c0a7ba0c30e90b", size = 8551, upload-time = "2026-06-19T16:17:10.564Z" }, + { url = "https://files.pythonhosted.org/packages/71/36/9257cef0ea8739fbcac8e21ee9bdaedf06632e218f3de09724498126b296/pyobjc_framework_scriptingbridge-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:57ed9cb870ed86a7a8bd42f1b418bbfeb5886c09a448429f190ea4bedd18bd38", size = 8441, upload-time = "2026-06-19T16:17:11.384Z" }, + { url = "https://files.pythonhosted.org/packages/da/38/48908ac4804d8f324c8cffd9027744e231ff8ca97c51e63f270b202d8e4f/pyobjc_framework_scriptingbridge-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a0899b94bb908ac9bd0dcf1feaea545d1477ce7bcd6d6665e7dbb551a75c7339", size = 8592, upload-time = "2026-06-19T16:17:12.202Z" }, + { url = "https://files.pythonhosted.org/packages/48/32/fc95c045f310d9d2bdf200cba3ad83baf9acaa373e1211d40fb6b5c849d9/pyobjc_framework_scriptingbridge-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:526bc671d9a3a401d3d310376f05503ae7f1381eb7ef1a274af90a8b6514e209", size = 8437, upload-time = "2026-06-19T16:17:12.991Z" }, + { url = "https://files.pythonhosted.org/packages/b6/26/49d0912123fa7744cd2a866b50197500676e2d4d5e9d190434739e7bdbed/pyobjc_framework_scriptingbridge-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:776afb47d013841b66c28c065a80863918b1530420db62db8e852247b2e0148d", size = 8593, upload-time = "2026-06-19T16:17:13.875Z" }, ] [[package]] name = "pyobjc-framework-searchkit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-coreservices" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6e/60/a38523198430e14fdef21ebe62a93c43aedd08f1f3a07ea3d96d9997db5d/pyobjc_framework_searchkit-12.1.tar.gz", hash = "sha256:ddd94131dabbbc2d7c3f17db3da87c1a712c431310eef16f07187771e7e85226", size = 30942, upload-time = "2025-11-14T10:21:55.483Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/53/be9909e2c3672242d5a4f8223a5132d39f3ae9e9b82062e214ddc4db828b/pyobjc_framework_searchkit-12.2.1.tar.gz", hash = "sha256:1fe1ceb2db1d8c86f75484dd9f88ac39dd3d2cffba250498ba0e2312435214cf", size = 31141, upload-time = "2026-06-19T16:21:39.275Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/72/46/4f9cd3011f47b43b21b2924ab3770303c3f0a4d16f05550d38c5fcb42e78/pyobjc_framework_searchkit-12.1-py2.py3-none-any.whl", hash = "sha256:844ce62b7296b19da8db7dedd539d07f7b3fb3bb8b029c261f7bcf0e01a97758", size = 3733, upload-time = "2025-11-14T10:02:47.026Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d2/10e5fc076f236f96fefea233cd31eb15374c393bd8d737cb41b49ac746a1/pyobjc_framework_searchkit-12.2.1-py2.py3-none-any.whl", hash = "sha256:936e41880d48da6742128bc900de37d14771e718087719589d589e858aa2ea60", size = 3760, upload-time = "2026-06-19T16:17:14.683Z" }, ] [[package]] name = "pyobjc-framework-security" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/80/aa/796e09a3e3d5cee32ebeebb7dcf421b48ea86e28c387924608a05e3f668b/pyobjc_framework_security-12.1.tar.gz", hash = "sha256:7fecb982bd2f7c4354513faf90ba4c53c190b7e88167984c2d0da99741de6da9", size = 168044, upload-time = "2025-11-14T10:22:06.334Z" } +sdist = { url = "https://files.pythonhosted.org/packages/44/b8/4267b802d8dba6de468e7d0765b05cc4e146fa376ed9f55e0b6461016bef/pyobjc_framework_security-12.2.1.tar.gz", hash = "sha256:d7831b1537f4346892e7f2f0e2b09d79bee98919b0767f4061278d0e03028f2d", size = 181065, upload-time = "2026-06-19T16:21:40.151Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/67/31928b689b72a932c80e35662430355de09163bec8ee334f0994d16c4036/pyobjc_framework_security-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:787e9d873535247e2caca2036cbcdc956bcc92d0c06044bec7eefe0a456856b0", size = 41288, upload-time = "2025-11-14T10:02:50.693Z" }, - { url = "https://files.pythonhosted.org/packages/5e/3d/8d3a39cd292d7c76ab76233498189bc7170fc80f573b415308464f68c7ee/pyobjc_framework_security-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b2d8819f0fb7b619ec7627a0d8c1cac1a57c5143579ce8ac21548165680684b", size = 41287, upload-time = "2025-11-14T10:02:54.491Z" }, - { url = "https://files.pythonhosted.org/packages/76/66/5160c0f938fc0515fe8d9af146aac1b093f7ef285ce797fedae161b6c0e8/pyobjc_framework_security-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab42e55f5b782332be5442750fcd9637ee33247d57c7b1d5801bc0e24ee13278", size = 41280, upload-time = "2025-11-14T10:02:58.097Z" }, - { url = "https://files.pythonhosted.org/packages/32/48/b294ed75247c5cfa00d51925a10237337d24f54961d49a179b20a4307642/pyobjc_framework_security-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:afc36661cc6eb98cd794bed1d6668791e96557d6f72d9ac70aa49022d26af1d4", size = 41284, upload-time = "2025-11-14T10:03:01.722Z" }, - { url = "https://files.pythonhosted.org/packages/ef/57/0d3ef78779cf5c3bba878b2f824137e50978ad4a21dabe65d8b5ae0fc0d1/pyobjc_framework_security-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9510c98ab56921d1d416437372605cc1c1f6c1ad8d3061ee56b17bf423dd5427", size = 42162, upload-time = "2025-11-14T10:03:05.337Z" }, - { url = "https://files.pythonhosted.org/packages/66/4d/63c15f9449c191e7448a05ff8af4a82c39a51bb627bc96dc9697586c0f79/pyobjc_framework_security-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6319a34508fd87ab6ca3cda6f54e707196197a65b792b292705af967e225438a", size = 41348, upload-time = "2025-11-14T10:03:08.926Z" }, - { url = "https://files.pythonhosted.org/packages/1a/d8/5aaa2a8124ed04a9d6ca7053dc0fa64e42be51497ed8263a24b744a95598/pyobjc_framework_security-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:03d166371cefdef24908825148eb848f99ee2c0b865870a09dcbb94334dd3e0a", size = 42908, upload-time = "2025-11-14T10:03:13.01Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6d/5de5ba240d815ec0292db63f733b485f59ee7fd683375786c7b940ba7984/pyobjc_framework_security-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b490fb0d46275f165b1214785fec732bc7b8e06c99b6ba045382404d44400d9c", size = 41303, upload-time = "2026-06-19T16:17:15.804Z" }, + { url = "https://files.pythonhosted.org/packages/be/ac/f2ff946edfaf16b4ce5e31afac5e519f83705c0f4842fd25134ecb8f2f4a/pyobjc_framework_security-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ce461296b003b2ba17c8b65f6339f9d2fd5dcfa2b3b52ddc0a696334cc8974c5", size = 41306, upload-time = "2026-06-19T16:17:16.816Z" }, + { url = "https://files.pythonhosted.org/packages/4e/5b/2719bc4062e6c27083191fd20e365ae02d0bf1c22f4d1a88211e3d96b369/pyobjc_framework_security-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:76ff6e44e62d3e15651540493879bf16687d862c4f10f3cadade757811c8b8d0", size = 41300, upload-time = "2026-06-19T16:17:17.702Z" }, + { url = "https://files.pythonhosted.org/packages/15/90/dccd4cd6877ef208957dc1f3675287d8614a4dcd2a3ee0a5e56f5fb5a1ba/pyobjc_framework_security-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:990013baba29d6f985d8950b23701129b2597b3d16f628b785fe97596d8a8de3", size = 41299, upload-time = "2026-06-19T16:17:18.511Z" }, + { url = "https://files.pythonhosted.org/packages/ce/af/f9e8040e0c3ef6a50392a46ad1df482a666aa615180d40730b00282ff81f/pyobjc_framework_security-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:066a3e5e9d368e7a6ba8dd52be2077a634ef12a54fbfcc78b3b8154a8f988a1d", size = 42179, upload-time = "2026-06-19T16:17:19.48Z" }, + { url = "https://files.pythonhosted.org/packages/c9/3c/76e2a8bb8d5fe48f0e8e25c6abec1609f3667cc39935017badfe9e9603f2/pyobjc_framework_security-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:5319ae49b8874363ab51c6ff4d85d4ea0cfa6d836fe0306e901ba9ae560b880d", size = 41370, upload-time = "2026-06-19T16:17:20.501Z" }, + { url = "https://files.pythonhosted.org/packages/14/6e/7120956e9833b2c70757eec1f65f57c191e00662cf74c4545d88315643fa/pyobjc_framework_security-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:21618431e0dbfbd3d4029445e3118af88e5d7e52ddecf9a2d17c759c51628d85", size = 42926, upload-time = "2026-06-19T16:17:21.425Z" }, + { url = "https://files.pythonhosted.org/packages/b3/ff/0bafc557523e5755f74dd5363386a1e9b03f611e2e36df0737a508cd5ab4/pyobjc_framework_security-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:fa192e9df479375e6242adcadb9a44f32907dd7fe1207608710cd3af65fe3c84", size = 41376, upload-time = "2026-06-19T16:17:22.337Z" }, + { url = "https://files.pythonhosted.org/packages/47/33/33d266117e46fef148caa4f986b3d896cb9bfd76bef48bd761cb60c758ee/pyobjc_framework_security-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:07cd044a7996f9a897040c49055fa3bdf565acac4a25b834a72e60602376146d", size = 42944, upload-time = "2026-06-19T16:17:23.371Z" }, ] [[package]] name = "pyobjc-framework-securityfoundation" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-security" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/57/d5/c2b77e83c1585ba43e5f00c917273ba4bf7ed548c1b691f6766eb0418d52/pyobjc_framework_securityfoundation-12.1.tar.gz", hash = "sha256:1f39f4b3db6e3bd3a420aaf4923228b88e48c90692cf3612b0f6f1573302a75d", size = 12669, upload-time = "2025-11-14T10:22:09.256Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/9e/c1b6426d9ba602ceda4f5bf438705e05930d46c3ef561a89736e1b9cea51/pyobjc_framework_securityfoundation-12.2.1.tar.gz", hash = "sha256:b10f7c6f2fea27f105e69e0ef455df10e911748a4a414aff74f9dede48dd2cd3", size = 13103, upload-time = "2026-06-19T16:21:41.065Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/93/1e/349fb71a413b37b1b41e712c7ca180df82144478f8a9a59497d66d0f2ea2/pyobjc_framework_securityfoundation-12.1-py2.py3-none-any.whl", hash = "sha256:579cf23e63434226f78ffe0afb8426e971009588e4ad812c478d47dfd558201c", size = 3792, upload-time = "2025-11-14T10:03:14.459Z" }, + { url = "https://files.pythonhosted.org/packages/c2/03/b439d6af2c215a59e71cdb2cf00959415f0dd1ec0fd84b0a517c403290a0/pyobjc_framework_securityfoundation-12.2.1-py2.py3-none-any.whl", hash = "sha256:4f3f52573805977e94f3b50af27cacbe2abdd05fd96fac4be746685e523e1e85", size = 3826, upload-time = "2026-06-19T16:17:24.38Z" }, ] [[package]] name = "pyobjc-framework-securityinterface" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-security" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f9/64/bf5b5d82655112a2314422ee649f1e1e73d4381afa87e1651ce7e8444694/pyobjc_framework_securityinterface-12.1.tar.gz", hash = "sha256:deef11ad03be8d9ff77db6e7ac40f6b641ee2d72eaafcf91040537942472e88b", size = 25552, upload-time = "2025-11-14T10:22:12.098Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/24/5486d26d86abf4edb639de2eb5598b6c5cebe33a1aa19d55575694d72160/pyobjc_framework_securityinterface-12.2.1.tar.gz", hash = "sha256:08e58cc05741e8515f157854831063261a7247497c996a120f90148b8aa78842", size = 27798, upload-time = "2026-06-19T16:21:41.816Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/29/72ead8aecbccd06e4043754ba31d379eae70a6c39b3503a6e01cbb5ce6c3/pyobjc_framework_securityinterface-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:048950875a968032bc133c64e594d4810d5bf5ef359012830cf193610d9c04ac", size = 10723, upload-time = "2025-11-14T10:03:16.224Z" }, - { url = "https://files.pythonhosted.org/packages/37/1c/a01fd56765792d1614eb5e8dc0a7d5467564be6a2056b417c9ec7efc648f/pyobjc_framework_securityinterface-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed599be750122376392e95c2407d57bd94644e8320ddef1d67660e16e96b0d06", size = 10719, upload-time = "2025-11-14T10:03:18.353Z" }, - { url = "https://files.pythonhosted.org/packages/59/3e/17889a6de03dc813606bb97887dc2c4c2d4e7c8f266bc439548bae756e90/pyobjc_framework_securityinterface-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5cb5e79a73ea17663ebd29e350401162d93e42343da7d96c77efb38ae64ff01f", size = 10783, upload-time = "2025-11-14T10:03:20.202Z" }, - { url = "https://files.pythonhosted.org/packages/78/c0/b286689fca6dd23f1ad5185eb429a12fba60d157d7d53f6188c19475b331/pyobjc_framework_securityinterface-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:af5db06d53c92f05446600d241afab5aec6fec7ab10941b4eeb27a452c543b64", size = 10799, upload-time = "2025-11-14T10:03:22.296Z" }, - { url = "https://files.pythonhosted.org/packages/72/52/d378f25bb15f0d34e610f6cba50cedb0b99fdbae9bae9c0f0e715340f338/pyobjc_framework_securityinterface-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:08516c01954233fecb9bd203778b1bf559d427ccea26444ae1fa93691e751ddd", size = 11139, upload-time = "2025-11-14T10:03:24.17Z" }, - { url = "https://files.pythonhosted.org/packages/8e/df/c6b30b5eb671755d6d59baa34c406d38524eef309886b6a7d9b7a05eb00a/pyobjc_framework_securityinterface-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:153632d23b0235faa56d26d5641e585542dac6b13b0d7b152cca27655405dec4", size = 10836, upload-time = "2025-11-14T10:03:26.179Z" }, - { url = "https://files.pythonhosted.org/packages/aa/11/0e439fe86d93afd43587640e2904e73ff6d9c9401537b1e142cb623d95f6/pyobjc_framework_securityinterface-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:b9eb42c5d4c62af83d69adeff3608af9cd4cfe5b7c9885a6a399be74fcc3d0f0", size = 11182, upload-time = "2025-11-14T10:03:27.948Z" }, + { url = "https://files.pythonhosted.org/packages/2d/03/52723aa9d0fe8e0d48abd3adb2ca7079de1d5e6c4da4b89e7708aa3b4016/pyobjc_framework_securityinterface-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5ce92f1143c5a055cc952a21edd69c478fe26c16f1f68b312e336de0c08d0e2e", size = 10744, upload-time = "2026-06-19T16:17:25.305Z" }, + { url = "https://files.pythonhosted.org/packages/f5/36/4591538165b110012f1bf442212404ae4af6ba3a4d8cb5a4cf7be611a00c/pyobjc_framework_securityinterface-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:db17172727a7799a38076b759c824b81ab5c2c4f49b8b71f4d6bba25f9697a7c", size = 10741, upload-time = "2026-06-19T16:17:26.227Z" }, + { url = "https://files.pythonhosted.org/packages/ca/46/8dcb2c1983ca1fcb8279de27264c4b5e00484caaf714c7b846c7800dc898/pyobjc_framework_securityinterface-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e15228ac342d553464f990435e81ee67c4f2fa5cca99388933cee72fd8764193", size = 10807, upload-time = "2026-06-19T16:17:26.996Z" }, + { url = "https://files.pythonhosted.org/packages/19/13/3c18031c450eb8677f600f86af9c6569174d093dbf60af0bef202813b681/pyobjc_framework_securityinterface-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9b93c778e431c0290b0eaeefdd0e9f502a5defa172a82710d697537a4ff8db7c", size = 10820, upload-time = "2026-06-19T16:17:27.862Z" }, + { url = "https://files.pythonhosted.org/packages/d8/88/da14ec8edd7d22245bf3230a5b2c0127a73b7b8ae5d036053ce16a722e0e/pyobjc_framework_securityinterface-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:1bc4a9c50583bebc061b734e382389a50bd62ad5fdaadeb8585024278b96dea9", size = 11161, upload-time = "2026-06-19T16:17:28.683Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e6/e11a20ec8a2cdb15e9b0cc2dad354480ac92fdfb902faccb47500d5141b6/pyobjc_framework_securityinterface-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b427588d565fc4af5717e9ee2a017d01b6d8c22ba0b68799e2fe5001215db179", size = 10857, upload-time = "2026-06-19T16:17:29.465Z" }, + { url = "https://files.pythonhosted.org/packages/6c/c3/3e8a084ceed6c2a8256c67e6bb167b8fbddb163465514dfae3ffbeca18e5/pyobjc_framework_securityinterface-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0351cf5555cb4d7d2c04a4cd8047611d76b015c254259832adf8cd416e3efec6", size = 11205, upload-time = "2026-06-19T16:17:30.221Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ca/9544778341cee37392614102156b60056a16237b1cc9d7dba55e9d523817/pyobjc_framework_securityinterface-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:9970c0cfdf9d07335c9a6dc123893e430a9184e84e258e68878730ed9e47366a", size = 10863, upload-time = "2026-06-19T16:17:31.046Z" }, + { url = "https://files.pythonhosted.org/packages/44/27/9206c1d8aec4f4a27e3d4d52bb138d46370104d48b008c8ae0825dd321c0/pyobjc_framework_securityinterface-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:258b237ec6f2efa13ea7518491c0d5d48092ad5c5cbfa9274a45ac303859339a", size = 11209, upload-time = "2026-06-19T16:17:31.936Z" }, ] [[package]] name = "pyobjc-framework-securityui" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-security" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/83/3f/d870305f5dec58cd02966ca06ac29b69fb045d8b46dfb64e2da31f295345/pyobjc_framework_securityui-12.1.tar.gz", hash = "sha256:f1435fed85edc57533c334a4efc8032170424b759da184cb7a7a950ceea0e0b6", size = 12184, upload-time = "2025-11-14T10:22:14.323Z" } +sdist = { url = "https://files.pythonhosted.org/packages/54/f9/5aed7140a5f22102cbffad47e1f8a6cc231a428b2021a1731925da9a78f1/pyobjc_framework_securityui-12.2.1.tar.gz", hash = "sha256:87b07746fb9ca7634c3c74f89bf6ab90dffbfe0c0ffb89551aefce0e35353a50", size = 12648, upload-time = "2026-06-19T16:21:42.645Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/36/7f/eff9ffdd34511cc95a60e5bd62f1cfbcbcec1a5012ef1168161506628c87/pyobjc_framework_securityui-12.1-py2.py3-none-any.whl", hash = "sha256:3e988b83c9a2bb0393207eaa030fc023a8708a975ac5b8ea0508cdafc2b60705", size = 3594, upload-time = "2025-11-14T10:03:29.628Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b166bb5b4f5fa80dbf9c09074d435c41b04dc741f8a4cd37b241a2c884ce/pyobjc_framework_securityui-12.2.1-py2.py3-none-any.whl", hash = "sha256:e1e0d9ff4671aff464b7af48b1e10bf9aeeffc1ee40c91fd1ac301ab19d87e45", size = 3626, upload-time = "2026-06-19T16:17:32.783Z" }, ] [[package]] name = "pyobjc-framework-sensitivecontentanalysis" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-quartz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e7/ce/17bf31753e14cb4d64fffaaba2377453c4977c2c5d3cf2ff0a3db30026c7/pyobjc_framework_sensitivecontentanalysis-12.1.tar.gz", hash = "sha256:2c615ac10e93eb547b32b214cd45092056bee0e79696426fd09978dc3e670f25", size = 13745, upload-time = "2025-11-14T10:22:16.447Z" } +sdist = { url = "https://files.pythonhosted.org/packages/31/8e/50648c1e4029611acfa6a5cc6c20e3f36d9754414c7c9c690ef142ee1c6e/pyobjc_framework_sensitivecontentanalysis-12.2.1.tar.gz", hash = "sha256:e958e4333b72e7bd93a32be6c3118c50be8e98de6ca7a41bbf076a712ab2ca21", size = 14428, upload-time = "2026-06-19T16:21:43.381Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/23/c99568a0d4e38bd8337d52e4ae25a0b0bd540577f2e06f3430c951d73209/pyobjc_framework_sensitivecontentanalysis-12.1-py2.py3-none-any.whl", hash = "sha256:faf19d32d4599ac2b18fb1ccdc3e33b2b242bdf34c02e69978bd62d3643ad068", size = 4230, upload-time = "2025-11-14T10:03:31.26Z" }, + { url = "https://files.pythonhosted.org/packages/ad/38/ee90dce73e9eabcc5c80ee514b2affcc89dd3b47dfeaaaff20a1cb0b5e33/pyobjc_framework_sensitivecontentanalysis-12.2.1-py2.py3-none-any.whl", hash = "sha256:918c362c11673308191a9d94c2c59a368d4cc39e354694e1da5c99b3609cb47c", size = 4269, upload-time = "2026-06-19T16:17:33.696Z" }, ] [[package]] name = "pyobjc-framework-servicemanagement" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/31/d0/b26c83ae96ab55013df5fedf89337d4d62311b56ce3f520fc7597d223d82/pyobjc_framework_servicemanagement-12.1.tar.gz", hash = "sha256:08120981749a698033a1d7a6ab99dbbe412c5c0d40f2b4154014b52113511c1d", size = 14585, upload-time = "2025-11-14T10:22:18.735Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/2b/289270180fc32c2297907e6576355aaabf004297d9830a62f9792a5bc95b/pyobjc_framework_servicemanagement-12.2.1.tar.gz", hash = "sha256:99ceee681fea1e57246d33acbe199100f2e35a09cac97ae1c271e34073c28763", size = 15295, upload-time = "2026-06-19T16:21:44.189Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/5d/1009c32189f9cb26da0124b4a60640ed26dd8ad453810594f0cbfab0ff70/pyobjc_framework_servicemanagement-12.1-py2.py3-none-any.whl", hash = "sha256:9a2941f16eeb71e55e1cd94f50197f91520778c7f48ad896761f5e78725cc08f", size = 5357, upload-time = "2025-11-14T10:03:32.928Z" }, + { url = "https://files.pythonhosted.org/packages/30/15/ba63887c27663a7107f289a44072c04c6b351b52177c63d8004640fa7325/pyobjc_framework_servicemanagement-12.2.1-py2.py3-none-any.whl", hash = "sha256:8c84c82a09bf00046ef2d43f910204cd362fd1eceb706176c30d079ee208f2ed", size = 5456, upload-time = "2026-06-19T16:17:34.619Z" }, ] [[package]] name = "pyobjc-framework-sharedwithyou" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-sharedwithyoucore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0f/8b/8ab209a143c11575a857e2111acc5427fb4986b84708b21324cbcbf5591b/pyobjc_framework_sharedwithyou-12.1.tar.gz", hash = "sha256:167d84794a48f408ee51f885210c616fda1ec4bff3dd8617a4b5547f61b05caf", size = 24791, upload-time = "2025-11-14T10:22:21.248Z" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/85/8a2d509a27814d56e064f15469b8fd9720ce5c7ec669bcb0cf4b2e800b25/pyobjc_framework_sharedwithyou-12.2.1.tar.gz", hash = "sha256:b1908b9822244ea31d4d546118389c69687982e5fa67bc72cbf1a9e09f1f84b3", size = 27310, upload-time = "2026-06-19T16:21:45.008Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/8f/05eb8862ee9163dd41d38c0a1a3e8d3cbd2a1fb9397f792c19af84241556/pyobjc_framework_sharedwithyou-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b5e05940bd0b9107340437ecef4502a2d2326072b0fa0b458f41c02a173d1047", size = 8748, upload-time = "2025-11-14T10:03:34.7Z" }, - { url = "https://files.pythonhosted.org/packages/19/69/3ad9b344808c5619adc253b665f8677829dfb978888227e07233d120cfab/pyobjc_framework_sharedwithyou-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:359c03096a6988371ea89921806bb81483ea509c9aa7114f9cd20efd511b3576", size = 8739, upload-time = "2025-11-14T10:03:36.48Z" }, - { url = "https://files.pythonhosted.org/packages/ec/ee/e5113ce985a480d13a0fa3d41a242c8068dc09b3c13210557cf5cc6a544a/pyobjc_framework_sharedwithyou-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a99a6ebc6b6de7bc8663b1f07332fab9560b984a57ce344dc5703f25258f258d", size = 8763, upload-time = "2025-11-14T10:03:38.467Z" }, - { url = "https://files.pythonhosted.org/packages/2e/51/e833c41cb6578f51623da361f6ded50b5b91331f9339b125ea50b4e62f8b/pyobjc_framework_sharedwithyou-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:491b35cdb3a0bc11e730c96d4109944c77ab153573a28220ff12d41d34dd9c0f", size = 8781, upload-time = "2025-11-14T10:03:40.14Z" }, - { url = "https://files.pythonhosted.org/packages/59/c4/b843dc3b7bd1385634df7f0bb8b557d8d09df3a384c7b2df0bc85af5bd4e/pyobjc_framework_sharedwithyou-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:50f0b32e2bf6f7ceb3af4422b015f674dc20a8cb1afa72d78f7e4186eb3710b9", size = 8917, upload-time = "2025-11-14T10:03:41.824Z" }, - { url = "https://files.pythonhosted.org/packages/1e/b0/eca22cf9ba67c8ba04a98f8a26af0a5ca16b40e05a8100b8209a153046b1/pyobjc_framework_sharedwithyou-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:5a38bc6e3e0c9a36fe86e331eb16b680bab0024c897d252af1e611f0cd1087ef", size = 8824, upload-time = "2025-11-14T10:03:43.492Z" }, - { url = "https://files.pythonhosted.org/packages/b3/e9/4cc7420c7356b1a25b4c9a4544454e99c3da8d50ee4b4d9b55a82eb5a836/pyobjc_framework_sharedwithyou-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1b65c51a8f6f5baf382e419cda74896d196625f1468710660a1a87a8b02b34dc", size = 8970, upload-time = "2025-11-14T10:03:45.19Z" }, + { url = "https://files.pythonhosted.org/packages/ef/79/384b0cd0a9894eb2d8a3ce37c889467ec68626508bf04efa2d3451ea28be/pyobjc_framework_sharedwithyou-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:01ff5c2a9600248fe9bd911ef43668d5c571be43595d1da7b375c2503a3e4999", size = 8816, upload-time = "2026-06-19T16:17:35.423Z" }, + { url = "https://files.pythonhosted.org/packages/06/9f/6b4f112d25e6f7b78e60b4caf852b197416e4e5fc399c33becb67934d77f/pyobjc_framework_sharedwithyou-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6bd370918c4cedff2a2f6e15e22f6184e5431bd4624b0d270d6e518b6ffec8df", size = 8817, upload-time = "2026-06-19T16:17:36.462Z" }, + { url = "https://files.pythonhosted.org/packages/46/47/d9810da0987abdf3b21df2d8d872e46f4d916a5fa45ab43370136375a7ae/pyobjc_framework_sharedwithyou-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32188fc96282ba4d3686587d200d5c0304ec3e0cd5cfbb8d0bdadc5ab1fcb300", size = 8832, upload-time = "2026-06-19T16:17:37.247Z" }, + { url = "https://files.pythonhosted.org/packages/96/e1/bf963d03e57084970380d99af331cf5b6d3cb214a949fd8d8a7f266edd5e/pyobjc_framework_sharedwithyou-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:927a95d78693f2b2323dbc117d1fcf638612cbfb19d2fd0f6077c19c22eb92cd", size = 8846, upload-time = "2026-06-19T16:17:38.044Z" }, + { url = "https://files.pythonhosted.org/packages/de/6f/56ac0ba0950a3e20cc9e8993432466241b8b5fbb08867840dd796cbd9f38/pyobjc_framework_sharedwithyou-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:46d20007e80717f04506a7afee2b6bdcc5ca50a345d7a26fd3f0cd7d97e5cba0", size = 8984, upload-time = "2026-06-19T16:17:38.853Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a2/b8aba1597228ffad1fe1293bc16e7056b1d17d592794f369a9f0615bce58/pyobjc_framework_sharedwithyou-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:60c29e58e6cf6d760a337c6ae76186ae3fbe1dcec4877cf8c6b2827bcdd40836", size = 8894, upload-time = "2026-06-19T16:17:39.66Z" }, + { url = "https://files.pythonhosted.org/packages/d7/c1/f60ac8d3ad890c5f25d784b277b3f6319dba0aa7cad711d7d0812c50a73c/pyobjc_framework_sharedwithyou-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:52470d61f146dd5783a8a69acdb114fe8e7dee4e7d6853ae214460ccee11e485", size = 9041, upload-time = "2026-06-19T16:17:40.635Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ce/2f743a3865b82d017ea8b6bf74d4b84fae71b01ac230544e19ad6f666ac1/pyobjc_framework_sharedwithyou-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:f56137c9a1b53a69b76061c7cfe0f8739b5c42140891a326fd68b7d1e2be702a", size = 8888, upload-time = "2026-06-19T16:17:41.483Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ec/b33cbed5b00e5a8d158b0b32812fba1ee5ba6edbc96788f3e8a3ca316f61/pyobjc_framework_sharedwithyou-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:52d02b2188c9009946c3dbdb65db0b032a9a1546448c7f761f5be14d84250abe", size = 9037, upload-time = "2026-06-19T16:17:42.275Z" }, ] [[package]] name = "pyobjc-framework-sharedwithyoucore" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/ef/84059c5774fd5435551ab7ab40b51271cfb9997b0d21f491c6b429fe57a8/pyobjc_framework_sharedwithyoucore-12.1.tar.gz", hash = "sha256:0813149eeb755d718b146ec9365eb4ca3262b6af9ff9ba7db2f7b6f4fd104518", size = 22350, upload-time = "2025-11-14T10:22:23.611Z" } +sdist = { url = "https://files.pythonhosted.org/packages/83/05/5e9ba6cdde040717004115a1254ee315434bf7df5f2ee9f9f9ce619bf6dd/pyobjc_framework_sharedwithyoucore-12.2.1.tar.gz", hash = "sha256:b8a4d2d79702756d9fffc5e17f83b45d52c469579acde873a487842ac334384c", size = 24333, upload-time = "2026-06-19T16:21:45.714Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/d5/73669bcc8bde10f6a11d4c1d38f7c38c286289a59f0a3cf76c6ed121dd0b/pyobjc_framework_sharedwithyoucore-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a7dd3048ea898b8fa401088d9fae73dbda361fb7c2dd1dc1057102e503b12771", size = 8512, upload-time = "2025-11-14T10:03:47.027Z" }, - { url = "https://files.pythonhosted.org/packages/ce/a1/83e58eca8827a1a9975a9c5de7f8c0bdc73b5f53ee79768d1fdbec6747de/pyobjc_framework_sharedwithyoucore-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f4f9f7fed0768ebbbc2d24248365da2cf5f014b8822b2a1fbbce5fa920f410f1", size = 8512, upload-time = "2025-11-14T10:03:49.176Z" }, - { url = "https://files.pythonhosted.org/packages/dd/0e/0c2b0591ebc72d437dccca7a1e7164c5f11dde2189d4f4c707a132bab740/pyobjc_framework_sharedwithyoucore-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed928266ae9d577ff73de72a03bebc66a751918eb59ca660a9eca157392f17be", size = 8530, upload-time = "2025-11-14T10:03:50.839Z" }, - { url = "https://files.pythonhosted.org/packages/5e/23/2446cb158efe0f55d983ae7b4729b3b24c52a1370b5d22bc134f046cdb34/pyobjc_framework_sharedwithyoucore-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:13eebca21722556449e47b0eda3339165b5afbb455ae00b34aabe03988affd7a", size = 8547, upload-time = "2025-11-14T10:03:52.459Z" }, - { url = "https://files.pythonhosted.org/packages/8e/42/6c5de4e508a0c0f4715e3466c0035e23b5875d2a43525a6ed81e4770ad3c/pyobjc_framework_sharedwithyoucore-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:d9aa525cdff75005a8f0ca2f7afdd1535b9e34ccafb6a92a932f3ded4b6d64d4", size = 8677, upload-time = "2025-11-14T10:03:54.15Z" }, - { url = "https://files.pythonhosted.org/packages/94/a1/24ffb35098a239a8804e469fcd7430eaee5e47bf0756c59cd77a66c3edff/pyobjc_framework_sharedwithyoucore-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:2ceb4c3ad7bc1c93b4cbbbab6404d3e32714c12c36fab2932c170946af83c548", size = 8591, upload-time = "2025-11-14T10:03:56.543Z" }, - { url = "https://files.pythonhosted.org/packages/9f/5e/2460f60a931f11933ea6d5d1f7c73b6f4ade7980360cfcf327cb785b7bf8/pyobjc_framework_sharedwithyoucore-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0a55c843bd4cfdefa4a4566ccb64782466341715ecab3956c3566dbfbad0d1e5", size = 8739, upload-time = "2025-11-14T10:03:58.23Z" }, + { url = "https://files.pythonhosted.org/packages/ad/29/63461b6214c123dd7140bd40b95cab24a10fc1f3864f3e7d52d1638cca9e/pyobjc_framework_sharedwithyoucore-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c717b1c6208ed38ba4c190e3f3098c0817318f348029a7e51ad1a424e965e213", size = 8584, upload-time = "2026-06-19T16:17:43.079Z" }, + { url = "https://files.pythonhosted.org/packages/71/e3/e234ad577ab3efe97a3a1b0703e57430672ed14a70fccf345601adbbdecf/pyobjc_framework_sharedwithyoucore-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ac0e9812f970bba7f4db5c6332c9282c1b32456dadc2c88c82165712af341b5d", size = 8579, upload-time = "2026-06-19T16:17:43.976Z" }, + { url = "https://files.pythonhosted.org/packages/46/0b/c26f3af20af44e755f33ba8c75a5439bd8e2fd4bfd406da1211fc08dd8a4/pyobjc_framework_sharedwithyoucore-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:26d3c00be64f9e422be6a902dd2e95532c7ef4e6149ef756dfc2811a71a1c6e6", size = 8599, upload-time = "2026-06-19T16:17:44.808Z" }, + { url = "https://files.pythonhosted.org/packages/07/26/fd4dd0b314bbe91b58cdc30b0621a8c6efee2fa9a9a75a4f985d9f14b492/pyobjc_framework_sharedwithyoucore-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:25d42419c12d964bcadce37a4cc2e3825d5227bd1bf00767902a702136bdc1c4", size = 8614, upload-time = "2026-06-19T16:17:45.633Z" }, + { url = "https://files.pythonhosted.org/packages/08/90/d2177377c80ab30c30a292686e84b298b09c431303fa0c26ebdaf9fd5cca/pyobjc_framework_sharedwithyoucore-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c6ca2669c6c7d1a061acbf0c12c55aac3848a43d580b9aa96ffea69d36985086", size = 8748, upload-time = "2026-06-19T16:17:46.39Z" }, + { url = "https://files.pythonhosted.org/packages/ed/aa/2878309744e21c37c66c72e1d1b3519888706220b8857253c6be54702efe/pyobjc_framework_sharedwithyoucore-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:803748c78c9b062cd0aba8205be6c178ce6fa2eb227d9ac3bf26b484cd2f4517", size = 8664, upload-time = "2026-06-19T16:17:47.204Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b0/f4c5764d5971fadc6d73c45d28d29ebb67086e007bb08bb731442aceb7f4/pyobjc_framework_sharedwithyoucore-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:f991c7b52cdc831244ab49f76ec20fd331a561f364aa33ceadebc684e7fac7da", size = 8810, upload-time = "2026-06-19T16:17:48.123Z" }, + { url = "https://files.pythonhosted.org/packages/a4/d8/cde9e276d2540eaf4d619a299c50c7b594c77ca0ac4cd68d341d308019a0/pyobjc_framework_sharedwithyoucore-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:f5544a515a902b796a22272fabe83a3fd39256de9cf243d90cfb7c02ec9e3f7d", size = 8665, upload-time = "2026-06-19T16:17:48.881Z" }, + { url = "https://files.pythonhosted.org/packages/ac/67/639e301589e643658c2e45d8312f31df3257205948f10be6c2e0fff4024f/pyobjc_framework_sharedwithyoucore-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:0defd143c6633a4032e705a77c9c340bd11d5581bdc098e43455c35ea7c43e16", size = 8795, upload-time = "2026-06-19T16:17:49.618Z" }, ] [[package]] name = "pyobjc-framework-shazamkit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ed/2c/8d82c5066cc376de68ad8c1454b7c722c7a62215e5c2f9dac5b33a6c3d42/pyobjc_framework_shazamkit-12.1.tar.gz", hash = "sha256:71db2addd016874639a224ed32b2000b858802b0370c595a283cce27f76883fe", size = 22518, upload-time = "2025-11-14T10:22:25.996Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/7b/12a46aee28ffc14a5118ab45be8f0f629feece3548df81d934e31e723ada/pyobjc_framework_shazamkit-12.2.1.tar.gz", hash = "sha256:4cfa9325e381e8b365b2d4725b9165a5e52f7986f28dcf23c3e7f0bd3bddf3aa", size = 26062, upload-time = "2026-06-19T16:21:46.513Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/68/669b073beec0013a3bd3b99c99312fbf1018cfa702819962f5da8c121143/pyobjc_framework_shazamkit-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:18ff0a83a6d2517d30669cf5337e688310e424d1cdc1fa90acf3753a73cc1bd4", size = 8558, upload-time = "2025-11-14T10:04:00.448Z" }, - { url = "https://files.pythonhosted.org/packages/92/12/09d83a8ac51dc11a574449dea48ffa99b3a7c9baf74afeedb487394d110d/pyobjc_framework_shazamkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0c10ba22de524fbedf06270a71bb0a3dbd4a3853b7002ddf54394589c3be6939", size = 8555, upload-time = "2025-11-14T10:04:02.552Z" }, - { url = "https://files.pythonhosted.org/packages/04/5e/7d60d8e7b036b20d0e94cd7c4563e7414653344482e85fbc7facffabc95f/pyobjc_framework_shazamkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e184dd0f61a604b1cfcf44418eb95b943e7b8f536058a29e4b81acadd27a9420", size = 8577, upload-time = "2025-11-14T10:04:04.182Z" }, - { url = "https://files.pythonhosted.org/packages/a9/fa/476cf0eb6f70e434056276b1a52bb47419e4b91d80e0c8e1190ce84f888f/pyobjc_framework_shazamkit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:957c5e31b2b275c822ea43d7c4435fa1455c6dc5469ad4b86b29455571794027", size = 8587, upload-time = "2025-11-14T10:04:06.351Z" }, - { url = "https://files.pythonhosted.org/packages/9a/69/105fccda6c5ca32d35edc5e055d4cffc9aefe6a40fdd00bb21ec5d21e0ce/pyobjc_framework_shazamkit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:eb2875ddf18d3cd2dc2b1327f58e142b9bd86fafd32078387ed867ec5a6c5571", size = 8734, upload-time = "2025-11-14T10:04:08.33Z" }, - { url = "https://files.pythonhosted.org/packages/8d/79/09d4b2c121d3d3a662e19d67328904fd62a3303b7a169698d654a3493140/pyobjc_framework_shazamkit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:951b989997a7c19d0c0d91a477d3d221ddb890085f3538ae3c520177c2322caa", size = 8647, upload-time = "2025-11-14T10:04:09.972Z" }, - { url = "https://files.pythonhosted.org/packages/74/37/859660e654ebcf6b0b4a7f3016a0473629642cf387419be2052f363a6001/pyobjc_framework_shazamkit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:70f203ffe3e4c130b3a9c699d9a2081884bd7b3bd1ce08c7402b6d60fc755d75", size = 8790, upload-time = "2025-11-14T10:04:11.957Z" }, + { url = "https://files.pythonhosted.org/packages/17/70/ece3d55135997ec0c884d487fe7bfbaa9bd96b191a3e1c1333a9872623ec/pyobjc_framework_shazamkit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ef75dc67147b9bb6f783ca34fa2e15c9fee3cedbf27175f84f8355332c747fcf", size = 8645, upload-time = "2026-06-19T16:17:50.395Z" }, + { url = "https://files.pythonhosted.org/packages/c9/78/3ae7f591a3bf96ddc0c31fa9524fa495b1f51910a110cb6725d8ab6a0bb7/pyobjc_framework_shazamkit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cafe6fdaa478611ddbea6be8fe01373bf65b57f54bc6ca7f8a5580b2f75ca7c9", size = 8639, upload-time = "2026-06-19T16:17:51.337Z" }, + { url = "https://files.pythonhosted.org/packages/9a/24/7e327525ed03ed041fb97c5f32bd9e0ca79bd282f50d373be68f8f71f14c/pyobjc_framework_shazamkit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ee31a56ae4d15401e10f79fa07f7f79c5cc747afa9a5ce581654f82ee5b3c6d9", size = 8661, upload-time = "2026-06-19T16:17:52.177Z" }, + { url = "https://files.pythonhosted.org/packages/43/a0/5247501e4d5482c0977025d9914d481ad5029f9f372a84475c01240940e2/pyobjc_framework_shazamkit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6b5fcc07180a2fb5a1cef0f38cbdb24caa869e423f612116bc67135dee59b44d", size = 8675, upload-time = "2026-06-19T16:17:53.462Z" }, + { url = "https://files.pythonhosted.org/packages/ad/d0/ad91bcaf2f43bbbc5a7b67cf1c0544e2dd875befb95664540346e9328324/pyobjc_framework_shazamkit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0ccde3a967120d3285fcc40d48f34f345bc6f0b4accb4ff64799535030bc3e3a", size = 8820, upload-time = "2026-06-19T16:17:54.278Z" }, + { url = "https://files.pythonhosted.org/packages/03/32/46d5a58ef0c0956586d7bee5ce846bcebc0d167ec353b6fdfbd7ec14b4e3/pyobjc_framework_shazamkit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:2d7a35d6e66e99b44810a3045c1922d3feaedaef546ccaf930c44312c1b83d3f", size = 8729, upload-time = "2026-06-19T16:17:55.104Z" }, + { url = "https://files.pythonhosted.org/packages/de/69/aa19849a9ecb5f8db5d7e904826b78e76f6e1ef4dad15431d4da8243ab4b/pyobjc_framework_shazamkit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:450bebfde1979e6776ca880c1765d9b9a2b08c4433301cff256bf20944541de4", size = 8876, upload-time = "2026-06-19T16:17:55.902Z" }, + { url = "https://files.pythonhosted.org/packages/c7/46/857e30042b4161eaa7169e17a77bec4fa61ed12fc69203bc3d0f3d74a427/pyobjc_framework_shazamkit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:449d63cd4403963d3d1cdd6e56bea738367fc85bd8c3f861f81caaba05ca677a", size = 8726, upload-time = "2026-06-19T16:17:56.741Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f5/2c310004afd4f85d45e4bfc470cae6067a6c195fc2cf8a552de7d3fdecf4/pyobjc_framework_shazamkit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:e429463fe172944232e95a6e2e7a22aacf09885423928574e868c5a0b20d7fe3", size = 8883, upload-time = "2026-06-19T16:17:57.613Z" }, ] [[package]] name = "pyobjc-framework-social" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/31/21/afc6f37dfdd2cafcba0227e15240b5b0f1f4ad57621aeefda2985ac9560e/pyobjc_framework_social-12.1.tar.gz", hash = "sha256:1963db6939e92ae40dd9d68852e8f88111cbfd37a83a9fdbc9a0c08993ca7e60", size = 13184, upload-time = "2025-11-14T10:22:28.048Z" } +sdist = { url = "https://files.pythonhosted.org/packages/23/25/0a3ba41ba7aa0380968854a53f02e549afe0b5af89856abfcc2f8988e050/pyobjc_framework_social-12.2.1.tar.gz", hash = "sha256:c2877c7ddbed8f3ea17065687725df57243d25b64beb3b885e8a18482c24bf7f", size = 13751, upload-time = "2026-06-19T16:21:47.204Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/fb/090867e332d49a1e492e4b8972ac6034d1c7d17cf39f546077f35be58c46/pyobjc_framework_social-12.1-py2.py3-none-any.whl", hash = "sha256:2f3b36ba5769503b1bc945f85fd7b255d42d7f6e417d78567507816502ff2b44", size = 4462, upload-time = "2025-11-14T10:04:14.578Z" }, + { url = "https://files.pythonhosted.org/packages/c8/81/91b2b6420d8c72ffae0df08249d3f5bf8f6f2a2ba2f4f90d071855d74913/pyobjc_framework_social-12.2.1-py2.py3-none-any.whl", hash = "sha256:3d76bce48e3eced0683096a8f8d32ba233c44db8cb9469f881a783932f29c2f5", size = 4494, upload-time = "2026-06-19T16:17:58.466Z" }, ] [[package]] name = "pyobjc-framework-soundanalysis" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6b/d6/5039b61edc310083425f87ce2363304d3a87617e941c1d07968c63b5638d/pyobjc_framework_soundanalysis-12.1.tar.gz", hash = "sha256:e2deead8b9a1c4513dbdcf703b21650dcb234b60a32d08afcec4895582b040b1", size = 14804, upload-time = "2025-11-14T10:22:29.998Z" } +sdist = { url = "https://files.pythonhosted.org/packages/db/43/b6a1644c01010c50dd4a69b0e4ed144139d60d7900edae937486c62af73b/pyobjc_framework_soundanalysis-12.2.1.tar.gz", hash = "sha256:d17bdea63c2b910c2046ba43383b29b82d594a37feee4431d45b55adc08a3882", size = 15777, upload-time = "2026-06-19T16:21:47.973Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/d3/8df5183d52d20d459225d3f5d24f55e01b8cd9fe587ed972e3f20dd18709/pyobjc_framework_soundanalysis-12.1-py2.py3-none-any.whl", hash = "sha256:8b2029ab48c1a9772f247f0aea995e8c3ff4706909002a9c1551722769343a52", size = 4188, upload-time = "2025-11-14T10:04:16.12Z" }, + { url = "https://files.pythonhosted.org/packages/ba/16/2e8f86936aea345432c6d0d080513dc53eac417f4a485d9b8ffb2d5db885/pyobjc_framework_soundanalysis-12.2.1-py2.py3-none-any.whl", hash = "sha256:4385f492320a304bf64ca772e4a5b29d796f0fc160ff764ed060ac1456aa3cdc", size = 4244, upload-time = "2026-06-19T16:17:59.484Z" }, ] [[package]] name = "pyobjc-framework-speech" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8d/3d/194cf19fe7a56c2be5dfc28f42b3b597a62ebb1e1f52a7dd9c55b917ac6c/pyobjc_framework_speech-12.1.tar.gz", hash = "sha256:2a2a546ba6c52d5dd35ddcfee3fd9226a428043d1719597e8701851a6566afdd", size = 25218, upload-time = "2025-11-14T10:22:32.505Z" } +sdist = { url = "https://files.pythonhosted.org/packages/99/fe/0d1f1710d77deb2aefbdcca344b682f86277a0cf2df55f49615bdee213e1/pyobjc_framework_speech-12.2.1.tar.gz", hash = "sha256:77e01c6e92b34e3bb47dc5b0c43a59cb7941a7eb6ce595ca3de3a686a5df21fa", size = 27772, upload-time = "2026-06-19T16:21:48.792Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/ce/d63b45886df45ea678d066ad943990eb3fbe7d9b5f9e3d4e9375f0e6134d/pyobjc_framework_speech-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:be5c005595918557f17e991b2575159b8ea943e7fb08fd00b1dabccde35f8b1b", size = 9244, upload-time = "2025-11-14T10:04:17.913Z" }, - { url = "https://files.pythonhosted.org/packages/03/54/77e12e4c23a98fc49d874f9703c9f8fd0257d64bb0c6ae329b91fc7a99e3/pyobjc_framework_speech-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0301bfae5d0d09b6e69bd4dbabc5631209e291cc40bda223c69ed0c618f8f2dc", size = 9248, upload-time = "2025-11-14T10:04:19.73Z" }, - { url = "https://files.pythonhosted.org/packages/f9/1b/224cb98c9c32a6d5e68072f89d26444095be54c6f461efe4fefe9d1330a5/pyobjc_framework_speech-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cae4b88ef9563157a6c9e66b37778fc4022ee44dd1a2a53081c2adbb69698945", size = 9254, upload-time = "2025-11-14T10:04:21.361Z" }, - { url = "https://files.pythonhosted.org/packages/21/98/9ae05ebe183f35ac4bb769070f90533405d886fb9216e868e30a0e58d1ad/pyobjc_framework_speech-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:49df0ac39ae6fb44a83b2f4d7f500e0fa074ff58fbc53106d8f626d325079c23", size = 9274, upload-time = "2025-11-14T10:04:23.399Z" }, - { url = "https://files.pythonhosted.org/packages/ec/9d/41581c58ea8f8962189bcf6a15944f9a0bf36b46c5fce611a9632b3344a2/pyobjc_framework_speech-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ed5455f6d9e473c08ebf904ae280ad5fd0d00a073448bf4f0a01fee5887c5537", size = 9430, upload-time = "2025-11-14T10:04:25.026Z" }, - { url = "https://files.pythonhosted.org/packages/00/df/2af011d05b4ab008b1e9e4b8c71b730926ef8e9599aeb8220a898603580b/pyobjc_framework_speech-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a958b3ace1425cf9319f5d8ace920c2f3dac95a5a6d1bd8742d5b64d24671e30", size = 9336, upload-time = "2025-11-14T10:04:26.764Z" }, - { url = "https://files.pythonhosted.org/packages/6f/2e/51599acce043228164355f073b218253d57c06a2927c5dbebc300c5a4cf8/pyobjc_framework_speech-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:893052631198c5447453f81e4ed4af8077038666a7893fbe2d6a2f72b9c44b7e", size = 9496, upload-time = "2025-11-14T10:04:28.403Z" }, + { url = "https://files.pythonhosted.org/packages/b2/43/71171014ce0e31fe6b780f763862e728eeea99afff41b469853169a74afe/pyobjc_framework_speech-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6122a5d8911b87ddc5228073224f4fabd87fb569d78b481689951f4a644aec13", size = 9278, upload-time = "2026-06-19T16:18:00.334Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8a/f8391a8daa0743ab652a1ef1666b62d1dcfb526ebce2254d61e6d7c76adc/pyobjc_framework_speech-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:481ab813439be28ae7b93dd9de4d5a52e5d7351954f1555489b3bdb635dd8243", size = 9279, upload-time = "2026-06-19T16:18:01.222Z" }, + { url = "https://files.pythonhosted.org/packages/fc/10/0415e5368531cec9274b4d028d2075a2e3b9752af8d6c75aec75603fbee4/pyobjc_framework_speech-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:20e70519b54b09ca3ded0bf4b19dace1d39847caebebebdc4381f127dc58f33c", size = 9287, upload-time = "2026-06-19T16:18:02.332Z" }, + { url = "https://files.pythonhosted.org/packages/79/f1/b89ca277820479cba6aeab75ee50357efa902ea4d4a6f7871bc66c1c7403/pyobjc_framework_speech-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:774d6d07193fb49ffbc5c39447eafca21ca5b2058ae41f79af876aa6cce5d726", size = 9305, upload-time = "2026-06-19T16:18:03.253Z" }, + { url = "https://files.pythonhosted.org/packages/a0/be/dc1620d483e55337a529982c04d58edba4f77ec435c9700a78319171bcaa/pyobjc_framework_speech-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f425a3a11c282dfbcb0b3f3e218f5f155232c94406b31ec395c40aa9e309a0e0", size = 9465, upload-time = "2026-06-19T16:18:04.206Z" }, + { url = "https://files.pythonhosted.org/packages/ce/6b/426c949ab1a5bc59fa6fe90bdd20f59661a87816d75397787a00611ee0d1/pyobjc_framework_speech-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:64d44383fb9fac20f457e902e1ea4b680c871ffb9b3e88862c482dcd59da7df7", size = 9371, upload-time = "2026-06-19T16:18:05.035Z" }, + { url = "https://files.pythonhosted.org/packages/4b/00/6dd086d3b241648374314730f6a520e0cce309d581f9682a34d78613d66e/pyobjc_framework_speech-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e646d8c781baf531e5943c2376c92ffeeb0a8bdf4f48d93f88e59b5033d12b55", size = 9525, upload-time = "2026-06-19T16:18:05.855Z" }, + { url = "https://files.pythonhosted.org/packages/87/67/02afd94b537c9303e7a83da184d8a92fd7a41a50d0c7042ea7b0ea6e7f1b/pyobjc_framework_speech-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:6ef705bf457651ee3c0089a3c4a0a14d6241189f51a3255fee0424caccb9d9f5", size = 9357, upload-time = "2026-06-19T16:18:06.712Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c8/39fd8ac2922c78b092dc26c175c549e1dcd628f50b73f1768ce12be95997/pyobjc_framework_speech-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:1f62eefa6b576f6cf0d558a6e9ee5a1e9f99c973f3bb0c5521a5d7a356db9e0c", size = 9519, upload-time = "2026-06-19T16:18:07.494Z" }, ] [[package]] name = "pyobjc-framework-spritekit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-quartz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b6/78/d683ebe0afb49f46d2d21d38c870646e7cb3c2e83251f264e79d357b1b74/pyobjc_framework_spritekit-12.1.tar.gz", hash = "sha256:a851f4ef5aa65cc9e08008644a528e83cb31021a1c0f17ebfce4de343764d403", size = 64470, upload-time = "2025-11-14T10:22:37.569Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/0a/3da8666b42a696b1d82a283ba442f2941060fa304359d4855c3c6072dd3d/pyobjc_framework_spritekit-12.2.1.tar.gz", hash = "sha256:989a25cb2e9d45ecb97655f55464e44a342eb525a891e46a563aae27c683eac0", size = 83906, upload-time = "2026-06-19T16:21:49.536Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/b5/6624e7a28d244beb6bc0ca26f16c137b40933250624babadc924a43bc719/pyobjc_framework_spritekit-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9324955df38e24ab799d5bc7f66cce20aa0c9a93aef3139e54dee99f9d7848cc", size = 17738, upload-time = "2025-11-14T10:04:30.851Z" }, - { url = "https://files.pythonhosted.org/packages/60/6a/e8e44fc690d898394093f3a1c5fe90110d1fbcc6e3f486764437c022b0f8/pyobjc_framework_spritekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:26fd12944684713ae1e3cdd229348609c1142e60802624161ca0c3540eec3ffa", size = 17736, upload-time = "2025-11-14T10:04:33.202Z" }, - { url = "https://files.pythonhosted.org/packages/3b/38/97c3b6c3437e3e9267fb4e1cd86e0da4eff07e0abe7cd6923644d2dfc878/pyobjc_framework_spritekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1649e57c25145795d04bb6a1ec44c20ef7cf0af7c60a9f6f5bc7998dd269db1e", size = 17802, upload-time = "2025-11-14T10:04:35.346Z" }, - { url = "https://files.pythonhosted.org/packages/1f/c6/0e62700fbc90ab57170931fb5056d964202d49efd4d07a610fdaa28ffcfa/pyobjc_framework_spritekit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd6847cb7a287c42492ffd7c30bc08165f4fbb51b2602290e001c0d27e0aa0f0", size = 17818, upload-time = "2025-11-14T10:04:37.804Z" }, - { url = "https://files.pythonhosted.org/packages/a6/22/26b19fc487913d9324cbba824841c9ac921aa9bdd6e340ed46b9968547bc/pyobjc_framework_spritekit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:dd6e309aa284fa9b434aa7bf8ab9ab23fe52e7a372e2db3869586a74471f3419", size = 18088, upload-time = "2025-11-14T10:04:39.973Z" }, - { url = "https://files.pythonhosted.org/packages/13/df/453d5885c79a1341e947c7654aa2c4c0cd6bed5cef4d1c16b26c58051d91/pyobjc_framework_spritekit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:5c9cb8f23436fc7bd0a8149f1271b307131a4c5669dfbb8302beef56cdca057f", size = 17787, upload-time = "2025-11-14T10:04:42.166Z" }, - { url = "https://files.pythonhosted.org/packages/6d/96/4cf353ee49e92f7df02b069eb8eeb6cc36ac09d40a016cf48d1b462dd4c4/pyobjc_framework_spritekit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9ebe7740c124ea7f8fb765e86df39f331f137be575ddb6d0d81bfb2258ee72d7", size = 18069, upload-time = "2025-11-14T10:04:44.348Z" }, + { url = "https://files.pythonhosted.org/packages/87/7b/249a1f6ff3e20f19fe34608caac11e6a84e40c43a9741428efea441e240f/pyobjc_framework_spritekit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6719c153b800f3e0731de21969e84ec0c806bb0379d9b3aa959cf3af5a9de5d8", size = 18588, upload-time = "2026-06-19T16:18:08.419Z" }, + { url = "https://files.pythonhosted.org/packages/1e/ef/260a6deff24f18e68e776288346624e6c36c20e85dc82db74e844182b1cb/pyobjc_framework_spritekit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a1744c4f79b33274c4957d609e8ff87a3c2920105222f4a38e6df870f5e8bc9b", size = 18584, upload-time = "2026-06-19T16:18:09.471Z" }, + { url = "https://files.pythonhosted.org/packages/eb/bb/d19a875a22e30759be9ec33c7f238126d14c5b9cde515fad9c708577585c/pyobjc_framework_spritekit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:182326b96722731536018564a890fce1b6cd9860d5fde3e0c2dfe31c3108f7fd", size = 18650, upload-time = "2026-06-19T16:18:10.321Z" }, + { url = "https://files.pythonhosted.org/packages/17/e6/83366c41ed99c17d690aaf76574e083d885d00c96b615a8552f284b318df/pyobjc_framework_spritekit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee809a16cb549197f124240d1387e0da8ad9dee9bd853ab302fd8538de2cc381", size = 18668, upload-time = "2026-06-19T16:18:11.573Z" }, + { url = "https://files.pythonhosted.org/packages/05/58/76cb4ff81419708ef45095d588f9f73a75a7c6a4c1c0f3070f3ab981ae2d/pyobjc_framework_spritekit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:780d80b09c3a5f368efd5d567291bdf52cc07da079e75260776904ff4c5bac17", size = 18935, upload-time = "2026-06-19T16:18:12.605Z" }, + { url = "https://files.pythonhosted.org/packages/15/58/06ea038a30f55ad8a748d187d74aa83d2464979b058eaa6925a68455e6a9/pyobjc_framework_spritekit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a72410b7e9e4e1363f69dbcf1f2a0055275c7a3d205f707d5d29fa2406f39211", size = 18637, upload-time = "2026-06-19T16:18:13.439Z" }, + { url = "https://files.pythonhosted.org/packages/6b/cd/83f8a7508a43915ee5e88841b98deae8b5d1e3b0ce3a7596d3636a03a6da/pyobjc_framework_spritekit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:79310eb2221dadcb5e0e8dae31f28f4d8f78cef73cebd7403220b82702ccb781", size = 18914, upload-time = "2026-06-19T16:18:14.251Z" }, + { url = "https://files.pythonhosted.org/packages/2e/06/d2c35beb5aec1b97ee13d72adceb6dfecfbdb1a4c836f89e9dd9e00c6e1c/pyobjc_framework_spritekit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:3dc6f3f7db1b971175416481bf9b20d0f39be0a6266c04ed3a999e4755453256", size = 18649, upload-time = "2026-06-19T16:18:15.098Z" }, + { url = "https://files.pythonhosted.org/packages/61/36/0ca18d7a78ceec1c2d0f2e7f1d993511fc7d138d676c5b9ffd12e6278bb5/pyobjc_framework_spritekit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:22d2e8218bd59e05d37c2e0faa0f67711a3a1f0f78e3a1f31680c0587d2cb622", size = 18925, upload-time = "2026-06-19T16:18:16Z" }, ] [[package]] name = "pyobjc-framework-storekit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/87/8a66a145feb026819775d44975c71c1c64df4e5e9ea20338f01456a61208/pyobjc_framework_storekit-12.1.tar.gz", hash = "sha256:818452e67e937a10b5c8451758274faa44ad5d4329df0fa85735115fb0608da9", size = 34574, upload-time = "2025-11-14T10:22:40.73Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/2e/d299e11aefcc70414281e5f82e2297a314c108c5bf81091149f1c3f6411a/pyobjc_framework_storekit-12.2.1.tar.gz", hash = "sha256:5d3b306f08810c485a4bd184bc6e45cc92eaf4cb6d4b88bf701bcb854ab66f59", size = 40971, upload-time = "2026-06-19T16:21:50.541Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/93/39946f690a07bb28f14c73738b133094fb79c34e9fa69553cd683b3e118b/pyobjc_framework_storekit-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e67341cb14adfdecbd230393f3b02d4b19fbb3ada427b06d4f82a703ae90431f", size = 12807, upload-time = "2025-11-14T10:04:46.643Z" }, - { url = "https://files.pythonhosted.org/packages/d9/41/af2afc4d27bde026cfd3b725ee1b082b2838dcaa9880ab719226957bc7cd/pyobjc_framework_storekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a29f45bcba9dee4cf73dae05ab0f94d06a32fb052e31414d0c23791c1ec7931c", size = 12810, upload-time = "2025-11-14T10:04:48.693Z" }, - { url = "https://files.pythonhosted.org/packages/8a/9f/938985e506de0cc3a543e44e1f9990e9e2fb8980b8f3bcfc8f7921d09061/pyobjc_framework_storekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9fe2d65a2b644bb6b4fdd3002292cba153560917de3dd6cf969431fa32d21dd0", size = 12819, upload-time = "2025-11-14T10:04:50.945Z" }, - { url = "https://files.pythonhosted.org/packages/5a/84/d354fd6f50952148614597dd4ebd52ed1d6a3e38cbd5d88e930bd549983d/pyobjc_framework_storekit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:556c3dc187646ab8bda714a7e5630201b931956b81b0162ba420c64f55e5faaf", size = 12835, upload-time = "2025-11-14T10:04:52.866Z" }, - { url = "https://files.pythonhosted.org/packages/4f/24/f8a8d2f1c1107a0a0f85bd830b9e0ff7016d4530924b17787cb8c7bf4f4c/pyobjc_framework_storekit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:15d4643bc4de4aa62f72efcb7a4930bd7e15280867be225bd2c582b3367d75ae", size = 13028, upload-time = "2025-11-14T10:04:55.605Z" }, - { url = "https://files.pythonhosted.org/packages/6d/9b/3d510cc03d5aeef298356578aa8077e4ddebea0a0cd2f50a13bf4f98f9e8/pyobjc_framework_storekit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:5e9354f2373b243066358bf32988d07d8a2da6718563ee6946a40c981a37c7c1", size = 12828, upload-time = "2025-11-14T10:04:57.557Z" }, - { url = "https://files.pythonhosted.org/packages/1a/0c/760f3d4e4deedc11c4144fa3fdf2a697ea7e2f7eef492f6662687b872085/pyobjc_framework_storekit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d11ffe3f8e638ebe7c156c5bf2919115c7562f44f44be8067521b7c5f6e50553", size = 13013, upload-time = "2025-11-14T10:04:59.517Z" }, + { url = "https://files.pythonhosted.org/packages/35/13/e5420b4821fcde19d0b90059197743f38e2bd502e779e398b33a98f505bb/pyobjc_framework_storekit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4b45699dde6a7675a624766397f1ec4782a59ac5365d385c53ab1b835fe4dc29", size = 12896, upload-time = "2026-06-19T16:18:16.917Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ae/22edaf607a167973a5a857ef1c1a1c0b97f232d975953bc2ad6e05bce759/pyobjc_framework_storekit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b90d2187290f6bb4b943a313b7c2adfd4865c11c8173781e6d48c01154b6d9d1", size = 12893, upload-time = "2026-06-19T16:18:18.041Z" }, + { url = "https://files.pythonhosted.org/packages/09/09/9cb870b865d1adb49a910df20b2347ceecd07d906e58b14a5c807bf67e30/pyobjc_framework_storekit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4394366c2d4442c41faadaa030202f4b8a6272b8ec25828a04504f5b40179b6e", size = 12903, upload-time = "2026-06-19T16:18:18.964Z" }, + { url = "https://files.pythonhosted.org/packages/56/7a/931bc925612fd74d09679ec976c45bdb2e41492e63d287594e11e6b1d5ac/pyobjc_framework_storekit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3dab34b774ae217dfff0bc636925cd39c072300aed322e107936f15b9c2184ca", size = 12919, upload-time = "2026-06-19T16:18:19.987Z" }, + { url = "https://files.pythonhosted.org/packages/09/0b/eee8956c1d94824357ac92674ce9e4c96285612a22d290da00aad647291c/pyobjc_framework_storekit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:11a40b897ef4d042079daf2fc4d05fe910d6fc737fdf15d3873875721fcea69a", size = 13117, upload-time = "2026-06-19T16:18:20.999Z" }, + { url = "https://files.pythonhosted.org/packages/2a/2e/5e2badd9e8b75b735dbc5cd37863410a1abe2bdb704b653bd2283a13b7ba/pyobjc_framework_storekit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0d398dd266a050944b7f3b3762820377edd7f4043829ea1be932a3b30896e72e", size = 12908, upload-time = "2026-06-19T16:18:21.932Z" }, + { url = "https://files.pythonhosted.org/packages/e2/f0/af9c8e9a395a69015b0aabbd04853b438692c3a195d136c6f9ffe293a8b9/pyobjc_framework_storekit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:8b4851593b80482e74dfd1c189b5140a79659069fae54aa3c505aaa859114e33", size = 13099, upload-time = "2026-06-19T16:18:22.753Z" }, + { url = "https://files.pythonhosted.org/packages/59/09/eec2af1f269f73d54735b05069ce60cbe4e59db14b49e98d7afa3831b3c0/pyobjc_framework_storekit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:0a958caa5905c3e2de270b09a75cb012a22499943df494cc899fb36b5541a28b", size = 12901, upload-time = "2026-06-19T16:18:23.981Z" }, + { url = "https://files.pythonhosted.org/packages/7d/01/3b2384b06ab47750c8f4ffb93137e265ef72fd1dcbb3035d806bc2f1e1f7/pyobjc_framework_storekit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:ff6b0cd7ed194b248f1d4db50073b85fb6c67574183ae31c9261d94218c8881b", size = 13102, upload-time = "2026-06-19T16:18:24.762Z" }, ] [[package]] name = "pyobjc-framework-symbols" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9a/ce/a48819eb8524fa2dc11fb3dd40bb9c4dcad0596fe538f5004923396c2c6c/pyobjc_framework_symbols-12.1.tar.gz", hash = "sha256:7d8e999b8a59c97d38d1d343b6253b1b7d04bf50b665700957d89c8ac43b9110", size = 12782, upload-time = "2025-11-14T10:22:42.609Z" } +sdist = { url = "https://files.pythonhosted.org/packages/12/1f/6575bd54a71ccae067b56cbe9277b65d095c9a9880c9e059d8d2e845a8ae/pyobjc_framework_symbols-12.2.1.tar.gz", hash = "sha256:c15d32ae7c94e0e95fd83bc2099437a70671b79d0f438a1b0cd1f3eb2cc6f365", size = 14778, upload-time = "2026-06-19T16:21:51.277Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/ea/6e9af9c750d68109ac54fbffb5463e33a7b54ffe8b9901a5b6b603b7884b/pyobjc_framework_symbols-12.1-py2.py3-none-any.whl", hash = "sha256:c72eecbc25f6bfcd39c733067276270057c5aca684be20fdc56def645f2b6446", size = 3331, upload-time = "2025-11-14T10:05:01.333Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ae/163c7af387d5f65cbd55a5814596b1f4cb5c26b28e2e9ad6c2f331cc920b/pyobjc_framework_symbols-12.2.1-py2.py3-none-any.whl", hash = "sha256:95199cb08207680ee36bcfdc4cab5d4c5eaa0ae81e01b1a752066effa6c9b039", size = 3548, upload-time = "2026-06-19T16:18:25.705Z" }, ] [[package]] name = "pyobjc-framework-syncservices" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-coredata" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/21/91/6d03a988831ddb0fb001b13573560e9a5bcccde575b99350f98fe56a2dd4/pyobjc_framework_syncservices-12.1.tar.gz", hash = "sha256:6a213e93d9ce15128810987e4c5de8c73cfab1564ac8d273e6b437a49965e976", size = 31032, upload-time = "2025-11-14T10:22:45.902Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/8d/ad9492c9f0a305e4a1e30968407385cd4bc61051a1a2f9c40677c964fff9/pyobjc_framework_syncservices-12.2.1.tar.gz", hash = "sha256:c351286f14d257e20f8305665825fd73f108c8f0d787e4643818dee5debb3511", size = 34867, upload-time = "2026-06-19T16:21:52.215Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/20/ad93a4c5840da78ee9792756ad6f3dd2abc768d063c1444a8dc2dd990d6e/pyobjc_framework_syncservices-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:21dcb61da6816d2c55afb24d5bbf30741f806c0db8bb7a842fd27177550a3c9c", size = 13380, upload-time = "2025-11-14T10:05:03.318Z" }, - { url = "https://files.pythonhosted.org/packages/4a/9b/25c117f8ffe15aa6cc447da7f5c179627ebafb2b5ec30dfb5e70fede2549/pyobjc_framework_syncservices-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e81a38c2eb7617cb0ecfc4406c1ae2a97c60e95af42e863b2b0f1f6facd9b0da", size = 13380, upload-time = "2025-11-14T10:05:05.814Z" }, - { url = "https://files.pythonhosted.org/packages/54/ac/a83cdd120e279ee905e9085afda90992159ed30c6a728b2c56fa2d36b6ea/pyobjc_framework_syncservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0cd629bea95692aad2d26196657cde2fbadedae252c7846964228661a600b900", size = 13411, upload-time = "2025-11-14T10:05:07.741Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e3/9a6bd76529feffe08a3f6b2962c9a96d75febc02453881ec81389ff9ac13/pyobjc_framework_syncservices-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:606afac9255b5bf828f1dcf7b0d7bdc7726021b686ad4f5743978eb4086902d9", size = 13425, upload-time = "2025-11-14T10:05:09.692Z" }, - { url = "https://files.pythonhosted.org/packages/3b/5d/338850a31968b94417ba95a7b94db9fcd40b16011eaf82f757de7c1eba6c/pyobjc_framework_syncservices-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9d1ebe60e92efd08455be209a265879cf297feda831aadf36431f38229b1dd52", size = 13599, upload-time = "2025-11-14T10:05:11.732Z" }, - { url = "https://files.pythonhosted.org/packages/88/fa/f27f1a706a72c7a87a2aa37e49ae5f5e7445e02323218638e6ff5897c5c9/pyobjc_framework_syncservices-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:2af99db7c23f0368300e8bd428ecfb75b14449d3467e883ff544dbc5ae9e1351", size = 13404, upload-time = "2025-11-14T10:05:13.677Z" }, - { url = "https://files.pythonhosted.org/packages/0c/51/0b135d4af853fabc9a794e78647100503457f9e42e8c0289f745c558c105/pyobjc_framework_syncservices-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:c27754af8cb86bd445e1182a184617229fa70cf3a716e740a93b0622f44ceb27", size = 13585, upload-time = "2025-11-14T10:05:16.03Z" }, + { url = "https://files.pythonhosted.org/packages/22/bc/0a7388e11caa10ff9c8c091a02518fbb03da8cd6af3b7b0bcd0681761dbb/pyobjc_framework_syncservices-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:83f1c2c8688f33cf368dd6557755cab1a73d94cc4f916339d7419b492c144309", size = 13408, upload-time = "2026-06-19T16:18:27.216Z" }, + { url = "https://files.pythonhosted.org/packages/80/84/459e2c058d5d069b825afc722143afed6bc29347110abbc1f19b528835ce/pyobjc_framework_syncservices-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4e2f5e4d2c72c6f4247bd8497a450a09652f84f6990a3ef089345f9a2475134", size = 13408, upload-time = "2026-06-19T16:18:28.336Z" }, + { url = "https://files.pythonhosted.org/packages/37/43/504b6ef68ed22a3546aa42339f7e6c3d6953c856cbe28158ce231611eba3/pyobjc_framework_syncservices-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4cdce34f17d978f349e22dd6604a46bc01c25cd23142b856c393ffd28d6e687a", size = 13442, upload-time = "2026-06-19T16:18:29.174Z" }, + { url = "https://files.pythonhosted.org/packages/da/de/feb58f62fb4a5a2970cb8660a48e4fd8f9480771c66a0c82ec3f87d25418/pyobjc_framework_syncservices-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4a569b68068163df043146b999b606e431b6cb4de4ccd24861031321fa442eda", size = 13455, upload-time = "2026-06-19T16:18:29.976Z" }, + { url = "https://files.pythonhosted.org/packages/ed/af/fd67ce6c70f9329261dff246322f08000fad6f96f940e8f0a3859c85d7ea/pyobjc_framework_syncservices-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:8ade4390f80e4b8d9f94610a3310d1c4cadc195e7db09a9702bec39bd8a0aa8b", size = 13626, upload-time = "2026-06-19T16:18:31.254Z" }, + { url = "https://files.pythonhosted.org/packages/13/7d/78bced3548a0243c4cd0eeb7b4732972b1ee35d9750a1cb47a8e1aed9543/pyobjc_framework_syncservices-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:5aa55b01b440d9c2d3ac55c676b5a8e512d1adab43aec1012c3d0756f415de07", size = 13426, upload-time = "2026-06-19T16:18:32.078Z" }, + { url = "https://files.pythonhosted.org/packages/12/74/65cbe27a99ca7cc8c4b6e30de51a040162a25d2d7ab6f5e3b17c12bdf2a8/pyobjc_framework_syncservices-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:dc0a3bfc317930edde03f76fd776d9aba548a911bd870b8a0b272091d2d3e852", size = 13613, upload-time = "2026-06-19T16:18:33.044Z" }, + { url = "https://files.pythonhosted.org/packages/b2/09/b936f2a9504161a570dd63231c4ebb4a18e822830eaf767abaddc2e3f1b3/pyobjc_framework_syncservices-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:5bc5b8981b29e45af0ec68040dc6313678217e83b556c6c7fd1b26e68e02f2d3", size = 13429, upload-time = "2026-06-19T16:18:33.915Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2a/26c5e0b8256917b0f19e7bd056df0bec241288ae286266331c21d85c40ca/pyobjc_framework_syncservices-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:b10b47d7242b8db20230da090b72b6fe688d513b0ec0d47bb3abff1bcf6b61b5", size = 13611, upload-time = "2026-06-19T16:18:34.941Z" }, ] [[package]] name = "pyobjc-framework-systemconfiguration" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/90/7d/50848df8e1c6b5e13967dee9fb91d3391fe1f2399d2d0797d2fc5edb32ba/pyobjc_framework_systemconfiguration-12.1.tar.gz", hash = "sha256:90fe04aa059876a21626931c71eaff742a27c79798a46347fd053d7008ec496e", size = 59158, upload-time = "2025-11-14T10:22:53.056Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/6f/805ee24f58c13eb593e458ec1f79d94d3415edace02eec7c614ef3518f69/pyobjc_framework_systemconfiguration-12.2.1.tar.gz", hash = "sha256:877a90eafe3df72625e50d61fc9c6dbd40e8cdabab7c4101992090107bb71ddb", size = 63314, upload-time = "2026-06-19T16:21:53.115Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/20/8092f80482d90d7cf9962c53988599ae1df3241a633c5c40bc153bb658fe/pyobjc_framework_systemconfiguration-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:db640a31120e8cd4e47516f5e32a222488809478d6cf4402db506496defd3e18", size = 21667, upload-time = "2025-11-14T10:05:18.484Z" }, - { url = "https://files.pythonhosted.org/packages/1d/7b/9126a7af1b798998837027390a20b981e0298e51c4c55eed6435967145cb/pyobjc_framework_systemconfiguration-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:796390a80500cc7fde86adc71b11cdc41d09507dd69103d3443fbb60e94fb438", size = 21663, upload-time = "2025-11-14T10:05:21.259Z" }, - { url = "https://files.pythonhosted.org/packages/d3/d3/bb935c3d4bae9e6ce4a52638e30eea7039c480dd96bc4f0777c9fabda21b/pyobjc_framework_systemconfiguration-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0e5bb9103d39483964431db7125195c59001b7bff2961869cfe157b4c861e52d", size = 21578, upload-time = "2025-11-14T10:05:25.572Z" }, - { url = "https://files.pythonhosted.org/packages/64/26/22f031c99fd7012dffa41455951004a758aaf9a25216b3a4ee83496bc44f/pyobjc_framework_systemconfiguration-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:359b35c00f52f57834169c1057522279201ac5a64ac5b4d90dbafa40ad6c54b4", size = 21575, upload-time = "2025-11-14T10:05:28.396Z" }, - { url = "https://files.pythonhosted.org/packages/f2/58/648803bdf3d2ebd3221ef43deb008c77aefe0bec231af2aa67e5b29a78e2/pyobjc_framework_systemconfiguration-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f4ff57defb4dcd933db392eb8ea9e5a46005cb7a6f2b46c27ab2dd5e13a459ab", size = 21990, upload-time = "2025-11-14T10:05:30.875Z" }, - { url = "https://files.pythonhosted.org/packages/05/95/9fbb2ab26f03142b84ff577dcd2dcd3ca8b0c13c2f6193ceecd20544b7a5/pyobjc_framework_systemconfiguration-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e9c597c13b9815dce7e1fccdfae7c66b9df98e8c688b7afdf4af39de26d917b3", size = 21612, upload-time = "2025-11-14T10:05:33.387Z" }, - { url = "https://files.pythonhosted.org/packages/0a/67/c1d5ea1089c41f0d1563ab42d6ff6ed320e195646008c8fdaa3e31d354cd/pyobjc_framework_systemconfiguration-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:10ad47ec2bee4f567e78369359b8c75a23097c6d89b11aa37840c22cc79229f1", size = 21997, upload-time = "2025-11-14T10:05:36.211Z" }, + { url = "https://files.pythonhosted.org/packages/c0/af/4fc1e4d48497382e1b3ba03ed7ec6a5cad0cd0de72e56191880d6adc7097/pyobjc_framework_systemconfiguration-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cbac365c8ec5aefb12cb12f75a3d756d769104bd68029f933ced056ae5d8f0c9", size = 21673, upload-time = "2026-06-19T16:18:35.78Z" }, + { url = "https://files.pythonhosted.org/packages/4c/95/50cab59ebfe08dd2c4856da113bb5f74102d915b699c2e3988af3550ccfc/pyobjc_framework_systemconfiguration-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:df9a278948483d40d56a780a99e692b9bdeb2be6279f8f59c96fbd31f620eb72", size = 21674, upload-time = "2026-06-19T16:18:36.836Z" }, + { url = "https://files.pythonhosted.org/packages/1e/7e/f153e2db0cc3724b6cd3c1e939149fb93a6d7b4b9c0ab75a8d7ecdadb02f/pyobjc_framework_systemconfiguration-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9671060ab587eaf485eb8e1d6b328d664ba4a210c4ebffbf7dd2705d741bf90a", size = 21570, upload-time = "2026-06-19T16:18:37.853Z" }, + { url = "https://files.pythonhosted.org/packages/e9/bb/ecff9943e5bd24b78319f683e9219209db502f46a3dff24d1e50bcf9d74c/pyobjc_framework_systemconfiguration-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:06e93a97364681543592bff38f801e2942eb272d68101ccbf3278a8fa043c645", size = 21565, upload-time = "2026-06-19T16:18:38.829Z" }, + { url = "https://files.pythonhosted.org/packages/71/dc/ecc0b5a79d54507be2a3bb8a5088471bbd2a5bd8d2f89dd07538f3bf2551/pyobjc_framework_systemconfiguration-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e3aa4706f81d8334c717d44567314ebbdf03e62259ede99a89f4703789c212a2", size = 21979, upload-time = "2026-06-19T16:18:39.662Z" }, + { url = "https://files.pythonhosted.org/packages/40/f7/8e476085716dce017c92c1b24f66a49d09d5bb86d45a82942df6296086d1/pyobjc_framework_systemconfiguration-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c1327154369fd07dd0b9f02a4fd65068c531ffa99f8e0efbab51f03446750f33", size = 21587, upload-time = "2026-06-19T16:18:40.519Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ee/404d50f15729d0cdc06f89763b089ccf86ef2103776332ac5313a6a31067/pyobjc_framework_systemconfiguration-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:2573755bdc6480470d2cf2cb24c529371bcc3658953ba0efa87e0a6af5b68144", size = 21983, upload-time = "2026-06-19T16:18:41.497Z" }, + { url = "https://files.pythonhosted.org/packages/2d/43/40c9c0d44007c8185d992da1b1d39359acdf701d1c41f14e274bb676c7b9/pyobjc_framework_systemconfiguration-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:150e1b5f35c4badf34b98c56aacc128f631d5084857c86ccccfb4d78e35a7257", size = 21601, upload-time = "2026-06-19T16:18:42.357Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ff/a637bebb60880c96dcad2c888e66d4eb2f6d2609ceea3cfd1fad606384d4/pyobjc_framework_systemconfiguration-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:a1ac9972a24e8900bd09313afe3550579a3e7ef643766f38da2f965e0d9c7e1f", size = 21995, upload-time = "2026-06-19T16:18:43.339Z" }, ] [[package]] name = "pyobjc-framework-systemextensions" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/12/01/8a706cd3f7dfcb9a5017831f2e6f9e5538298e90052db3bb8163230cbc4f/pyobjc_framework_systemextensions-12.1.tar.gz", hash = "sha256:243e043e2daee4b5c46cd90af5fff46b34596aac25011bab8ba8a37099685eeb", size = 20701, upload-time = "2025-11-14T10:22:58.257Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/1b/6edbfef0f03ff67bc22ba03ac7027aee804ea5b16512dd9547dab5786c81/pyobjc_framework_systemextensions-12.2.1.tar.gz", hash = "sha256:4f9f6d729544acfab49fe02a4b38712112c51f07790f8d4bf7ac3bb18c334839", size = 21667, upload-time = "2026-06-19T16:21:53.916Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/df/70194c9aab9052797947967f218ebab56207223cb55eab5ebd89e6e3555c/pyobjc_framework_systemextensions-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:820f2d0364340395efafdfa85630b2e4a3ffc3f40b469b2880bab2c03f1e2907", size = 9157, upload-time = "2025-11-14T10:05:37.902Z" }, - { url = "https://files.pythonhosted.org/packages/ac/a1/f8df6d59e06bc4b5989a76724e8551935e5b99aff6a21d3592e5ced91f1c/pyobjc_framework_systemextensions-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2a4e82160e43c0b1aa17e6d4435e840a655737fbe534e00e37fc1961fbf3bebd", size = 9156, upload-time = "2025-11-14T10:05:39.744Z" }, - { url = "https://files.pythonhosted.org/packages/0a/cc/a42883d6ad0ae257a7fa62660b4dd13be15f8fa657922f9a5b6697f26e28/pyobjc_framework_systemextensions-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:01fac4f8d88c0956d9fc714d24811cd070e67200ba811904317d91e849e38233", size = 9166, upload-time = "2025-11-14T10:05:41.479Z" }, - { url = "https://files.pythonhosted.org/packages/dd/ef/fd34784added1dff088bd18cc2694049b0893b01e835587eab1735fd68f3/pyobjc_framework_systemextensions-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:038032801d46cc7b1ea69400f43d5c17b25d7a16efa7a7d9727b25789387a8cf", size = 9185, upload-time = "2025-11-14T10:05:43.136Z" }, - { url = "https://files.pythonhosted.org/packages/72/76/fd6f06e54299998677548bacd21105450bc6435df215a6620422a31b0099/pyobjc_framework_systemextensions-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:2aea4e823d915abca463b1c091ff969cef09108c88b71b68569485dec6f3651d", size = 9345, upload-time = "2025-11-14T10:05:44.814Z" }, - { url = "https://files.pythonhosted.org/packages/af/c8/4e9669b6b43af7f50df43cb76af84805ee3a9b32881d69b4e7685edd3017/pyobjc_framework_systemextensions-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:51f0a4488fa245695c7e8c1c83909c86bf27b34519807437c753602ff6d7e9af", size = 9253, upload-time = "2025-11-14T10:05:46.508Z" }, - { url = "https://files.pythonhosted.org/packages/18/6e/91e55fa71bd402acbf06ecfc342e4f56dbc0f7d622be1e5dd22d13508d0e/pyobjc_framework_systemextensions-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:b393e3bf85ccb9321f134405eac6fd16a8e7f048286301b67f0cf8d99588bf29", size = 9412, upload-time = "2025-11-14T10:05:48.256Z" }, + { url = "https://files.pythonhosted.org/packages/65/da/d581f6fe91dc4e3414a7b0fab9d0a0117a3cb98f3e48d48d0e8d7be12538/pyobjc_framework_systemextensions-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1c26176274156892d59d9ab5736d818505ed862e22f998e3c3feb5e4599c3932", size = 9210, upload-time = "2026-06-19T16:18:44.255Z" }, + { url = "https://files.pythonhosted.org/packages/c9/30/d50fc19821ae25d43fb0bde681891fa4715d41783f96156dd7a1f77a3ca7/pyobjc_framework_systemextensions-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:21d5993eee958be7f445ec2f0616c4213773cbcd9213346f4ad367037e5acba0", size = 9208, upload-time = "2026-06-19T16:18:45.178Z" }, + { url = "https://files.pythonhosted.org/packages/36/da/a70c451b3ae4cf8387b0f0d709754e8f87db7057f2e0bb5ab0d73f96cc98/pyobjc_framework_systemextensions-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b871a52125eff3c6ea10b769920d64fddcbc26a58bb3759545f914cf0c5cc9fa", size = 9221, upload-time = "2026-06-19T16:18:45.995Z" }, + { url = "https://files.pythonhosted.org/packages/f6/49/20ec0f1239d58a0f8ccaebfc545f27c26904faab81f4e6e7d07efeba1f71/pyobjc_framework_systemextensions-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ded8228991aedcc2b3f9d8c7a0af5f9e9cd9125af562d68817e6ce0bf8a2a713", size = 9239, upload-time = "2026-06-19T16:18:47.155Z" }, + { url = "https://files.pythonhosted.org/packages/8e/94/c6f40110f2da5bbea11f41ed906888aa726bc22b465b39027b1ea5d4c516/pyobjc_framework_systemextensions-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ae954da6267be3d7bafd53ee8357c8b5d3c13dd56359a0d862b69f5cb7eea260", size = 9395, upload-time = "2026-06-19T16:18:48.452Z" }, + { url = "https://files.pythonhosted.org/packages/2e/41/0c7ec750b86f92e4100bfabf9adb121516b0c869fb3da39b4d6214e388b4/pyobjc_framework_systemextensions-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9231d0d14502d51e18a2ba89a4d7f2e310489b0f60e2b951887e8852880563ca", size = 9306, upload-time = "2026-06-19T16:18:49.309Z" }, + { url = "https://files.pythonhosted.org/packages/74/1b/bafbd1f9f53b31f8dcf1f4775206de0cd07fd66d1aecd9b39ba9f9e96507/pyobjc_framework_systemextensions-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a38e9ef6f5fa5f4dbc4d1e6bae8d169f3f0301a52e14de45ad3631033daa4436", size = 9464, upload-time = "2026-06-19T16:18:50.151Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/a36d6951585b12ec57c3d98c103046ebb34bfc5a1c48df0ea2d7b54c3401/pyobjc_framework_systemextensions-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:dfb2884c0fe97893c4b226dd7d95bdec43f7f8a574347a1e80280bbf87230736", size = 9302, upload-time = "2026-06-19T16:18:50.947Z" }, + { url = "https://files.pythonhosted.org/packages/2e/6c/da629b65b470c5eec528e1e8ec703d8654701bd03078bf1e15df508b1950/pyobjc_framework_systemextensions-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:1412d2c62f9df0f52160f7edafcdd608259b6c70925cdb520f45f16a46d2c685", size = 9465, upload-time = "2026-06-19T16:18:52.08Z" }, ] [[package]] name = "pyobjc-framework-threadnetwork" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/62/7e/f1816c3461e4121186f2f7750c58af083d1826bbd73f72728da3edcf4915/pyobjc_framework_threadnetwork-12.1.tar.gz", hash = "sha256:e071eedb41bfc1b205111deb54783ec5a035ccd6929e6e0076336107fdd046ee", size = 12788, upload-time = "2025-11-14T10:23:00.329Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/ae/a2f44eade30d2231938acb81ef049f4172afd6e9acbdff55eca036e75038/pyobjc_framework_threadnetwork-12.2.1.tar.gz", hash = "sha256:98397cf45354750c4b5a1237f6961c616d12f3ad0a570b3808a03e5d3373f64a", size = 13341, upload-time = "2026-06-19T16:21:54.938Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/b8/94b37dd353302c051a76f1a698cf55b5ad50ca061db7f0f332aa9e195766/pyobjc_framework_threadnetwork-12.1-py2.py3-none-any.whl", hash = "sha256:07d937748fc54199f5ec04d5a408e8691a870481c11b641785c2adc279dd8e4b", size = 3771, upload-time = "2025-11-14T10:05:49.899Z" }, + { url = "https://files.pythonhosted.org/packages/6c/cd/822620f1c4f00e24fced4a267113a77d7fafe394d0b516b8c94ff01f62b0/pyobjc_framework_threadnetwork-12.2.1-py2.py3-none-any.whl", hash = "sha256:75afa29eb2884cb9c5ddf5bbc81fed7f45f168422ae897c1a791374152c9ffd0", size = 3827, upload-time = "2026-06-19T16:18:53.181Z" }, ] [[package]] name = "pyobjc-framework-uniformtypeidentifiers" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/65/b8/dd9d2a94509a6c16d965a7b0155e78edf520056313a80f0cd352413f0d0b/pyobjc_framework_uniformtypeidentifiers-12.1.tar.gz", hash = "sha256:64510a6df78336579e9c39b873cfcd03371c4b4be2cec8af75a8a3d07dff607d", size = 17030, upload-time = "2025-11-14T10:23:02.222Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/a1/108fa1e5a3dd8aff626f98fb97de370323b290404b04ffa2ef9420665ed3/pyobjc_framework_uniformtypeidentifiers-12.2.1.tar.gz", hash = "sha256:1fb89d13aa3c2df8e6d6536f6df3493fe5a6caefd2a5adebf17c5af3b29ed4a2", size = 20679, upload-time = "2026-06-19T16:21:55.739Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/5f/1f10f5275b06d213c9897850f1fca9c881c741c1f9190cea6db982b71824/pyobjc_framework_uniformtypeidentifiers-12.1-py2.py3-none-any.whl", hash = "sha256:ec5411e39152304d2a7e0e426c3058fa37a00860af64e164794e0bcffee813f2", size = 4901, upload-time = "2025-11-14T10:05:51.532Z" }, + { url = "https://files.pythonhosted.org/packages/e3/44/18a7b3c3b4f9f6784fddf64ed5a2c148577d0300705a50e8ab81da8fc71d/pyobjc_framework_uniformtypeidentifiers-12.2.1-py2.py3-none-any.whl", hash = "sha256:ea08413ad895a7dfea13670e26548bcf5b00154084cdfb5d8f96603320e77cf3", size = 5042, upload-time = "2026-06-19T16:18:54.085Z" }, ] [[package]] name = "pyobjc-framework-usernotifications" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/90/cd/e0253072f221fa89a42fe53f1a2650cc9bf415eb94ae455235bd010ee12e/pyobjc_framework_usernotifications-12.1.tar.gz", hash = "sha256:019ccdf2d400f9a428769df7dba4ea97c02453372bc5f8b75ce7ae54dfe130f9", size = 29749, upload-time = "2025-11-14T10:23:05.364Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/77/bdd49a1fe4d89ce86078e94121cf6e9c7e2f733215556194da04c512075e/pyobjc_framework_usernotifications-12.2.1.tar.gz", hash = "sha256:64379ab6b603949ea20b7852343cbcff7403443b4876ec8ac0c03bd0f11b1b22", size = 33955, upload-time = "2026-06-19T16:21:56.492Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/58/5d/ef8695e10c5d350d86723830b003acca419a9395928c53beebf7ace4a9a0/pyobjc_framework_usernotifications-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:95796a075e3a92257d69596ec16d9e03cb504f1324294ed41052f5b3bf90ce9f", size = 9628, upload-time = "2025-11-14T10:05:53.319Z" }, - { url = "https://files.pythonhosted.org/packages/d1/96/aa25bb0727e661a352d1c52e7288e25c12fe77047f988bb45557c17cf2d7/pyobjc_framework_usernotifications-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c62e8d7153d72c4379071e34258aa8b7263fa59212cfffd2f137013667e50381", size = 9632, upload-time = "2025-11-14T10:05:55.166Z" }, - { url = "https://files.pythonhosted.org/packages/61/ad/c95053a475246464cba686e16269b0973821601910d1947d088b855a8dac/pyobjc_framework_usernotifications-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:412afb2bf5fe0049f9c4e732e81a8a35d5ebf97c30a5a6abd276259d020c82ac", size = 9644, upload-time = "2025-11-14T10:05:56.801Z" }, - { url = "https://files.pythonhosted.org/packages/b1/cc/4c6efe6a65b1742ea238734f81509ceba5346b45f605baa809ca63f30692/pyobjc_framework_usernotifications-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:40a5457f4157ca007f80f0644413f44f0dc141f7864b28e1728623baf56a8539", size = 9659, upload-time = "2025-11-14T10:05:58.763Z" }, - { url = "https://files.pythonhosted.org/packages/06/4e/02ff6975567974f360cf0e1e358236026e35f7ba7795511bc4dcbaa13f62/pyobjc_framework_usernotifications-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:58c09bd1bd7a8cd29613d0d0e6096eda6c8465dc5a7a733675e1b8d0406f7adc", size = 9811, upload-time = "2025-11-14T10:06:00.775Z" }, - { url = "https://files.pythonhosted.org/packages/cd/1a/caa96066b36c2c20ba6f033857fc24ff8e6b5811cf1bc112818928d27216/pyobjc_framework_usernotifications-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:cc69e2aed9b55296a447f2fb69cc52a1a026c50e46253dbf482f5807bce3ae7c", size = 9720, upload-time = "2025-11-14T10:06:02.409Z" }, - { url = "https://files.pythonhosted.org/packages/95/f7/8def35e9e7b2a7a7d4e61923b0f29fcdca70df5ac6b91cddb418a1d5ffed/pyobjc_framework_usernotifications-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0746d2a67ca05ae907b7551ccd3a534e9d6e76115882ab962365f9ad259c4032", size = 9876, upload-time = "2025-11-14T10:06:04.07Z" }, + { url = "https://files.pythonhosted.org/packages/5c/35/ee4eebbec76f7da3dabf24d9552dd40624996b84f7c4364a352c09ec5d77/pyobjc_framework_usernotifications-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:46a3921f48fbe76eab33d8038f53fd791b0febab368cf3a30bcce5d2b26f47c2", size = 10194, upload-time = "2026-06-19T16:18:55.076Z" }, + { url = "https://files.pythonhosted.org/packages/d3/a8/4de5e87308d3803b4c49acabf885d408ee490d2d019ad224c0f72da2235f/pyobjc_framework_usernotifications-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74e7e7af3bd5842502ee553f4d0ec566f698195bb89ae925c91441c86a0aa5da", size = 10195, upload-time = "2026-06-19T16:18:56.111Z" }, + { url = "https://files.pythonhosted.org/packages/97/2d/c40189560f8db4ab9f29a9c79eb7b2d89544d80a27252de5112ebcabdd4b/pyobjc_framework_usernotifications-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:12fab51085e4796e04783e457d9355b805afbd1cb13bf9d207e5fb7e1a32fb36", size = 10211, upload-time = "2026-06-19T16:18:56.988Z" }, + { url = "https://files.pythonhosted.org/packages/21/71/cff5abc272742148f086cf00b5625e6a7f23171dd7b23d075f16a26e47c9/pyobjc_framework_usernotifications-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:273ef82c7ddc1701d56b586cde2615360618c1b2d17fb5c24bac77a5f02b6092", size = 10221, upload-time = "2026-06-19T16:18:57.775Z" }, + { url = "https://files.pythonhosted.org/packages/69/54/75b4c4c15dd4f4f3c190424244ca35976e3a29c51740b7648f3fa78eb73a/pyobjc_framework_usernotifications-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e872d65dc71bc297401819106b5e5c4b758aa38247d74284bce20e63a2bdbfa1", size = 10379, upload-time = "2026-06-19T16:18:58.507Z" }, + { url = "https://files.pythonhosted.org/packages/36/00/7a7c79a3700bcc24c9cabfac984ae38ddcfef72e6564aeaf4c3237009279/pyobjc_framework_usernotifications-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b43a46b4a95bdbf0d2acf1ec0d709b376c4149c618693a05745485c741ad4f28", size = 10283, upload-time = "2026-06-19T16:18:59.418Z" }, + { url = "https://files.pythonhosted.org/packages/d2/81/dda0b17f8761bf9aec3e1eb26aedb5590a51946ff4e8ef62e12870c639fd/pyobjc_framework_usernotifications-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4050919639556e63854e9965d942f928745ac346b52b0cb7acd3823b8a9af153", size = 10444, upload-time = "2026-06-19T16:19:00.341Z" }, + { url = "https://files.pythonhosted.org/packages/4c/bd/4b6d4d7aea2f1b99f73548f01f964769e04574e0bd823afad003736290e1/pyobjc_framework_usernotifications-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:0811da6cf3e4b5dce91ad61888204b08281b86c889c4241309f55bcf996bce03", size = 10280, upload-time = "2026-06-19T16:19:01.116Z" }, + { url = "https://files.pythonhosted.org/packages/72/8c/fcaadbea1daa56e73aef844f89e2534b51b04e61881e2b6cef0727e0f72f/pyobjc_framework_usernotifications-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:fc70d0f149716b8488934921a28e96716d2db7eeac4bcb1043b970c52d551613", size = 10445, upload-time = "2026-06-19T16:19:01.904Z" }, ] [[package]] name = "pyobjc-framework-usernotificationsui" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, { name = "pyobjc-framework-usernotifications" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0e/03/73e29fd5e5973cb3800c9d56107c1062547ef7524cbcc757c3cbbd5465c6/pyobjc_framework_usernotificationsui-12.1.tar.gz", hash = "sha256:51381c97c7344099377870e49ed0871fea85ba50efe50ab05ccffc06b43ec02e", size = 13125, upload-time = "2025-11-14T10:23:07.259Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a8/9a/566813aed69566c68045fc080b2238f62df131826d16da42529e89d56434/pyobjc_framework_usernotificationsui-12.2.1.tar.gz", hash = "sha256:ea6aecea828e088416aa6d14055c023cac6aa03ea0cdded8baba679ce5414cc8", size = 13457, upload-time = "2026-06-19T16:21:57.294Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/c8/52ac8a879079c1fbf25de8335ff506f7db87ff61e64838b20426f817f5d5/pyobjc_framework_usernotificationsui-12.1-py2.py3-none-any.whl", hash = "sha256:11af59dc5abfcb72c08769ab4d7ca32a628527a8ba341786431a0d2dacf31605", size = 3933, upload-time = "2025-11-14T10:06:05.478Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4d/ae257ba381d779ff760eea94fc12df5aded3bbf9da4304f66f70962ad68c/pyobjc_framework_usernotificationsui-12.2.1-py2.py3-none-any.whl", hash = "sha256:25464587228e128758a1ea9d8b0abdb872d2a957d1c554d791796a39ddc0e4ce", size = 3953, upload-time = "2026-06-19T16:19:02.794Z" }, ] [[package]] name = "pyobjc-framework-videosubscriberaccount" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/10/f8/27927a9c125c622656ee5aada4596ccb8e5679da0260742360f193df6dcf/pyobjc_framework_videosubscriberaccount-12.1.tar.gz", hash = "sha256:750459fa88220ab83416f769f2d5d210a1f77b8938fa4d119aad0002fc32846b", size = 18793, upload-time = "2025-11-14T10:23:09.33Z" } +sdist = { url = "https://files.pythonhosted.org/packages/84/4a/b3485f9e123b05a44852f07ca9387d8ad719a3ecbe0a10e2d2f726a460ff/pyobjc_framework_videosubscriberaccount-12.2.1.tar.gz", hash = "sha256:7af53b410d3943be09d8601bd8d50fd0e193e4e5577fdaa2f801d7f9dbc0454d", size = 21346, upload-time = "2026-06-19T16:21:58.123Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/41/ca/e2f982916267508c1594f1e50d27bf223a24f55a5e175ab7d7822a00997c/pyobjc_framework_videosubscriberaccount-12.1-py2.py3-none-any.whl", hash = "sha256:381a5e8a3016676e52b88e38b706559fa09391d33474d8a8a52f20a883104a7b", size = 4825, upload-time = "2025-11-14T10:06:07.027Z" }, + { url = "https://files.pythonhosted.org/packages/38/78/4dc23b84c668c10dfde26c861c697b4bd682d1d137422a0546e742c08455/pyobjc_framework_videosubscriberaccount-12.2.1-py2.py3-none-any.whl", hash = "sha256:ac43567833a4aa21fec81d39449d080b2cd58d6b02de76b57004067bdf37ba76", size = 4890, upload-time = "2026-06-19T16:19:03.85Z" }, ] [[package]] name = "pyobjc-framework-videotoolbox" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, @@ -4025,39 +4259,43 @@ dependencies = [ { name = "pyobjc-framework-coremedia" }, { name = "pyobjc-framework-quartz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b3/5f/6995ee40dc0d1a3460ee183f696e5254c0ad14a25b5bc5fd9bd7266c077b/pyobjc_framework_videotoolbox-12.1.tar.gz", hash = "sha256:7adc8670f3b94b086aed6e86c3199b388892edab4f02933c2e2d9b1657561bef", size = 57825, upload-time = "2025-11-14T10:23:13.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/33/82/307369b27b00b38cf6b6e021fcdce0ae6f1a91f24fed9b090addbe90f1e2/pyobjc_framework_videotoolbox-12.2.1.tar.gz", hash = "sha256:83582abc25e55ed04f0267fa69923839d779a11da3d34ee8c93ad1a66439e48d", size = 64995, upload-time = "2026-06-19T16:21:59.115Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/56/11fb89e9d10c31101c7ec69978e4637a3400e2154851c0f7c7180ff94f07/pyobjc_framework_videotoolbox-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5cb63e0e69aac148fa45d577f049e1e4846d65d046fcb0f7744fb90ac85da936", size = 18782, upload-time = "2025-11-14T10:06:09.318Z" }, - { url = "https://files.pythonhosted.org/packages/1e/42/53d57b09fd4879988084ec0d9b74c645c9fdd322be594c9601f6cf265dd0/pyobjc_framework_videotoolbox-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a1eb1eb41c0ffdd8dcc6a9b68ab2b5bc50824a85820c8a7802a94a22dfbb4f91", size = 18781, upload-time = "2025-11-14T10:06:11.89Z" }, - { url = "https://files.pythonhosted.org/packages/94/a5/91c6c95416f41c412c2079950527cb746c0712ec319c51a6c728c8d6b231/pyobjc_framework_videotoolbox-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eb6ce6837344ee319122066c16ada4beb913e7bfd62188a8d14b1ecbb5a89234", size = 18908, upload-time = "2025-11-14T10:06:14.087Z" }, - { url = "https://files.pythonhosted.org/packages/f0/59/7fc3d67df437f3e263b477dd181eef3ac3430cb7eb1acc951f5f1e84cc4d/pyobjc_framework_videotoolbox-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca28b39e22016eb5f81f540102a575ee6e6114074d09e17e22eb3b5647976d93", size = 18929, upload-time = "2025-11-14T10:06:16.418Z" }, - { url = "https://files.pythonhosted.org/packages/f4/41/08b526d2f228271994f8216651d2e5c8e76415224daa012e67c53c90fc7a/pyobjc_framework_videotoolbox-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:dba7e078df01432331ee75a90c2c147264bfdb9e31998b4e4fc28913b93b832e", size = 19139, upload-time = "2025-11-14T10:06:18.602Z" }, - { url = "https://files.pythonhosted.org/packages/00/a9/581edc658e3ae242a55d463092a237cf9f744ba5a91d91c769af7d3f2ac6/pyobjc_framework_videotoolbox-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e67a3890916346b7c15c9270d247e191c3899e4698fee79d460a476145715401", size = 18927, upload-time = "2025-11-14T10:06:20.834Z" }, - { url = "https://files.pythonhosted.org/packages/91/17/97f3e4704246b0496c90bf4c604005f426f62c75e616e68d2e3f8833affb/pyobjc_framework_videotoolbox-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:67227431c340e308c4ecdce743b5d1d27757994663c983f179f2e934acdacb99", size = 19121, upload-time = "2025-11-14T10:06:23.072Z" }, + { url = "https://files.pythonhosted.org/packages/3d/63/1dfef836f44d2e55e723d7d88e6f9a70ef3b5cbdff3267f2d0aaf69a18f2/pyobjc_framework_videotoolbox-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e298b94093faecb68edfcf8a38ff633d9cba7f79cbdde5e9baa3607862ae8879", size = 18868, upload-time = "2026-06-19T16:19:04.846Z" }, + { url = "https://files.pythonhosted.org/packages/43/0c/8693571c03dacaf86a21c79035dbc36e0fd39422f55251b8d1ed5c5dc0b2/pyobjc_framework_videotoolbox-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eed49d96127b0a52afe88a78ee3ea28cbc39c8d274cab1c921cc64a46201b0d6", size = 18877, upload-time = "2026-06-19T16:19:05.803Z" }, + { url = "https://files.pythonhosted.org/packages/f0/64/804ef9bda687dd3ead9ecd9e2fe29842a5acaee95bfa5f5d6932716090e9/pyobjc_framework_videotoolbox-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:67e03405bf7ea4790688c3937c5838dd8e1d25aa95923a7bb71ba3c49031d1af", size = 19004, upload-time = "2026-06-19T16:19:06.641Z" }, + { url = "https://files.pythonhosted.org/packages/9d/eb/771c0564a82e4e9ff8a4369100d6a3720824ac65a8e899c70da970e89ed4/pyobjc_framework_videotoolbox-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:efead1dc80cfd7612b952e831bb1e4b3ce9b13e6e4848ee82c28fcd5eaf718d3", size = 19024, upload-time = "2026-06-19T16:19:07.52Z" }, + { url = "https://files.pythonhosted.org/packages/a7/e7/0725ee9ad4576c432fcc38f90c78b0f377034aa71460420d4dde649a1d00/pyobjc_framework_videotoolbox-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:45bb85a114280763e7b4b5f9f726be1b0ed97041ef3816e77fee2268211d6732", size = 19231, upload-time = "2026-06-19T16:19:08.338Z" }, + { url = "https://files.pythonhosted.org/packages/68/54/65e0076b81322d53b1235fab0e180be212a906cbe1f0e94c4875dfa052f5/pyobjc_framework_videotoolbox-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0fb2e7be0568af5864077be2856a88121667f839e96304907b05a4b6ecd770e0", size = 19017, upload-time = "2026-06-19T16:19:09.238Z" }, + { url = "https://files.pythonhosted.org/packages/da/79/3685cc3c4ec52086a5a1e26cf3574e2c7f84214a86cdd1e1f19c21ef616f/pyobjc_framework_videotoolbox-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:eb0d58dc6ce118c5e8610fc76112a769a02fb6ed201e4f89c655ca1ac75218be", size = 19215, upload-time = "2026-06-19T16:19:10.088Z" }, + { url = "https://files.pythonhosted.org/packages/e5/b6/b956d04ddd26a8b07d3df0a49f0352cc773203a14e3fdcd98edf2f42d024/pyobjc_framework_videotoolbox-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:37d70510fb35f85a3ef4e37047e0ed6008e78bc83728ece763efb87d2919ca75", size = 19025, upload-time = "2026-06-19T16:19:10.945Z" }, + { url = "https://files.pythonhosted.org/packages/b6/80/ffb0ba5ad6911fe66e7faa8901cbbb580facc9f41ed05cbb41751ebe2512/pyobjc_framework_videotoolbox-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:0339694a3caaa3a6415a5c845b812caedaecba8be9b322c17777d9e4258ff203", size = 19215, upload-time = "2026-06-19T16:19:11.995Z" }, ] [[package]] name = "pyobjc-framework-virtualization" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3b/6a/9d110b5521d9b898fad10928818c9f55d66a4af9ac097426c65a9878b095/pyobjc_framework_virtualization-12.1.tar.gz", hash = "sha256:e96afd8e801e92c6863da0921e40a3b68f724804f888bce43791330658abdb0f", size = 40682, upload-time = "2025-11-14T10:23:17.456Z" } +sdist = { url = "https://files.pythonhosted.org/packages/78/49/5c306fac7b85c4f875b01f38266b75b9b8ba80a9747bfcc692c9b83adffb/pyobjc_framework_virtualization-12.2.1.tar.gz", hash = "sha256:dd752180219ddc54112876576debca9f3316e91ce75afce622981eeb8c9a0f4a", size = 49190, upload-time = "2026-06-19T16:22:00.154Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/64/dfb1ba93ecbbde95e9cd8fe06842d2114f3af7506eff47d97a547d4a181a/pyobjc_framework_virtualization-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9a5f565411330c5776b60eb5eb94ab1591f76f0969e85b23a046d2de915fc84e", size = 13101, upload-time = "2025-11-14T10:06:24.973Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ee/e18d0d9014c42758d7169144acb2d37eb5ff19bf959db74b20eac706bd8c/pyobjc_framework_virtualization-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a88a307dc96885afc227ceda4067f1af787f024063f4ccf453d59e7afd47cda8", size = 13099, upload-time = "2025-11-14T10:06:27.403Z" }, - { url = "https://files.pythonhosted.org/packages/c6/f2/0da47e91f3f8eeda9a8b4bb0d3a0c54a18925009e99b66a8226b9e06ce1e/pyobjc_framework_virtualization-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7d5724b38e64b39ab5ec3b45993afa29fc88b307d99ee2c7a1c0fd770e9b4b21", size = 13131, upload-time = "2025-11-14T10:06:29.337Z" }, - { url = "https://files.pythonhosted.org/packages/76/ca/228fffccbeafecbe7599fc2cdaa64bf2a8e42fd8fe619c5b670c92b263c3/pyobjc_framework_virtualization-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:232956de8a0c3086a58c96621e0a2148497d1750ebb1bb6bea9f7f34ec3c83c6", size = 13147, upload-time = "2025-11-14T10:06:31.294Z" }, - { url = "https://files.pythonhosted.org/packages/b3/2f/4e56147bc9963bb7f96886fda376004a66c5abe579dc029180952fd872fa/pyobjc_framework_virtualization-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a9552e49b967fb520e5be1cfce510e0b68c2ba314a28ac90aad36fe33218d430", size = 13351, upload-time = "2025-11-14T10:06:33.189Z" }, - { url = "https://files.pythonhosted.org/packages/72/4f/ed32bb177edca9feedd518aa2f98c75e86365497f086af21d807785d264c/pyobjc_framework_virtualization-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e40bff972adfefbe8a02e508571b32c58e90e4d974d65470eab75c53fe47006d", size = 13137, upload-time = "2025-11-14T10:06:35.426Z" }, - { url = "https://files.pythonhosted.org/packages/3b/01/fc9a7714bd3d9d43085c7c027c395b9c0205a330956f200bfa3c41b09a82/pyobjc_framework_virtualization-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:8d53e81f1928c4e90cbebebd39b965aa679f7fadda1fd075e18991872c4cb56b", size = 13343, upload-time = "2025-11-14T10:06:37.219Z" }, + { url = "https://files.pythonhosted.org/packages/d4/55/3a26a21d189718a361792b032e013dd1deac9c44bcc6919dd74329a9d5f5/pyobjc_framework_virtualization-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f3baa3115c38b44d12fb34ba3cf9c54e247385cb9f7e59db169bcfc970999848", size = 13591, upload-time = "2026-06-19T16:19:12.952Z" }, + { url = "https://files.pythonhosted.org/packages/9d/4a/eb0b108eb7720f647a26bbf6a477cec09f9138df93e470473fb35f96df0a/pyobjc_framework_virtualization-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0137793bee5c628b6f9e44a773e49e3f68f758ef5b08ce407a9910b907ee6092", size = 13592, upload-time = "2026-06-19T16:19:14.052Z" }, + { url = "https://files.pythonhosted.org/packages/f4/0a/d5e4670a5b12fa3150b25b7f7a7694a72d8895527d33c0e33d63753a7086/pyobjc_framework_virtualization-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6c90011e2f90dd5593996cb80f9c4739bd4a12db7303086678217a08909ee045", size = 13626, upload-time = "2026-06-19T16:19:15.082Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d3/fb9b5f4d457715d165293cf7112d7405128ab461c957c7dbe19d4dba5ca3/pyobjc_framework_virtualization-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee6423823b4b851d76567e44c649493eff6cda7ada94a86942a4c706f3e22f36", size = 13642, upload-time = "2026-06-19T16:19:16.051Z" }, + { url = "https://files.pythonhosted.org/packages/20/8c/146a46d88eaa8071f36c28c759790ba853b5d4630d53f22d8d74a871f13b/pyobjc_framework_virtualization-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:862c7d9c8ddb47eb086bb55271459e6107be904c526bdd3d520787a89d4e6c72", size = 13841, upload-time = "2026-06-19T16:19:16.928Z" }, + { url = "https://files.pythonhosted.org/packages/81/84/03fd75be82d47a27ca16bf8c5b467a78322a5e3692b5d76bd67b14d27a3e/pyobjc_framework_virtualization-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:047ae558051db4682f42f8d18f3391aa454ae5b64ef676c4e7580323aa457a87", size = 13628, upload-time = "2026-06-19T16:19:17.814Z" }, + { url = "https://files.pythonhosted.org/packages/81/5e/587ebffbd4f9781b88fc18607f10ba17510df40fc79cb6660a4a9a6c64cb/pyobjc_framework_virtualization-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:503cbd9b7d995500a6dcaa0c3d131c41d3b694a9789d396465205fdf0090a130", size = 13836, upload-time = "2026-06-19T16:19:18.67Z" }, + { url = "https://files.pythonhosted.org/packages/b0/07/455cf0cdae517ab0b879621b5c48ee81228d9305fa995ce982be5f4f0c55/pyobjc_framework_virtualization-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:363f43c0a96c41d4cb1430c9faa5cc7c8b5cdaa2c66bc0be93e904c8611a5feb", size = 13621, upload-time = "2026-06-19T16:19:19.487Z" }, + { url = "https://files.pythonhosted.org/packages/47/96/53ca0653a397196b2ba4969d6f0820102336de4d420b3311050b4630eff8/pyobjc_framework_virtualization-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:ab87f688824575e0e09dcb0672f59ca76d468c55c229e34a356d4ee5ccc757b9", size = 13832, upload-time = "2026-06-19T16:19:20.337Z" }, ] [[package]] name = "pyobjc-framework-vision" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, @@ -4065,34 +4303,38 @@ dependencies = [ { name = "pyobjc-framework-coreml" }, { name = "pyobjc-framework-quartz" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c2/5a/08bb3e278f870443d226c141af14205ff41c0274da1e053b72b11dfc9fb2/pyobjc_framework_vision-12.1.tar.gz", hash = "sha256:a30959100e85dcede3a786c544e621ad6eb65ff6abf85721f805822b8c5fe9b0", size = 59538, upload-time = "2025-11-14T10:23:21.979Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/7a/1fdffff1b6bf124b260a2169869f4b71a08b9f6603698f7dec990d5ae5f3/pyobjc_framework_vision-12.2.1.tar.gz", hash = "sha256:debfd59dd7d962a6053bf733370148c11a9ec44091b517a0966f48d81c305879", size = 72683, upload-time = "2026-06-19T16:22:01.102Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/48/b23e639a66e5d3d944710bb2eaeb7257c18b0834dffc7ea2ddadadf8620e/pyobjc_framework_vision-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a30c3fff926348baecc3ce1f6da8ed327d0cbd55ca1c376d018e31023b79c0ab", size = 21432, upload-time = "2025-11-14T10:06:39.709Z" }, - { url = "https://files.pythonhosted.org/packages/bd/37/e30cf4eef2b4c7e20ccadc1249117c77305fbc38b2e5904eb42e3753f63c/pyobjc_framework_vision-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1edbf2fc18ce3b31108f845901a88f2236783ae6bf0bc68438d7ece572dc2a29", size = 21432, upload-time = "2025-11-14T10:06:42.373Z" }, - { url = "https://files.pythonhosted.org/packages/3a/5a/23502935b3fc877d7573e743fc3e6c28748f33a45c43851d503bde52cde7/pyobjc_framework_vision-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6b3211d84f3a12aad0cde752cfd43a80d0218960ac9e6b46b141c730e7d655bd", size = 16625, upload-time = "2025-11-14T10:06:44.422Z" }, - { url = "https://files.pythonhosted.org/packages/f5/e4/e87361a31b82b22f8c0a59652d6e17625870dd002e8da75cb2343a84f2f9/pyobjc_framework_vision-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7273e2508db4c2e88523b4b7ff38ac54808756e7ba01d78e6c08ea68f32577d2", size = 16640, upload-time = "2025-11-14T10:06:46.653Z" }, - { url = "https://files.pythonhosted.org/packages/b1/dd/def55d8a80b0817f486f2712fc6243482c3264d373dc5ff75037b3aeb7ea/pyobjc_framework_vision-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:04296f0848cc8cdead66c76df6063720885cbdf24fdfd1900749a6e2297313db", size = 16782, upload-time = "2025-11-14T10:06:48.816Z" }, - { url = "https://files.pythonhosted.org/packages/a7/a4/ee1ef14d6e1df6617e64dbaaa0ecf8ecb9e0af1425613fa633f6a94049c1/pyobjc_framework_vision-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:631add775ed1dafb221a6116137cdcd78432addc16200ca434571c2a039c0e03", size = 16614, upload-time = "2025-11-14T10:06:50.852Z" }, - { url = "https://files.pythonhosted.org/packages/af/53/187743d9244becd4499a77f8ee699ae286e2f6ade7c0c7ad2975ae60f187/pyobjc_framework_vision-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:fe41a1a70cc91068aee7b5293fa09dc66d1c666a8da79fdf948900988b439df6", size = 16771, upload-time = "2025-11-14T10:06:53.04Z" }, + { url = "https://files.pythonhosted.org/packages/2f/95/f3654046937fa1de8bf2998bfcaa2ce2da05676cce059ed0760073fd21a5/pyobjc_framework_vision-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c9792afbc3ba16483095d9a90c3f7a562383a6d7e86d2783824fab9b4b562219", size = 21792, upload-time = "2026-06-19T16:19:21.332Z" }, + { url = "https://files.pythonhosted.org/packages/f5/dc/a4043619a8c1bae2ef0463ebf88b3d2d5973ad5809dcc5d2d5fc93f34151/pyobjc_framework_vision-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:86f221d7ced483e7292194293b791a255e0bd1e4011048533501679e378d40fd", size = 21794, upload-time = "2026-06-19T16:19:22.356Z" }, + { url = "https://files.pythonhosted.org/packages/3c/1b/ee3aa7d1517d2d161cd9c2d00b9fc19206d2472b4bb35e98835ec9992d02/pyobjc_framework_vision-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fa2e56d891eab12a0ddac2373c69957c108f70dea56f5a9a6081a3c2f905c98b", size = 16947, upload-time = "2026-06-19T16:19:23.209Z" }, + { url = "https://files.pythonhosted.org/packages/e4/a0/1bc00a6b6031b7d6985a0547d2b41ed4ea029c3ffbfe31c8040bb14f6674/pyobjc_framework_vision-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a041188213ae84153d5fe74cd69468827c150cb7157e2649e40a2a3cedb8f55d", size = 16963, upload-time = "2026-06-19T16:19:23.99Z" }, + { url = "https://files.pythonhosted.org/packages/59/af/e6618858bd8f9be6c58ea7238b7ec224d1a31df506dd912c41672fe4f369/pyobjc_framework_vision-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e288cb41349d6e84cfac0822a0c1fb476bf5fa094913b19e8c2899e90a1a9e8f", size = 17105, upload-time = "2026-06-19T16:19:24.817Z" }, + { url = "https://files.pythonhosted.org/packages/f1/76/67d7098ab8e3d55b24425feccf452620f234d558400d9e5eb7ca56c60a9d/pyobjc_framework_vision-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:da4f6811c23bdfa701ed727d6e46a83729606476cee435c2461f0f25579b8080", size = 16939, upload-time = "2026-06-19T16:19:25.627Z" }, + { url = "https://files.pythonhosted.org/packages/97/99/9b46821533d1b138e69a230cc17ee6a7be24bcb6093daf6ab2096fdc21d5/pyobjc_framework_vision-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a319b6e809ac03c41fbbcb1b8e449c8bc9b6e2b0e06c3c67cbe4ce8e040a1f78", size = 17097, upload-time = "2026-06-19T16:19:26.538Z" }, + { url = "https://files.pythonhosted.org/packages/39/6d/74e275165801d0309f9915b0def9b73f546b1ab378892f86e5bb26a2f2d3/pyobjc_framework_vision-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:2dd7ce3563afbbcbc10d9bb5e777b3d0cd77d4d5a9f7b0c32d0f65eeb5d34f0f", size = 16927, upload-time = "2026-06-19T16:19:27.351Z" }, + { url = "https://files.pythonhosted.org/packages/d0/39/e2b0286125ddbe475e74de668b470b8d9bec7a26cd95de70178f8b382ca4/pyobjc_framework_vision-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:5aafb8fd87b5580b98a9f4a6de18013fd000f78a5f0930b2160fa82775de84fd", size = 17097, upload-time = "2026-06-19T16:19:28.232Z" }, ] [[package]] name = "pyobjc-framework-webkit" -version = "12.1" +version = "12.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core" }, { name = "pyobjc-framework-cocoa" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/14/10/110a50e8e6670765d25190ca7f7bfeecc47ec4a8c018cb928f4f82c56e04/pyobjc_framework_webkit-12.1.tar.gz", hash = "sha256:97a54dd05ab5266bd4f614e41add517ae62cdd5a30328eabb06792474b37d82a", size = 284531, upload-time = "2025-11-14T10:23:40.287Z" } +sdist = { url = "https://files.pythonhosted.org/packages/11/d2/b230c594f70ecb970b4cef67bae2648d1bfa5b381e9b7e3710bf24ec8887/pyobjc_framework_webkit-12.2.1.tar.gz", hash = "sha256:a56acae55b50d549b20dff2921ad1099add8fbc377d0de09ddc2ba50957f7def", size = 332374, upload-time = "2026-06-19T16:22:01.988Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/79/b5582113b28cae64cec4aca63d36620421c21ca52f3897388b865a0dbb86/pyobjc_framework_webkit-12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:231048d250e97323b25e5f1690d09e2415b691c0d57bc13241e442d486ef94c8", size = 49971, upload-time = "2025-11-14T10:06:57.155Z" }, - { url = "https://files.pythonhosted.org/packages/e5/37/5082a0bbe12e48d4ffa53b0c0f09c77a4a6ffcfa119e26fa8dd77c08dc1c/pyobjc_framework_webkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3db734877025614eaef4504fadc0fbbe1279f68686a6f106f2e614e89e0d1a9d", size = 49970, upload-time = "2025-11-14T10:07:01.413Z" }, - { url = "https://files.pythonhosted.org/packages/db/67/64920c8d201a7fc27962f467c636c4e763b43845baba2e091a50a97a5d52/pyobjc_framework_webkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:af2c7197447638b92aafbe4847c063b6dd5e1ed83b44d3ce7e71e4c9b042ab5a", size = 50084, upload-time = "2025-11-14T10:07:05.868Z" }, - { url = "https://files.pythonhosted.org/packages/7a/3d/80d36280164c69220ce99372f7736a028617c207e42cb587716009eecb88/pyobjc_framework_webkit-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1da0c428c9d9891c93e0de51c9f272bfeb96d34356cdf3136cb4ad56ce32ec2d", size = 50096, upload-time = "2025-11-14T10:07:10.027Z" }, - { url = "https://files.pythonhosted.org/packages/8a/7a/03c29c46866e266b0c705811c55c22625c349b0a80f5cf4776454b13dc4c/pyobjc_framework_webkit-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:1a29e334d5a7dd4a4f0b5647481b6ccf8a107b92e67b2b3c6b368c899f571965", size = 50572, upload-time = "2025-11-14T10:07:14.232Z" }, - { url = "https://files.pythonhosted.org/packages/3b/ac/924878f239c167ffe3bfc643aee4d6dd5b357e25f6b28db227e40e9e6df3/pyobjc_framework_webkit-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:99d0d28542a266a95ee2585f51765c0331794bca461aaf4d1f5091489d475179", size = 50210, upload-time = "2025-11-14T10:07:18.926Z" }, - { url = "https://files.pythonhosted.org/packages/2d/86/637cda4983dc0936b73a385f3906256953ac434537b812814cb0b6d231a2/pyobjc_framework_webkit-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1aaa3bf12c7b68e1a36c0b294d2728e06f2cc220775e6dc4541d5046290e4dc8", size = 50680, upload-time = "2025-11-14T10:07:23.331Z" }, + { url = "https://files.pythonhosted.org/packages/16/34/ec01564ac00165736c76481ea31a0a644c2b9fafac39e7fca827da2c4a34/pyobjc_framework_webkit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ad59030aeb9bb11d28a939760cb64b93108b6a8508c6001cf87789fd7a9312bd", size = 50260, upload-time = "2026-06-19T16:19:29.532Z" }, + { url = "https://files.pythonhosted.org/packages/f0/d3/2ab99d3975dd4624dd943e5a7c8d37e40258d3c9fcf4f26baf09a24e6c9b/pyobjc_framework_webkit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:af5c4ccdf03845adac082823a3b4341b5b2fe62d2d664550afa705b5286a06fc", size = 50264, upload-time = "2026-06-19T16:19:30.607Z" }, + { url = "https://files.pythonhosted.org/packages/84/47/7a2099eb2e062c6230a9440f1795cf34056ca5e16ef25c8aad7c059b8734/pyobjc_framework_webkit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7e04dcc08cdc59380113ea1232af75a0a04c2426418ebe967b4c0045c973f776", size = 50372, upload-time = "2026-06-19T16:19:31.581Z" }, + { url = "https://files.pythonhosted.org/packages/2a/a4/202ec288808011d3f459d000d593e88b1118f2d1d5a4dfaaf5232f2c2ac2/pyobjc_framework_webkit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:23bee8bf7077f91da4e3ae54a00c7f5e4414319e15f98be8584dbd67c4043fae", size = 50387, upload-time = "2026-06-19T16:19:32.522Z" }, + { url = "https://files.pythonhosted.org/packages/95/a4/f796e94b43a66704b6ae17c747c7b97fd4b79348f1cfa9bef7b008aaa718/pyobjc_framework_webkit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:00ffb254f97e9ffdd0a82c1faa61a07f6072ba900fa8aba70c83c21198b52e4e", size = 50853, upload-time = "2026-06-19T16:19:33.43Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f6/d24716fef19ccc3d880e99029458803f0174c05df310d991eb97ea3a0799/pyobjc_framework_webkit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:67030258c3cd66e8495ccfccef3d2d58010ff0209284c5115e5afdb0e9fd6de1", size = 50499, upload-time = "2026-06-19T16:19:34.45Z" }, + { url = "https://files.pythonhosted.org/packages/a8/6c/817119a52efcc229a30ceff56a0641005a431806a1f555e0571626ba313a/pyobjc_framework_webkit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:5d91527c9950c79269dd0d70f2bb8668c298dd06930637c1c063ce5f274a87e5", size = 50967, upload-time = "2026-06-19T16:19:35.474Z" }, + { url = "https://files.pythonhosted.org/packages/2d/59/5fac0754d53b2a72aed6f424dfc72e5fa245f83cb57c2e00d02e45390fca/pyobjc_framework_webkit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:657825081484c9920c50b76b469b9583f116225b0449c9d95c46cbc8c640adc8", size = 50498, upload-time = "2026-06-19T16:19:36.397Z" }, + { url = "https://files.pythonhosted.org/packages/da/0c/e997e33d99d4ad91da2cf70f0e51ac39b03c58ba210548e9e944bbb421be/pyobjc_framework_webkit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:f46adcc6227873f2b14d74b2e789c937f227722274ab59b9fa3c04c6ecb46dd5", size = 50958, upload-time = "2026-06-19T16:19:37.424Z" }, ] [[package]]