feat: add safe BLE discovery and network baseline tools
This commit is contained in:
parent
4c401bc59b
commit
faa442eefc
|
|
@ -6,7 +6,8 @@ evidence.
|
|||
|
||||
## Non-negotiable safety boundaries
|
||||
|
||||
- Default to passive or read-only operations.
|
||||
- Default to non-mutating discovery or read-only operations. CoreBluetooth BLE
|
||||
scanning on macOS is active discovery, not passive radio sniffing.
|
||||
- Do not send BLE writes until the exact service, characteristic, framing,
|
||||
payload semantics, rollback, and expected state transition are documented.
|
||||
- BLE notification subscription may perform the standard temporary CCCD write;
|
||||
|
|
|
|||
16
README.md
16
README.md
|
|
@ -50,6 +50,20 @@ uv run pytest
|
|||
and reports external tools; it does not request Bluetooth permission, scan the
|
||||
LAN, touch the K1, alter Homebrew, or change capture permissions.
|
||||
|
||||
The currently implemented laboratory commands are:
|
||||
|
||||
```bash
|
||||
uv run k1link ble scan --duration 30 --out sessions/<id>/captures/ble.json
|
||||
uv run k1link ble gatt-dump --device <corebluetooth-uuid> \
|
||||
--out sessions/<id>/captures/gatt.json
|
||||
uv run k1link net snapshot --out sessions/<id>/captures/network.json
|
||||
```
|
||||
|
||||
On macOS the scan is active CoreBluetooth discovery, but it does not connect to
|
||||
or modify devices. `gatt-dump` connects and performs service discovery only; it
|
||||
does not read characteristic values, subscribe to notifications or write
|
||||
configuration. Session output is sensitive and ignored by Git.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Technical audit](docs/00_TECHNICAL_AUDIT.md)
|
||||
|
|
@ -65,7 +79,7 @@ provenance remains clear.
|
|||
|
||||
## Safety boundary
|
||||
|
||||
Allowed initial work is passive discovery, standard device-information reads,
|
||||
Allowed initial work is non-mutating discovery, standard device-information reads,
|
||||
controlled notification listening, autonomous button operation, targeted
|
||||
capture of traffic to or from the confirmed K1 address, and offline analysis of
|
||||
owned artifacts.
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ documenting the missing ground truth.
|
|||
STOP: serious fault, overheating, activation lock, or destructive/ambiguous
|
||||
device state.
|
||||
|
||||
## Stage 1 — BLE read-only toolkit
|
||||
## Stage 1 — BLE non-mutating discovery and metadata toolkit
|
||||
|
||||
Minimal dependencies: `bleak` plus the existing CLI stack. Add them only in the
|
||||
repository-local environment.
|
||||
|
|
@ -59,6 +59,8 @@ k1link ble listen
|
|||
Requirements:
|
||||
|
||||
- preserve advertisement snapshots, manufacturer/service data and RSSI;
|
||||
- describe macOS scanning as active discovery: CoreBluetooth does not support
|
||||
passive scan mode, although discovery does not change K1 configuration;
|
||||
- identify by observed name + macOS UUID + advertisement fingerprint;
|
||||
- inspect standby, scanning and USB states separately;
|
||||
- start with standard Device Information/Battery services;
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ Decision:
|
|||
- PAUSE USB analysis if recording works but USB is unavailable.
|
||||
- STOP on activation lock or device fault.
|
||||
|
||||
## Experiment C — passive BLE surface
|
||||
## Experiment C — BLE discovery and metadata surface
|
||||
|
||||
Prerequisite: Stage 1 BLE commands implemented and reviewed.
|
||||
|
||||
|
|
@ -131,4 +131,3 @@ GO / PAUSE / BLOCKED:
|
|||
Evidence:
|
||||
Next smallest experiment:
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ requires-python = ">=3.12,<3.13"
|
|||
license = { text = "Proprietary" }
|
||||
authors = [{ name = "NODE.DC" }]
|
||||
dependencies = [
|
||||
"bleak==3.0.2",
|
||||
"rich>=13.9,<15",
|
||||
"typer>=0.15,<1",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
def utc_now_iso() -> str:
|
||||
"""Return a stable UTC timestamp for manifests and capture artifacts."""
|
||||
return datetime.now(UTC).isoformat(timespec="milliseconds").replace("+00:00", "Z")
|
||||
|
||||
|
||||
def write_json_atomic(path: Path, payload: Any) -> None:
|
||||
"""Write JSON without exposing a partially written artifact."""
|
||||
path = path.expanduser()
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
serialized = json.dumps(payload, ensure_ascii=False, indent=2) + "\n"
|
||||
|
||||
temp_name: str | None = None
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode="w",
|
||||
encoding="utf-8",
|
||||
dir=path.parent,
|
||||
prefix=f".{path.name}.",
|
||||
suffix=".tmp",
|
||||
delete=False,
|
||||
) as stream:
|
||||
temp_name = stream.name
|
||||
stream.write(serialized)
|
||||
stream.flush()
|
||||
os.fsync(stream.fileno())
|
||||
Path(temp_name).replace(path)
|
||||
finally:
|
||||
if temp_name is not None:
|
||||
Path(temp_name).unlink(missing_ok=True)
|
||||
|
|
@ -0,0 +1 @@
|
|||
"""Bluetooth Low Energy discovery tools."""
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from importlib.metadata import version
|
||||
from typing import TypedDict
|
||||
|
||||
from bleak import BleakClient, BleakScanner
|
||||
from bleak.exc import BleakDeviceNotFoundError
|
||||
|
||||
from k1link.artifacts import utc_now_iso
|
||||
|
||||
|
||||
class DescriptorRecord(TypedDict):
|
||||
uuid: str
|
||||
handle: int
|
||||
description: str
|
||||
|
||||
|
||||
class CharacteristicRecord(TypedDict):
|
||||
uuid: str
|
||||
handle: int
|
||||
description: str
|
||||
properties: list[str]
|
||||
descriptors: list[DescriptorRecord]
|
||||
|
||||
|
||||
class ServiceRecord(TypedDict):
|
||||
uuid: str
|
||||
handle: int
|
||||
description: str
|
||||
characteristics: list[CharacteristicRecord]
|
||||
|
||||
|
||||
class GattDumpResult(TypedDict):
|
||||
schema_version: int
|
||||
started_at_utc: str
|
||||
completed_at_utc: str
|
||||
adapter: str
|
||||
bleak_version: str
|
||||
device_macos_uuid: str
|
||||
device_name: str
|
||||
metadata_only: bool
|
||||
services: list[ServiceRecord]
|
||||
|
||||
|
||||
async def dump_metadata(device_macos_uuid: str, timeout_seconds: float) -> GattDumpResult:
|
||||
"""Connect and enumerate GATT metadata without characteristic reads or writes."""
|
||||
if timeout_seconds <= 0:
|
||||
raise ValueError("timeout_seconds must be positive")
|
||||
|
||||
started_at = utc_now_iso()
|
||||
async with asyncio.timeout(timeout_seconds):
|
||||
device = await BleakScanner.find_device_by_address(
|
||||
device_macos_uuid,
|
||||
timeout=min(20.0, timeout_seconds),
|
||||
)
|
||||
if device is None:
|
||||
raise BleakDeviceNotFoundError(
|
||||
device_macos_uuid,
|
||||
"Device was not rediscovered; keep the K1 powered and nearby.",
|
||||
)
|
||||
|
||||
async with BleakClient(device, timeout=timeout_seconds, pair=False) as client:
|
||||
services: list[ServiceRecord] = []
|
||||
for service in client.services:
|
||||
characteristics: list[CharacteristicRecord] = []
|
||||
for characteristic in service.characteristics:
|
||||
descriptors: list[DescriptorRecord] = []
|
||||
for descriptor in characteristic.descriptors:
|
||||
descriptors.append(
|
||||
{
|
||||
"uuid": descriptor.uuid,
|
||||
"handle": descriptor.handle,
|
||||
"description": descriptor.description,
|
||||
}
|
||||
)
|
||||
characteristics.append(
|
||||
{
|
||||
"uuid": characteristic.uuid,
|
||||
"handle": characteristic.handle,
|
||||
"description": characteristic.description,
|
||||
"properties": sorted(characteristic.properties),
|
||||
"descriptors": descriptors,
|
||||
}
|
||||
)
|
||||
services.append(
|
||||
{
|
||||
"uuid": service.uuid,
|
||||
"handle": service.handle,
|
||||
"description": service.description,
|
||||
"characteristics": characteristics,
|
||||
}
|
||||
)
|
||||
return {
|
||||
"schema_version": 1,
|
||||
"started_at_utc": started_at,
|
||||
"completed_at_utc": utc_now_iso(),
|
||||
"adapter": "CoreBluetooth",
|
||||
"bleak_version": version("bleak"),
|
||||
"device_macos_uuid": device_macos_uuid,
|
||||
"device_name": client.name,
|
||||
"metadata_only": True,
|
||||
"services": services,
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from importlib.metadata import version
|
||||
from typing import TypedDict
|
||||
|
||||
from bleak import BleakScanner
|
||||
from bleak.backends.device import BLEDevice
|
||||
from bleak.backends.scanner import AdvertisementData
|
||||
|
||||
from k1link.artifacts import utc_now_iso
|
||||
|
||||
|
||||
class BleDeviceRecord(TypedDict):
|
||||
macos_uuid: str
|
||||
id_kind: str
|
||||
name: str | None
|
||||
local_name: str | None
|
||||
rssi: int
|
||||
tx_power: int | None
|
||||
service_uuids: list[str]
|
||||
manufacturer_data_hex: dict[str, str]
|
||||
service_data_hex: dict[str, str]
|
||||
k1_name_candidate: bool
|
||||
|
||||
|
||||
class BleScanResult(TypedDict):
|
||||
schema_version: int
|
||||
started_at_utc: str
|
||||
completed_at_utc: str
|
||||
duration_seconds: float
|
||||
adapter: str
|
||||
bleak_version: str
|
||||
device_count: int
|
||||
devices: list[BleDeviceRecord]
|
||||
|
||||
|
||||
def advertisement_record(device: BLEDevice, advertisement: AdvertisementData) -> BleDeviceRecord:
|
||||
name = advertisement.local_name or device.name
|
||||
normalized_name = (name or "").casefold()
|
||||
return {
|
||||
"macos_uuid": device.address,
|
||||
"id_kind": "corebluetooth_uuid",
|
||||
"name": device.name,
|
||||
"local_name": advertisement.local_name,
|
||||
"rssi": advertisement.rssi,
|
||||
"tx_power": advertisement.tx_power,
|
||||
"service_uuids": sorted(advertisement.service_uuids),
|
||||
"manufacturer_data_hex": {
|
||||
str(company_id): data.hex()
|
||||
for company_id, data in sorted(advertisement.manufacturer_data.items())
|
||||
},
|
||||
"service_data_hex": {
|
||||
service_uuid: data.hex()
|
||||
for service_uuid, data in sorted(advertisement.service_data.items())
|
||||
},
|
||||
"k1_name_candidate": any(marker in normalized_name for marker in ("lixel", "xgrids", "k1")),
|
||||
}
|
||||
|
||||
|
||||
async def scan(duration_seconds: float) -> BleScanResult:
|
||||
if duration_seconds <= 0:
|
||||
raise ValueError("duration_seconds must be positive")
|
||||
|
||||
started_at = utc_now_iso()
|
||||
discovered = await BleakScanner.discover(timeout=duration_seconds, return_adv=True)
|
||||
devices = [
|
||||
advertisement_record(device, advertisement) for device, advertisement in discovered.values()
|
||||
]
|
||||
devices.sort(
|
||||
key=lambda item: (not item["k1_name_candidate"], -item["rssi"], item["macos_uuid"])
|
||||
)
|
||||
return {
|
||||
"schema_version": 1,
|
||||
"started_at_utc": started_at,
|
||||
"completed_at_utc": utc_now_iso(),
|
||||
"duration_seconds": duration_seconds,
|
||||
"adapter": "CoreBluetooth",
|
||||
"bleak_version": version("bleak"),
|
||||
"device_count": len(devices),
|
||||
"devices": devices,
|
||||
}
|
||||
|
|
@ -1,18 +1,24 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import TypedDict
|
||||
from typing import Annotated, TypedDict
|
||||
|
||||
import typer
|
||||
from bleak.exc import BleakError
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
|
||||
from k1link import __version__
|
||||
from k1link.artifacts import write_json_atomic
|
||||
from k1link.ble.gatt import dump_metadata
|
||||
from k1link.ble.scanner import scan
|
||||
from k1link.net.snapshot import snapshot
|
||||
|
||||
app = typer.Typer(
|
||||
name="k1link",
|
||||
|
|
@ -20,6 +26,10 @@ app = typer.Typer(
|
|||
no_args_is_help=True,
|
||||
)
|
||||
console = Console()
|
||||
ble_app = typer.Typer(help="Bluetooth LE discovery and metadata commands.", no_args_is_help=True)
|
||||
net_app = typer.Typer(help="Passive local network observation commands.", no_args_is_help=True)
|
||||
app.add_typer(ble_app, name="ble")
|
||||
app.add_typer(net_app, name="net")
|
||||
|
||||
|
||||
class ToolStatus(TypedDict):
|
||||
|
|
@ -168,5 +178,86 @@ def doctor(
|
|||
console.print(f"- {note}")
|
||||
|
||||
|
||||
@ble_app.command("scan")
|
||||
def ble_scan(
|
||||
out: Annotated[
|
||||
Path,
|
||||
typer.Option(help="Ignored session JSON path; parent directories are created."),
|
||||
],
|
||||
duration: Annotated[
|
||||
float,
|
||||
typer.Option(min=1.0, max=300.0, help="Scan duration in seconds."),
|
||||
] = 30.0,
|
||||
) -> None:
|
||||
"""Discover BLE advertisements without connecting or changing device configuration."""
|
||||
try:
|
||||
result = asyncio.run(scan(duration))
|
||||
except (BleakError, OSError, ValueError) as exc:
|
||||
console.print(f"[red]BLE scan failed:[/red] {type(exc).__name__}: {exc}")
|
||||
console.print("Check System Settings → Privacy & Security → Bluetooth.")
|
||||
raise typer.Exit(code=2) from exc
|
||||
|
||||
write_json_atomic(out, result)
|
||||
table = Table(title=f"BLE devices ({result['device_count']})")
|
||||
table.add_column("Candidate")
|
||||
table.add_column("Name")
|
||||
table.add_column("RSSI")
|
||||
table.add_column("macOS UUID")
|
||||
for device in result["devices"]:
|
||||
table.add_row(
|
||||
"K1?" if device["k1_name_candidate"] else "",
|
||||
device["local_name"] or device["name"] or "-",
|
||||
str(device["rssi"]),
|
||||
device["macos_uuid"],
|
||||
)
|
||||
console.print(table)
|
||||
console.print(f"Saved: {out}")
|
||||
|
||||
|
||||
@ble_app.command("gatt-dump")
|
||||
def ble_gatt_dump(
|
||||
device: Annotated[str, typer.Option(help="CoreBluetooth/macOS UUID from ble scan.")],
|
||||
out: Annotated[
|
||||
Path,
|
||||
typer.Option(help="Ignored session JSON path; parent directories are created."),
|
||||
],
|
||||
timeout: Annotated[
|
||||
float,
|
||||
typer.Option(min=5.0, max=120.0, help="Connection timeout in seconds."),
|
||||
] = 45.0,
|
||||
) -> None:
|
||||
"""Enumerate GATT metadata only: no characteristic reads, subscriptions or writes."""
|
||||
console.print(
|
||||
"Connecting for service discovery only; no characteristic values will be read or written."
|
||||
)
|
||||
try:
|
||||
result = asyncio.run(dump_metadata(device, timeout))
|
||||
except (BleakError, OSError, ValueError) as exc:
|
||||
console.print(f"[red]GATT metadata failed:[/red] {type(exc).__name__}: {exc}")
|
||||
raise typer.Exit(code=2) from exc
|
||||
|
||||
write_json_atomic(out, result)
|
||||
characteristic_count = sum(len(service["characteristics"]) for service in result["services"])
|
||||
console.print(
|
||||
f"Device: {result['device_name']}; services: {len(result['services'])}; "
|
||||
f"characteristics: {characteristic_count}"
|
||||
)
|
||||
console.print(f"Saved: {out}")
|
||||
|
||||
|
||||
@net_app.command("snapshot")
|
||||
def net_snapshot(
|
||||
out: Annotated[
|
||||
Path,
|
||||
typer.Option(help="Ignored session JSON path; parent directories are created."),
|
||||
],
|
||||
) -> None:
|
||||
"""Save routes, interfaces and the existing neighbor table without scanning the LAN."""
|
||||
result = snapshot()
|
||||
write_json_atomic(out, result)
|
||||
console.print("Saved a sensitive local network snapshot; do not commit the output.")
|
||||
console.print(f"Saved: {out}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app()
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
"""Passive network observation tools."""
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
from typing import TypedDict
|
||||
|
||||
from k1link.artifacts import utc_now_iso
|
||||
|
||||
|
||||
class CommandRecord(TypedDict):
|
||||
argv: list[str]
|
||||
returncode: int | None
|
||||
stdout: str
|
||||
stderr: str
|
||||
error: str | None
|
||||
|
||||
|
||||
class NetworkSnapshot(TypedDict):
|
||||
schema_version: int
|
||||
created_at_utc: str
|
||||
sensitivity: str
|
||||
commands: list[CommandRecord]
|
||||
|
||||
|
||||
def command_record(argv: list[str]) -> CommandRecord:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
argv,
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
)
|
||||
except (OSError, subprocess.SubprocessError) as exc:
|
||||
return {
|
||||
"argv": argv,
|
||||
"returncode": None,
|
||||
"stdout": "",
|
||||
"stderr": "",
|
||||
"error": f"{type(exc).__name__}: {exc}",
|
||||
}
|
||||
return {
|
||||
"argv": argv,
|
||||
"returncode": result.returncode,
|
||||
"stdout": result.stdout,
|
||||
"stderr": result.stderr,
|
||||
"error": None,
|
||||
}
|
||||
|
||||
|
||||
def snapshot() -> NetworkSnapshot:
|
||||
"""Collect a local read-only network snapshot; no packets are transmitted intentionally."""
|
||||
commands = [
|
||||
["route", "-n", "get", "default"],
|
||||
["networksetup", "-listallhardwareports"],
|
||||
["scutil", "--nwi"],
|
||||
["arp", "-an"],
|
||||
]
|
||||
return {
|
||||
"schema_version": 1,
|
||||
"created_at_utc": utc_now_iso(),
|
||||
"sensitivity": (
|
||||
"contains local interface, route, IP/MAC and neighbor metadata; do not commit"
|
||||
),
|
||||
"commands": [command_record(command) for command in commands],
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from k1link.artifacts import write_json_atomic
|
||||
|
||||
|
||||
def test_write_json_atomic(tmp_path: Path) -> None:
|
||||
output = tmp_path / "nested" / "artifact.json"
|
||||
write_json_atomic(output, {"value": "тест"})
|
||||
assert json.loads(output.read_text(encoding="utf-8")) == {"value": "тест"}
|
||||
assert not list(output.parent.glob("*.tmp"))
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
from bleak.backends.device import BLEDevice
|
||||
from bleak.backends.scanner import AdvertisementData
|
||||
|
||||
from k1link.ble.scanner import advertisement_record
|
||||
|
||||
|
||||
def test_advertisement_record_marks_k1_candidate() -> None:
|
||||
device = BLEDevice("TEST-UUID", "Unknown", details=None)
|
||||
advertisement = AdvertisementData(
|
||||
local_name="Lixel-K1-1234",
|
||||
manufacturer_data={123: b"\x01\x02"},
|
||||
service_data={"service": b"\xaa"},
|
||||
service_uuids=["B", "a"],
|
||||
tx_power=-4,
|
||||
rssi=-52,
|
||||
platform_data=(),
|
||||
)
|
||||
record = advertisement_record(device, advertisement)
|
||||
assert record["k1_name_candidate"] is True
|
||||
assert record["id_kind"] == "corebluetooth_uuid"
|
||||
assert record["manufacturer_data_hex"] == {"123": "0102"}
|
||||
assert record["service_data_hex"] == {"service": "aa"}
|
||||
assert record["service_uuids"] == ["B", "a"]
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
from k1link.net.snapshot import command_record, snapshot
|
||||
|
||||
|
||||
def test_command_record_captures_success() -> None:
|
||||
result = command_record(["/usr/bin/true"])
|
||||
assert result["returncode"] == 0
|
||||
assert result["error"] is None
|
||||
|
||||
|
||||
def test_snapshot_is_explicitly_sensitive() -> None:
|
||||
result = snapshot()
|
||||
assert result["schema_version"] == 1
|
||||
assert "do not commit" in result["sensitivity"]
|
||||
assert [record["argv"] for record in result["commands"]][-1] == ["arp", "-an"]
|
||||
213
uv.lock
213
uv.lock
|
|
@ -11,6 +11,30 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bleak"
|
||||
version = "3.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "dbus-fast", marker = "sys_platform == 'linux'" },
|
||||
{ name = "pyobjc-core", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "pyobjc-framework-corebluetooth", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "pyobjc-framework-libdispatch", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "winrt-runtime", marker = "sys_platform == 'win32'" },
|
||||
{ name = "winrt-windows-devices-bluetooth", marker = "sys_platform == 'win32'" },
|
||||
{ name = "winrt-windows-devices-bluetooth-advertisement", marker = "sys_platform == 'win32'" },
|
||||
{ name = "winrt-windows-devices-bluetooth-genericattributeprofile", marker = "sys_platform == 'win32'" },
|
||||
{ name = "winrt-windows-devices-enumeration", marker = "sys_platform == 'win32'" },
|
||||
{ name = "winrt-windows-devices-radios", marker = "sys_platform == 'win32'" },
|
||||
{ name = "winrt-windows-foundation", marker = "sys_platform == 'win32'" },
|
||||
{ name = "winrt-windows-foundation-collections", marker = "sys_platform == 'win32'" },
|
||||
{ name = "winrt-windows-storage-streams", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/16/df/05a3f80ca8e3f7f5b0dba68a9e618147c909ccdba1468f07487dc8d72a9d/bleak-3.0.2.tar.gz", hash = "sha256:c2229cb8238d5876b4bd05c74bf7a1aea1f88da39d2e51ac9dfd5cc319d5265f", size = 125293, upload-time = "2026-05-02T23:01:04.066Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/26/54/05aceb9cd80073805b3ed8522e3196e8cb22f70e741873fa51406c31f4e7/bleak-3.0.2-py3-none-any.whl", hash = "sha256:39092feb9e83f1df5ad2f88e837723c7211c982ce9e9cda6235104bc2ebe0d0d", size = 146490, upload-time = "2026-05-02T23:01:02.592Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
|
|
@ -20,6 +44,20 @@ 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 = "dbus-fast"
|
||||
version = "5.0.22"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/aa/db/b621610e50b1bc46ff63534d75239553c1bf33256de6096b58214fd9808a/dbus_fast-5.0.22.tar.gz", hash = "sha256:34dc67d7d21a12399828dd13e63b352750580beea54ea7c729e708f2d2905fef", size = 83224, upload-time = "2026-06-05T18:47:59.171Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/24/06/233b0bc13919474f70320bf389cb81ce02811956d6cf86c45e84679b63c3/dbus_fast-5.0.22-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f0bcad7f71d2304a68a5b0bc0d24c3fcc14710a2ffcf5f2a27521e3aece71ca", size = 799464, upload-time = "2026-06-05T18:56:02.617Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/e9/77bc23a6f5aebfb8f2c34489795e8517aed7eca31738438e1a4c4a4891d3/dbus_fast-5.0.22-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4ffcf16034f71a801bd2108aeffb6337d104c9459e8b1a218d16a917c8a2d2e9", size = 852687, upload-time = "2026-06-05T18:56:04.433Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/04/56769d0936d1273d1801ef574ec426ccb3f61f4b0a7a0eeb9eb2b8ccafa5/dbus_fast-5.0.22-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:98de6d2c200d8182e1fd0bdde3206fa556b8fa14ebb752a044cd8daa87b4658c", size = 833814, upload-time = "2026-06-05T18:56:05.87Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/ca/964f0d39a3be03b12a98f39519d34ad95b74360c7e3adf4cd4907dc25fd6/dbus_fast-5.0.22-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b013437b66dc22b8d9aca5e0b0d46bf1980208a143409469fe482d9684a2a717", size = 806891, upload-time = "2026-06-05T18:56:07.623Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/25/57fe6ab509ad9da2e190498fa9c37f868e38ac521d940a9edb9ba6b6c657/dbus_fast-5.0.22-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:855f15b7f7805171da2b82de1c317d01cfbb9fb8ac61fcc1e8dec54d8c69fab7", size = 830867, upload-time = "2026-06-05T18:56:09.473Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/2b/da036e9f4aeb776833139575fe0774544aa6cd13ba997eea7fbc4ab99852/dbus_fast-5.0.22-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7d1c42963235cfc015a2d2b8c5fe42b65387493b4ad4ce0ec122601c805e6742", size = 860651, upload-time = "2026-06-05T18:56:10.952Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.3.0"
|
||||
|
|
@ -107,6 +145,7 @@ name = "ndc-xgrids-k1-connector"
|
|||
version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "bleak" },
|
||||
{ name = "rich" },
|
||||
{ name = "typer" },
|
||||
]
|
||||
|
|
@ -120,6 +159,7 @@ dev = [
|
|||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "bleak", specifier = "==3.0.2" },
|
||||
{ name = "rich", specifier = ">=13.9,<15" },
|
||||
{ name = "typer", specifier = ">=0.15,<1" },
|
||||
]
|
||||
|
|
@ -167,6 +207,53 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyobjc-core"
|
||||
version = "12.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
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/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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyobjc-framework-cocoa"
|
||||
version = "12.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyobjc-core" },
|
||||
]
|
||||
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/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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyobjc-framework-corebluetooth"
|
||||
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/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/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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyobjc-framework-libdispatch"
|
||||
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/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/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" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.4.2"
|
||||
|
|
@ -253,3 +340,129 @@ sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3
|
|||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winrt-runtime"
|
||||
version = "3.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/16/dd/acdd527c1d890c8f852cc2af644aa6c160974e66631289420aa871b05e65/winrt_runtime-3.2.1.tar.gz", hash = "sha256:c8dca19e12b234ae6c3dadf1a4d0761b51e708457492c13beb666556958801ea", size = 21721, upload-time = "2025-06-06T14:40:27.593Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/54/3dd06f2341fab6abb06588a16b30e0b213b0125be7b79dafc3bdba3b334a/winrt_runtime-3.2.1-cp312-cp312-win32.whl", hash = "sha256:762b3d972a2f7037f7db3acbaf379dd6d8f6cda505f71f66c6b425d1a1eae2f1", size = 210090, upload-time = "2025-06-06T06:44:08.151Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/a1/1d7248d5c62ccbea5f3e0da64ca4529ce99c639c3be2485b6ed709f5c740/winrt_runtime-3.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:06510db215d4f0dc45c00fbb1251c6544e91742a0ad928011db33b30677e1576", size = 241391, upload-time = "2025-06-06T06:44:09.442Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/ae/6a205d8dafc79f7c242be7f940b1e0c1971fd64ab3079bda4b514aa3d714/winrt_runtime-3.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:14562c29a087ccad38e379e585fef333e5c94166c807bdde67b508a6261aa195", size = 415242, upload-time = "2025-06-06T06:44:10.407Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winrt-windows-devices-bluetooth"
|
||||
version = "3.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "winrt-runtime" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b2/a0/1c8a0c469abba7112265c6cb52f0090d08a67c103639aee71fc690e614b8/winrt_windows_devices_bluetooth-3.2.1.tar.gz", hash = "sha256:db496d2d92742006d5a052468fc355bf7bb49e795341d695c374746113d74505", size = 23732, upload-time = "2025-06-06T14:41:20.489Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/ff/c4a3de909a875b46fad5e9f4fd412bba48571405bfa802b878954abf128c/winrt_windows_devices_bluetooth-3.2.1-cp312-cp312-win32.whl", hash = "sha256:18c833ec49e7076127463679e85efc59f61785ade0dc185c852586b21be1f31c", size = 105752, upload-time = "2025-06-06T07:00:10.684Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/78/bfee1f0c8d188c561c5b946ab21f6a0037e60dea110e80b1d6a1d529639f/winrt_windows_devices_bluetooth-3.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:9b6702c462b216c91e32388023a74d0f87210cef6fd5d93b7191e9427ce2faca", size = 113356, upload-time = "2025-06-06T07:00:11.541Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/1b/d9da9c29d36cabadef4e19c3e9ba6d2692f6a28224c81fcff757132ea0da/winrt_windows_devices_bluetooth-3.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:419fd1078c7749119f6b4bbf6be4e586e03a0ed544c03b83178f1d85f1b3d148", size = 104724, upload-time = "2025-06-06T07:00:12.406Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winrt-windows-devices-bluetooth-advertisement"
|
||||
version = "3.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "winrt-runtime" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/06/fc/7ffe66ca4109b9e994b27c00f3d2d506e6e549e268791f755287ad9106d8/winrt_windows_devices_bluetooth_advertisement-3.2.1.tar.gz", hash = "sha256:0223852a7b7fa5c8dea3c6a93473bd783df4439b1ed938d9871f947933e574cc", size = 16906, upload-time = "2025-06-06T14:41:21.448Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/15/ad05c28e049208c97011728e2debdb45439175f75efe357b6faa4c9ba099/winrt_windows_devices_bluetooth_advertisement-3.2.1-cp312-cp312-win32.whl", hash = "sha256:901933cc40de5eb7e5f4188897c899dd0b0f577cb2c13eab1a63c7dfe89b08c4", size = 90033, upload-time = "2025-06-06T07:00:23.421Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/48/074779081841f6eba4987930c4e7adcec38a5985b7dffd9fecc41f39a89c/winrt_windows_devices_bluetooth_advertisement-3.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:e6c66e7d4f4ca86d2c801d30efd2b9673247b59a2b4c365d9e11650303d68d89", size = 95824, upload-time = "2025-06-06T07:00:24.238Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/25/e01966033a02b2d0718710bb47ef4f6b9b5a619ca2c857e06eb5c8e3ed13/winrt_windows_devices_bluetooth_advertisement-3.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:447d19defd8982d39944642eb7ebe89e4e20259ec9734116cf88879fb2c514ff", size = 89311, upload-time = "2025-06-06T07:00:25.029Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winrt-windows-devices-bluetooth-genericattributeprofile"
|
||||
version = "3.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "winrt-runtime" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/44/21/aeeddc0eccdfbd25e543360b5cc093233e2eab3cdfb53ad3cabae1b5d04d/winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1.tar.gz", hash = "sha256:cdf6ddc375e9150d040aca67f5a17c41ceaf13a63f3668f96608bc1d045dde71", size = 38896, upload-time = "2025-06-06T14:41:22.687Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/a1/75ac783a5faee9b455fef2f53b7fef97b21ed60d52401b44c690202141e4/winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1-cp312-cp312-win32.whl", hash = "sha256:ef894d21e0a805f3e114940254636a8045335fa9de766c7022af5d127dfad557", size = 183326, upload-time = "2025-06-06T07:00:52.662Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/d9/a9dcc15322d2f5c7dfd491bd7ab121e36437caf78ebfa92bc0dd0546e2ca/winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:db05de95cd1b24a51abb69cb936a8b17e9214e015757d0b37e3a5e207ddceb3d", size = 187810, upload-time = "2025-06-06T07:00:53.594Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/fc/47d00af076f558267097af3050910beda6bf8a21ceaa5830bbd26fcaf85e/winrt_windows_devices_bluetooth_genericattributeprofile-3.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d4e131cf3d15fc5ad81c1bcde3509ac171298217381abed6bdf687f29871984", size = 184516, upload-time = "2025-06-06T07:00:55.24Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winrt-windows-devices-enumeration"
|
||||
version = "3.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "winrt-runtime" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9e/dd/75835bfbd063dffa152109727dedbd80f6e92ea284855f7855d48cdf31c9/winrt_windows_devices_enumeration-3.2.1.tar.gz", hash = "sha256:df316899e39bfc0ffc1f3cb0f5ee54d04e1d167fbbcc1484d2d5121449a935cf", size = 23538, upload-time = "2025-06-06T14:41:26.787Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/31/3e/81642208ecd6c6c936f35a39a433c54e3f68e09d316546b8f953581ae334/winrt_windows_devices_enumeration-3.2.1-cp312-cp312-win32.whl", hash = "sha256:1db22b0292b93b0688d11ad932ad1f3629d4f471310281a2fbfe187530c2c1f3", size = 130249, upload-time = "2025-06-06T07:02:02.237Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/f4/a9ede5f3f0d86abfc7590726cf711133d97419b49ced372fca532e4f0696/winrt_windows_devices_enumeration-3.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:a73bc88d7f510af454f2b392985501c96f39b89fd987140708ccaec1588ceebc", size = 141512, upload-time = "2025-06-06T07:02:03.424Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/ef/4fad07c03124bdc3acd64f80f3bd3cc4417ea641e07bb16a9503afd3e554/winrt_windows_devices_enumeration-3.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:2853d687803f0dd76ae1afe3648abc0453e09dff0e7eddbb84b792eddb0473ca", size = 135383, upload-time = "2025-06-06T07:02:04.312Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winrt-windows-devices-radios"
|
||||
version = "3.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "winrt-runtime" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5e/02/9704ea359ad8b0d6faa1011f98fb477e8fb6eac5201f39d19e73c2407e7b/winrt_windows_devices_radios-3.2.1.tar.gz", hash = "sha256:4dc9b9d1501846049eb79428d64ec698d6476c27a357999b78a8331072e18a0b", size = 5908, upload-time = "2025-06-06T14:41:44.868Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/e0/4731a3c412318b2c5e74a8803a32e2fb9afc2c98368c6b61a422eb359e7e/winrt_windows_devices_radios-3.2.1-cp312-cp312-win32.whl", hash = "sha256:c3e683ce682338a5a5ed465f735e223ba7a22f16d0bbea2d070962bc7657edbb", size = 38606, upload-time = "2025-06-06T07:08:01.477Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/8e/91464854dfc9e0be9ce8dcbe2bd6a67c19b68ab91584fc5de0f4f13e78f8/winrt_windows_devices_radios-3.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:a116e552a3f38607b9be558fb2e7de9b4450d1f9080069944d74d80cdda1873e", size = 40172, upload-time = "2025-06-06T07:08:02.214Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/0d/1bd62f606b6c4dfa936fccc4712be5506a40fc5d1b7177c3d3cbcaf30972/winrt_windows_devices_radios-3.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:4c28822f9251c9d547324f596b5c2581f050254ded05e5b786c650a3502744c1", size = 36989, upload-time = "2025-06-06T07:08:03.295Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winrt-windows-foundation"
|
||||
version = "3.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "winrt-runtime" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0c/55/098ce7ea0679efcc1298b269c48768f010b6c68f90c588f654ec874c8a74/winrt_windows_foundation-3.2.1.tar.gz", hash = "sha256:ad2f1fcaa6c34672df45527d7c533731fdf65b67c4638c2b4aca949f6eec0656", size = 30485, upload-time = "2025-06-06T14:41:53.344Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/f8/495e304ddedd5ff2f196efbde906265cb75ade4d79e2937837f72ef654a0/winrt_windows_foundation-3.2.1-cp312-cp312-win32.whl", hash = "sha256:867642ccf629611733db482c4288e17b7919f743a5873450efb6d69ae09fdc2b", size = 112169, upload-time = "2025-06-06T07:11:01.438Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/5e/b5059e4ece095351c496c9499783130c302d25e353c18031d5231b1b3b3c/winrt_windows_foundation-3.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:45550c5b6c2125cde495c409633e6b1ea5aa1677724e3b95eb8140bfccbe30c9", size = 118668, upload-time = "2025-06-06T07:11:02.475Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/70/acbcb3ef07b1b67e2de4afab9176a5282cfd775afd073efe6828dfc65ace/winrt_windows_foundation-3.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:94f4661d71cb35ebc52be7af112f2eeabdfa02cb05e0243bf9d6bd2cafaa6f37", size = 109671, upload-time = "2025-06-06T07:11:03.538Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winrt-windows-foundation-collections"
|
||||
version = "3.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "winrt-runtime" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ef/62/d21e3f1eeb8d47077887bbf0c3882c49277a84d8f98f7c12bda64d498a07/winrt_windows_foundation_collections-3.2.1.tar.gz", hash = "sha256:0eff1ad0d8d763ad17e9e7bbd0c26a62b27215016393c05b09b046d6503ae6d5", size = 16043, upload-time = "2025-06-06T14:41:53.983Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/0b/7802349391466d3f7e8f62f588f36a1a0b6560abfcdbdaa426fe21d322b4/winrt_windows_foundation_collections-3.2.1-cp312-cp312-win32.whl", hash = "sha256:15704eef3125788f846f269cf54a3d89656fa09a1dc8428b70871f717d595ad6", size = 60060, upload-time = "2025-06-06T07:11:16.173Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/37/94/5b888713e472746635a382e523513ab1b8200af55c5b56bc70e1e4369115/winrt_windows_foundation_collections-3.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:550dfb8c82fe74d9e0728a2a16a9175cc9e34ca2b8ef758d69b2a398894b698b", size = 69058, upload-time = "2025-06-06T07:11:17.009Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/3c/829273622c9b37c67b97f187b92be318404f7d33db045e31d72b7d50f54c/winrt_windows_foundation_collections-3.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:810ad4bd11ab4a74fdbcd3ed33b597ef7c0b03af73fc9d7986c22bcf3bd24f84", size = 58793, upload-time = "2025-06-06T07:11:17.837Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winrt-windows-storage-streams"
|
||||
version = "3.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "winrt-runtime" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/00/50/f4488b07281566e3850fcae1021f0285c9653992f60a915e15567047db63/winrt_windows_storage_streams-3.2.1.tar.gz", hash = "sha256:476f522722751eb0b571bc7802d85a82a3cae8b1cce66061e6e758f525e7b80f", size = 34335, upload-time = "2025-06-06T14:43:23.905Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/87/e7/7d3f2a4a442f264e05cab2bdf20ed1b95cb3f753bd1b0f277f2b49fb8335/winrt_windows_storage_streams-3.2.1-cp312-cp312-win32.whl", hash = "sha256:77c1f0e004b84347b5bd705e8f0fc63be8cd29a6093be13f1d0869d0d97b7d78", size = 127787, upload-time = "2025-06-06T14:02:02.277Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/2f/cc36f475f8af293f40e2c2a5d6c2e75a189c2c2d4d01ecb3551578518c79/winrt_windows_storage_streams-3.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:e4508ee135af53e4fc142876abbf4bc7c2a95edfc7d19f52b291a8499cacd6dc", size = 131849, upload-time = "2025-06-06T14:02:03.09Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/84/896fb734f7456910ec412f3f3adfdc3f0dc3134864a496d5b120592f3bfd/winrt_windows_storage_streams-3.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:040cb94e6fb26b0d00a00e8b88b06fadf29dfe18cf24ed6cb3e69709c3613307", size = 128144, upload-time = "2025-06-06T14:02:03.946Z" },
|
||||
]
|
||||
|
|
|
|||
Loading…
Reference in New Issue