diff --git a/docs/08_LIXELGO_IPHONE_OBSERVATION.md b/docs/08_LIXELGO_IPHONE_OBSERVATION.md new file mode 100644 index 0000000..b101c7c --- /dev/null +++ b/docs/08_LIXELGO_IPHONE_OBSERVATION.md @@ -0,0 +1,76 @@ +# LixelGO / iPhone observation runbook + +This runbook prepares the Mac without full Xcode and defines the first bounded, +read-only capture. The accepted decision is ADR 0005. Do not start the iPhone +experiment until the exact-profile physical regression in ADR 0004 passes. + +## Current Mac baseline + +- macOS 26.5.1, Apple Silicon; +- Xcode Command Line Tools 16.4 are present; +- full Xcode and `rvictl` are absent; +- built-in `tcpdump` and `uv` are present; +- Wireshark/tshark are not required for capture; +- the isolated lab uses Python 3.12 and `pymobiledevice3==9.36.0`. + +## Preparation + +```bash +uv sync --project plugins/xgrids-k1/lab/iphone-capture --frozen +uv run --project plugins/xgrids-k1/lab/iphone-capture --frozen \ + pymobiledevice3 version +``` + +Expected version: `9.36.0`. + +Connect one unlocked iPhone using a data cable, approve the Mac accessory prompt, +and tap **Trust This Computer**. Do not paste a UDID into Terminal or notes. + +```bash +uv run --project plugins/xgrids-k1/lab/iphone-capture --frozen \ + python plugins/xgrids-k1/lab/iphone-capture/preflight.py +``` + +## First smoke without K1 + +After the iPhone preflight succeeds, run a 10-second idle capture only after the +current source milestone is committed or with an explicitly recorded dirty-tree +fingerprint: + +```bash +uv run --project plugins/xgrids-k1/lab/iphone-capture --frozen \ + python plugins/xgrids-k1/lab/iphone-capture/capture.py \ + --duration 10 +``` + +The tool retains the original metadata-bearing `PCAPNG` and creates a compatible +classic `PCAP`. Validate the latter with built-in `tcpdump`: + +```bash +tcpdump -nn -r sessions/iphone-k1-observation//captures/iphone-network.pcap -c 1 +``` + +After the capture and operator timeline are both closed, seal the complete +session without rewriting the capture manifest: + +```bash +uv run python plugins/xgrids-k1/lab/iphone-capture/session_integrity.py \ + sessions/iphone-k1-observation/ +``` + +The derived private inventory verifies all manifest hashes and also hashes the +late operator-event artifact when it exists. + +## Full baseline sequence + +1. Complete the ADR 0004 physical K1 regression without LixelGO. +2. Close unrelated iPhone apps and pause unrelated network activity. +3. Keep the iPhone on the K1/shared Wi-Fi while USB remains connected to Mac. +4. Start a bounded 60–180 second network capture. +5. Perform one predefined LixelGO action at a recorded monotonic offset. +6. Stop and retain the raw capture; analyze only offline. +7. Record endpoints, ports, sizes, timing and correlation before interpreting a + payload as a command, image, or model frame. + +The first baseline does not install the Bluetooth logging profile. BLE/HCI is a +separate operator-approved experiment. diff --git a/docs/adr/0005-owner-controlled-lixelgo-iphone-observation.md b/docs/adr/0005-owner-controlled-lixelgo-iphone-observation.md new file mode 100644 index 0000000..c097c91 --- /dev/null +++ b/docs/adr/0005-owner-controlled-lixelgo-iphone-observation.md @@ -0,0 +1,76 @@ +# ADR 0005: Owner-controlled LixelGO/iPhone observation + +- Status: accepted +- Date: 2026-07-16 +- Supersedes: only the unavailable-phone assumption in ADR 0001 + +## Context + +ADR 0001 correctly described the original stand: no phone and no LixelGO were +available, so they could not be dependencies or test oracles. An +owner-controlled iPhone with LixelGO is now available for a new, evidence-only +experiment. The previous no-phone results remain valid historical evidence. + +An ordinary third client on the mesh Wi-Fi cannot normally observe unicast +traffic between the iPhone and K1. Apple's native Remote Virtual Interface path +requires `rvictl`, which is installed by full Xcode and is absent from the +current Mac. Installing full Xcode solely for the first capture is unnecessary. + +## Decision + +The XGRIDS plugin may use a pinned, isolated `pymobiledevice3` lab environment +to read the iPhone `pcapd` service over a trusted USB connection. This mirrors +the iPhone IP stack while the phone remains connected to K1 over Wi-Fi. It is a +lab subprocess, not a root Mission Core dependency. + +The experiment is evidence-only: + +- no TLS interception or MITM certificate; +- no app automation; +- no new BLE or MQTT write; +- no router, firmware, device-file, or application mutation; +- no camera or command semantic claim until bounded decode and a controlled + physical correlation are both present. + +Network capture does not include BLE HCI. Bluetooth capture is a separate gate +that requires Apple's diagnostic logging profile, explicit operator approval, +and removal of that profile after the experiment. + +All captures are sensitive, mode `0600`, bounded in duration, stored under +ignored `sessions/`, hashed, and represented in Git/Ops only by redacted facts. +No device ID, credential, SSID, router metadata, or raw packet payload enters +normal Git history. + +## Entry gate + +LixelGO/iPhone observation starts only after ADR 0004's post-refactor physical +regression succeeds on exact firmware `3.0.2` and direct-LAN: + +1. operator attestation; +2. prepare the receiver; +3. physical double-click start; +4. first real point frame and pose through the new normalizer/viewer path; +5. raw evidence finalization; +6. operator-confirmed physical stop; +7. verification that Mission Core emitted no K1 application command. + +## Consequences + +Full Xcode, `rvictl`, Wireshark, and system/Homebrew changes are not required for +the first IP capture. TLS or proprietary encryption may remain opaque; endpoint, +port, size, timing, interface, process, and plaintext local protocol evidence are +still useful. Any discovered state-changing contract requires a new profile +revision and its own acceptance gate before implementation. + +## Outcome recorded on 2026-07-16 + +Lab 002 completed the bounded observation. It found a plaintext local MQTT data +and control plane plus two RTSP/H.264 camera preview endpoints. Owner-operated +LixelGO start and stop were correlated with bounded protobuf action values `1` +and `2`, device success responses, physical K1 transitions and start/stop of the +high-rate streams. No LixelGO WAN flow was observed in either principal window. + +This outcome updates descriptive evidence only. Mission Core still emits no +modeling request: private header/settings construction, save-complete semantics, +timeouts and rollback remain unresolved. See +`docs/lab/002_LIXELGO_IPHONE_LOCAL_PROTOCOL_20260716.redacted.md`. diff --git a/docs/lab/002_LIXELGO_IPHONE_LOCAL_PROTOCOL_20260716.redacted.md b/docs/lab/002_LIXELGO_IPHONE_LOCAL_PROTOCOL_20260716.redacted.md new file mode 100644 index 0000000..0b1808b --- /dev/null +++ b/docs/lab/002_LIXELGO_IPHONE_LOCAL_PROTOCOL_20260716.redacted.md @@ -0,0 +1,180 @@ +# Lab 002 — LixelGO/iPhone local K1 protocol observation + +- Date: 2026-07-16 +- Device scope: one owner-controlled LixelKity K1, exact firmware `3.0.2` +- Application scope: owner-operated LixelGO on a trusted iPhone +- Capture path: iPhone `pcapd` over USB to the Mac +- Result: **GO for protocol-backed camera discovery and start/stop mapping; + PAUSE for Mission Core command emission and save-state semantics** + +This report contains no device identifier, address, SSID, credential, project +name, room image, or raw packet payload. The sensitive source captures remain +under ignored `sessions/` with mode `0600`. + +## Experiment boundary + +The operator performed one clean LixelGO lifecycle and one control/stop window: + +1. K1 was powered to steady-green standby without a physical scan gesture. +2. LixelGO established Bluetooth control and requested the K1 Wi-Fi network. +3. The operator accepted the iOS Wi-Fi join request. +4. LixelGO entered its scan workspace; this navigation alone caused no K1 state + transition. +5. The operator supplied a private project name and confirmed start. +6. K1 performed static initialization and exposed point cloud, pose and camera + preview. +7. The operator selected left and right camera views. +8. The operator opened the stop confirmation and then confirmed it. +9. K1 stopped the LiDAR, displayed its saving state and returned to steady-green + standby without any physical K1 button press. + +Bluetooth HCI was not captured. The BLE stage is supported by the operator's +screen/physical observation, while all protocol findings below start at the +iPhone IP stack. + +## Evidence integrity + +The two principal network sessions contain `108,188` and `207,123` packets. +Their raw PCAPNG and compatible classic PCAP hashes match the capture manifests, +and packet timestamps/order are equivalent across the two representations. + +The original manifests and captures were left byte-for-byte unchanged. A +separate private integrity inventory re-hashes the manifests, packet files and +late operator-event JSONL. Operator events record sequence, not precise action +latency: several were narrated after the corresponding wire event. + +## Network and privacy result + +LixelGO accounted for more than `99.5%` of bytes in each principal capture and +communicated with exactly two private local endpoints. No LixelGO flow to a +public/WAN address was observed in either window. The first window contained a +very small amount of unrelated public traffic from other iPhone processes; the +second contained no public traffic at all. + +This is bounded negative evidence for these sessions, not a permanent claim +about every application mode or future LixelGO release. + +Observed K1 transports: + +| Transport | Purpose | Result | +| --- | --- | --- | +| MQTT 3.1.1 over plaintext TCP `1883` | control, status, point cloud, pose | observed | +| RTSP 1.0 over TCP `8554` | left/right camera session control | observed | +| interleaved RTP/TCP, H.264 PT96, 90 kHz | camera preview media | observed | +| HTTP over TCP `80` | three empty project-related setup responses | ancillary only | + +No LixelGO UDP flow was observed. + +## Start and static initialization + +The application start transaction is now physical wire evidence rather than +static-analysis evidence: + +- QoS 2 PUBLISH topic: `lixel/application/request/modeling`; +- bounded protobuf top-level action field: `1`; +- correlated device response: about `1.5 ms` after the request, with the same + action and a success result; +- first pose: about `23.1 s` after the request; +- first modeling/point-cloud reports: about `24.1 s` after the request; +- first camera RTSP PLAY: about `25.1 s` after the request. + +The observed 144-byte START protobuf has this bounded top-level wire shape +(private strings remain redacted): + +```text +f1/wire2 = 130-byte private header/session string +f2/wire0 = 1 +f3/wire2 = 3-byte private string +f4/wire0 = 2 +f5/wire0 = 1 +``` + +`f4` is the decoded value `2`; the occasionally noted `1` describes only the +one-byte varint encoding length and is not the field value. The successful +response repeats the private 130-byte field, reports `f2=1`, and includes an +observed success block in `f15/wire2` (15 bytes). These observations describe +the capture but do not establish safe construction rules for a new request. + +The 23–25 second interval matches the operator-observed static initialization. +The captured request contains additional private header/settings context. Its +complete construction and validation rules remain unresolved and are not +committed as a reusable command fixture. + +## Live data plane + +The existing repository decoder successfully decoded distributed samples of +both live spatial channels: + +- point cloud: `lixel/application/report/lio_pcl`, about `9.4 Hz`, roughly + `2,500–2,650` points per sampled frame; +- pose: `lixel/application/report/lio_pose`, about `10 Hz`, finite decoded + position/orientation fields. + +During active scanning the aggregate K1-to-phone data plane averaged roughly +`1.11 MB/s`, split approximately into `0.25 MB/s` MQTT and `0.86 MB/s` camera +RTSP/RTP. The observed peak was about `1.89 MB/s`. + +## Camera preview + +The K1 exposes two independent path-labelled RTSP preview endpoints: + +```text +rtsp://{confirmed-device-private-ipv4}:8554/live/chn_left_main +rtsp://{confirmed-device-private-ipv4}:8554/live/chn_right_main +``` + +Each session follows `OPTIONS`, `DESCRIBE`, `SETUP`, `PLAY`; media is H.264 in +interleaved RTP over TCP. The observed preview rate was approximately `10 fps` +and `6.45–6.59 Mbit/s`. + +Camera selection is not an MQTT command. LixelGO tears down the current RTSP +session and opens the other endpoint. In the controlled left-to-right switch, +the new RTSP connection began about `0.8 s` after the preceding stream's last +packet. RTP arrived immediately after PLAY, but the first new SPS/PPS/IDR group +arrived about `0.79 s` later. This explains most of the operator-observed visual +delay; exact tap-to-display latency was not measurable from retrospective event +markers. + +The capture proves a compressed preview path, not full-resolution raw camera +frames, camera calibration, stitched panorama geometry, or optical left/right +identity beyond the endpoint labels and operator-selected views. + +## Stop and post-stop boundary + +The remote stop transaction is also verified inside the packet capture: + +- QoS 2 PUBLISH on the same modeling-request topic; +- bounded protobuf top-level action field: `2`; +- correlated success response after about `33 ms`; +- final point-cloud PUBLISH after about `36.4 ms`; +- final pose PUBLISH after about `40.8 ms`; +- final interleaved video packet after about `54.1 ms`; +- point-cloud UNSUBSCRIBE and RTSP TEARDOWN at about `0.95 s`; +- status/modeling reports for about another `9 s`; +- control MQTT and heartbeat remained reachable for about `31 s` without a + recurrence of high-rate point, pose, or video traffic. + +The raw modeling-state value changed immediately after stop. Its enum name is +not decoded. The capture does not contain an independently verified +`save-complete`/`project-closed` response or the previously observed standby +state code. Completed saving and final steady-green standby are therefore +operator-corroborated, not packet-semantically proven. + +## Decision + +- **GO:** retain MQTT point cloud and pose as the current live spatial plane. +- **GO:** implement a read-only left/right RTSP/H.264 preview receiver behind + the XGRIDS plugin boundary. +- **GO:** promote start=`1` and stop=`2` from static hints to observed, + physical-correlated vendor mappings. +- **PAUSE:** keep all Mission Core vendor writes disabled until request headers, + settings, acknowledgements, timeout, save completion and rollback are modeled + and regression-tested. +- **PAUSE:** do not name the post-stop enum or claim durable project persistence + from this capture. +- **PAUSE:** BLE automation remains governed by the separately reviewed Wi-Fi + provisioning profile; no BLE/HCI inference is added here. + +The next smallest implementation step is an offline/then-live read-only camera +adapter with bounded buffering, H.264 metadata, explicit left/right selection +and no acquisition command publisher. diff --git a/plugins/xgrids-k1/lab/iphone-capture/.python-version b/plugins/xgrids-k1/lab/iphone-capture/.python-version new file mode 100644 index 0000000..e4fba21 --- /dev/null +++ b/plugins/xgrids-k1/lab/iphone-capture/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/plugins/xgrids-k1/lab/iphone-capture/README.md b/plugins/xgrids-k1/lab/iphone-capture/README.md new file mode 100644 index 0000000..c5c9170 --- /dev/null +++ b/plugins/xgrids-k1/lab/iphone-capture/README.md @@ -0,0 +1,69 @@ +# iPhone / LixelGO capture lab + +This is an isolated evidence tool for the XGRIDS K1 plugin. It is not a Mission +Core runtime dependency and must not be bundled without a separate GPL license +review. + +## Install without Xcode + +From the repository root: + +```bash +uv sync --project plugins/xgrids-k1/lab/iphone-capture --frozen +``` + +The environment is created under the lab directory. It pins +`pymobiledevice3==9.36.0`; it does not modify the system Python, Homebrew, the +Mission Core `.venv`, or the root lockfile. + +## USB preflight + +Connect exactly one iPhone with a data-capable USB cable, unlock it, confirm the +Mac accessory prompt, and tap **Trust This Computer** on the iPhone. Then run: + +```bash +uv run --project plugins/xgrids-k1/lab/iphone-capture --frozen \ + python plugins/xgrids-k1/lab/iphone-capture/preflight.py +``` + +The preflight prints only a device count. It never prints or stores the iPhone +UDID or serial number. + +## Bounded network capture + +Run this only after the post-refactor exact-profile K1 regression in ADR 0004: + +```bash +uv run --project plugins/xgrids-k1/lab/iphone-capture --frozen \ + python plugins/xgrids-k1/lab/iphone-capture/capture.py \ + --duration 120 +``` + +The capture uses the iPhone `pcapd` service over USB, suppresses packet hex on +stdout, and writes private artifacts under ignored +`sessions/iphone-k1-observation/`. It is intentionally bounded to 5–600 seconds. +Each successful run retains the original metadata-bearing `PCAPNG` and creates a +classic Ethernet `PCAP` that the macOS bundled `tcpdump` can read. The redacted +manifest records the validated packet count and hashes both artifacts. +The default refuses a dirty repository; `--allow-dirty` records a source-tree +fingerprint when an explicitly approved lab run cannot wait for a commit. + +Operator events may be recorded while the capture is active and can finish +after the bounded network process exits. Preserve the original capture manifest +and create a separate integrity inventory after the operator timeline is closed: + +```bash +uv run python plugins/xgrids-k1/lab/iphone-capture/session_integrity.py \ + sessions/iphone-k1-observation/ +``` + +The sealer re-hashes every manifest artifact, adds the manifest and optional +`operator-events.private.jsonl`, and writes an ignored mode-`0600` +`integrity.private.json`. It never edits the original manifest or raw capture. + +Do not add `--bluetooth` unless Apple's Bluetooth diagnostic logging profile is +already installed on the iPhone and the operator explicitly approved that +configuration change. The script never installs the profile. + +Wireshark is optional and is not required to record PCAP/PCAPNG. Full Xcode is +also not required for this path. diff --git a/plugins/xgrids-k1/lab/iphone-capture/capture.py b/plugins/xgrids-k1/lab/iphone-capture/capture.py new file mode 100644 index 0000000..48d962b --- /dev/null +++ b/plugins/xgrids-k1/lab/iphone-capture/capture.py @@ -0,0 +1,313 @@ +from __future__ import annotations + +import argparse +import hashlib +import json +import os +import re +import secrets +import struct +import subprocess +import time +from datetime import UTC, datetime +from pathlib import Path +from typing import Any + +from pcapng import FileScanner +from pcapng.blocks import EnhancedPacket +from preflight import ( + pymobiledevice3_binary, + pymobiledevice3_version, + usb_device_count, +) + +REPO_ROOT = Path(__file__).resolve().parents[4] +SESSION_ROOT = REPO_ROOT / "sessions" / "iphone-k1-observation" +SAFE_LABEL = re.compile(r"^[a-z0-9][a-z0-9-]{0,31}$") + + +def utc_now() -> str: + return datetime.now(UTC).isoformat() + + +def sha256_file(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as stream: + for chunk in iter(lambda: stream.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def git_output(*args: str) -> bytes: + completed = subprocess.run( + ["git", *args], + cwd=REPO_ROOT, + check=True, + capture_output=True, + ) + return completed.stdout + + +def source_snapshot() -> dict[str, Any]: + revision = git_output("rev-parse", "HEAD").decode().strip() + status = git_output("status", "--porcelain=v1", "--untracked-files=all") + dirty = bool(status.strip()) + digest = hashlib.sha256() + digest.update(revision.encode()) + digest.update(b"\0") + digest.update(status) + if dirty: + digest.update(git_output("diff", "--binary", "HEAD")) + untracked = git_output("ls-files", "--others", "--exclude-standard", "-z") + for raw_name in filter(None, untracked.split(b"\0")): + relative = raw_name.decode("utf-8", errors="surrogateescape") + path = REPO_ROOT / relative + digest.update(raw_name) + digest.update(b"\0") + if path.is_file(): + digest.update(sha256_file(path).encode()) + return { + "git_revision": revision, + "source_tree_dirty": dirty, + "source_tree_fingerprint_sha256": digest.hexdigest(), + } + + +def terminate(process: subprocess.Popen[bytes]) -> int: + if process.poll() is None: + process.terminate() + try: + process.wait(timeout=10) + except subprocess.TimeoutExpired: + process.kill() + process.wait(timeout=5) + return int(process.returncode or 0) + + +def artifact_record(path: Path, kind: str, session_dir: Path) -> dict[str, Any] | None: + if not path.is_file() or path.stat().st_size == 0: + return None + path.chmod(0o600) + return { + "kind": kind, + "relative_path": str(path.relative_to(session_dir)), + "size_bytes": path.stat().st_size, + "sha256": sha256_file(path), + } + + +def convert_pcapng_to_pcap(source: Path, destination: Path) -> int: + """Create a classic Ethernet PCAP that macOS's bundled tcpdump can read.""" + packet_count = 0 + with source.open("rb") as source_stream, destination.open("xb") as destination_stream: + destination_stream.write( + struct.pack( + "= 1_000_000: + seconds += 1 + microseconds -= 1_000_000 + destination_stream.write( + struct.pack( + " argparse.Namespace: + parser = argparse.ArgumentParser( + description="Bounded, redacted USB iPhone capture for the Mission Core K1 lab" + ) + parser.add_argument("--duration", type=int, default=120, help="capture seconds (5..600)") + parser.add_argument("--label", default="lixelgo-observe", help="safe session label") + parser.add_argument("--process", help="optional iOS process filter for network capture") + parser.add_argument("--interface", help="optional iOS interface filter for network capture") + parser.add_argument( + "--bluetooth", + action="store_true", + help="also capture iPhone Bluetooth HCI; requires Apple's logging profile", + ) + parser.add_argument( + "--acknowledge-ios-bluetooth-profile", + action="store_true", + help="confirm the operator already installed Apple's Bluetooth logging profile", + ) + parser.add_argument( + "--allow-dirty", + action="store_true", + help="record an explicit dirty-tree fingerprint instead of refusing the capture", + ) + return parser.parse_args() + + +def main() -> int: + args = parse_args() + if not 5 <= args.duration <= 600: + raise SystemExit("--duration must be between 5 and 600 seconds") + if not SAFE_LABEL.fullmatch(args.label): + raise SystemExit("--label must use lowercase ASCII letters, digits, and hyphens") + if args.bluetooth and not args.acknowledge_ios_bluetooth_profile: + raise SystemExit( + "Bluetooth capture requires --acknowledge-ios-bluetooth-profile; " + "the script never installs the profile" + ) + + os.umask(0o077) + binary = pymobiledevice3_binary() + if usb_device_count(binary) != 1: + raise SystemExit("exactly one trusted USB iPhone must be connected") + snapshot = source_snapshot() + if snapshot["source_tree_dirty"] and not args.allow_dirty: + raise SystemExit( + "repository is dirty; commit the milestone or pass --allow-dirty " + "to record its fingerprint" + ) + + session_id = ( + datetime.now(UTC).strftime("%Y%m%dT%H%M%SZ") + f"_{args.label}_{secrets.token_hex(2)}" + ) + session_dir = SESSION_ROOT / session_id + capture_dir = session_dir / "captures" + capture_dir.mkdir(parents=True, mode=0o700) + network_pcapng_path = capture_dir / "iphone-network.pcapng" + network_pcap_path = capture_dir / "iphone-network.pcap" + bluetooth_path = capture_dir / "iphone-bluetooth.pcapng" + stderr_path = session_dir / "capture.stderr.log" + manifest_path = session_dir / "manifest.redacted.json" + + manifest: dict[str, Any] = { + "schema": "missioncore.xgrids-k1/iphone-observation-manifest/v1alpha1", + "session_id": session_id, + "status": "starting", + "started_at_utc": utc_now(), + "started_monotonic_ns": time.monotonic_ns(), + "requested_duration_seconds": args.duration, + "capture_backend": "pymobiledevice3-9.36.0/com.apple.pcapd", + "pymobiledevice3_version": pymobiledevice3_version(binary), + "network_capture": True, + "bluetooth_capture": args.bluetooth, + "network_process_filter": args.process, + "network_interface_filter": args.interface, + "device_identity_recorded": False, + "credentials_recorded_in_manifest": False, + "source": snapshot, + "artifacts": [], + } + manifest_path.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8") + manifest_path.chmod(0o600) + + network_command = [binary, "--no-color", "pcap", "--out", str(network_pcapng_path)] + if args.process: + network_command.extend(["--process", args.process]) + if args.interface: + network_command.extend(["--interface", args.interface]) + commands = [("network", network_command)] + if args.bluetooth: + commands.append( + ( + "bluetooth", + [binary, "--no-color", "btlogger", str(bluetooth_path), "--format", "pcapng"], + ) + ) + + processes: list[tuple[str, subprocess.Popen[bytes]]] = [] + exit_codes: dict[str, int] = {} + interrupted = False + failure: str | None = None + with stderr_path.open("wb") as error_log: + stderr_path.chmod(0o600) + try: + for name, command in commands: + process = subprocess.Popen( + command, + cwd=REPO_ROOT, + stdout=subprocess.DEVNULL, + stderr=error_log, + ) + processes.append((name, process)) + time.sleep(1) + early = [name for name, process in processes if process.poll() is not None] + if early: + raise RuntimeError(f"capture service exited early: {', '.join(early)}") + + deadline = time.monotonic() + args.duration + while time.monotonic() < deadline: + ended = [name for name, process in processes if process.poll() is not None] + if ended: + raise RuntimeError(f"capture service stopped unexpectedly: {', '.join(ended)}") + time.sleep(min(0.25, deadline - time.monotonic())) + except KeyboardInterrupt: + interrupted = True + except RuntimeError as exc: + failure = str(exc) + finally: + for name, process in processes: + exit_codes[name] = terminate(process) + + network_packet_count: int | None = None + if failure is None: + try: + network_packet_count = convert_pcapng_to_pcap( + network_pcapng_path, + network_pcap_path, + ) + if network_packet_count == 0: + failure = "network capture validation found no packets" + except Exception as exc: # noqa: BLE001 - third-party parser errors vary by version + failure = f"network capture validation failed: {type(exc).__name__}" + + artifacts = [ + artifact_record(network_pcapng_path, "iphone-network-pcapng-raw", session_dir), + artifact_record(network_pcap_path, "iphone-network-pcap-compatible", session_dir), + artifact_record(bluetooth_path, "iphone-bluetooth-hci-pcapng", session_dir), + artifact_record(stderr_path, "capture-stderr", session_dir), + ] + manifest.update( + { + "status": "failed" if failure else "completed", + "interrupted_by_operator": interrupted, + "failure": failure, + "ended_at_utc": utc_now(), + "ended_monotonic_ns": time.monotonic_ns(), + "process_exit_codes": exit_codes, + "network_packet_count": network_packet_count, + "artifacts": [item for item in artifacts if item is not None], + } + ) + manifest_path.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8") + manifest_path.chmod(0o600) + print(f"session: {session_dir}") + print(f"status: {manifest['status']}") + return 1 if failure else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/plugins/xgrids-k1/lab/iphone-capture/preflight.py b/plugins/xgrids-k1/lab/iphone-capture/preflight.py new file mode 100644 index 0000000..1be6242 --- /dev/null +++ b/plugins/xgrids-k1/lab/iphone-capture/preflight.py @@ -0,0 +1,76 @@ +from __future__ import annotations + +import json +import shutil +import subprocess +import sys +from pathlib import Path + + +def pymobiledevice3_binary() -> str: + sibling = Path(sys.executable).with_name("pymobiledevice3") + if sibling.is_file(): + return str(sibling) + discovered = shutil.which("pymobiledevice3") + if discovered is None: + raise RuntimeError("pymobiledevice3 is not installed in this environment") + return discovered + + +def pymobiledevice3_version(binary: str) -> str: + completed = subprocess.run( + [binary, "--no-color", "version"], + check=False, + capture_output=True, + text=True, + timeout=20, + ) + if completed.returncode != 0: + raise RuntimeError("pymobiledevice3 version check failed") + return completed.stdout.strip() + + +def usb_device_count(binary: str) -> int: + completed = subprocess.run( + [binary, "--no-color", "usbmux", "list", "--usb"], + check=False, + capture_output=True, + text=True, + timeout=20, + ) + if completed.returncode != 0: + raise RuntimeError( + "USB discovery failed; unlock the iPhone and confirm Trust This Computer" + ) + try: + payload = json.loads(completed.stdout) + except json.JSONDecodeError as exc: + raise RuntimeError("USB discovery returned an unexpected response") from exc + if not isinstance(payload, list): + raise RuntimeError("USB discovery returned an unexpected response") + return len(payload) + + +def main() -> int: + try: + binary = pymobiledevice3_binary() + version = pymobiledevice3_version(binary) + count = usb_device_count(binary) + except (OSError, RuntimeError, subprocess.SubprocessError) as exc: + print(f"BLOCKED: {exc}") + return 2 + + print(f"pymobiledevice3: {version}") + print(f"USB iPhone devices: {count}") + if count == 0: + print("BLOCKED: connect one unlocked iPhone with a data cable and confirm Trust") + return 2 + if count > 1: + print("BLOCKED: leave exactly one trusted iPhone connected for a redacted capture") + return 2 + print("READY: one trusted USB iPhone is available; no device identifier was printed") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/plugins/xgrids-k1/lab/iphone-capture/pyproject.toml b/plugins/xgrids-k1/lab/iphone-capture/pyproject.toml new file mode 100644 index 0000000..52b79c8 --- /dev/null +++ b/plugins/xgrids-k1/lab/iphone-capture/pyproject.toml @@ -0,0 +1,12 @@ +[project] +name = "mission-core-ios-capture-lab" +version = "0.1.0" +description = "Isolated read-only iPhone packet-capture tooling for Mission Core lab work" +requires-python = ">=3.12,<3.13" +dependencies = [ + "pymobiledevice3==9.36.0", + "python-pcapng==2.1.1", +] + +[tool.uv] +package = false diff --git a/plugins/xgrids-k1/lab/iphone-capture/session_integrity.py b/plugins/xgrids-k1/lab/iphone-capture/session_integrity.py new file mode 100644 index 0000000..bbd8797 --- /dev/null +++ b/plugins/xgrids-k1/lab/iphone-capture/session_integrity.py @@ -0,0 +1,169 @@ +"""Seal one completed iPhone capture without rewriting its original manifest. + +The capture manifest is written when the bounded packet capture ends. Operator +events may be appended after that point, so they belong in a separate derived +integrity inventory rather than in a retroactively edited capture manifest. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import os +from datetime import UTC, datetime +from pathlib import Path +from typing import Any + +INTEGRITY_SCHEMA = "missioncore.xgrids-k1/iphone-session-integrity/v1alpha1" +CAPTURE_MANIFEST = "manifest.redacted.json" +INTEGRITY_INVENTORY = "integrity.private.json" +OPTIONAL_SESSION_ARTIFACTS = { + "capture.stderr.log": "capture-stderr", + "operator-events.private.jsonl": "operator-events", +} + + +class SessionIntegrityError(ValueError): + """The session cannot be safely verified or sealed.""" + + +def sha256_file(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as stream: + for chunk in iter(lambda: stream.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def _load_object(path: Path) -> dict[str, Any]: + try: + value = json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as exc: + raise SessionIntegrityError(f"cannot read JSON object: {path.name}") from exc + if not isinstance(value, dict): + raise SessionIntegrityError(f"JSON root must be an object: {path.name}") + return value + + +def _safe_artifact_path(session_dir: Path, relative_path: object) -> Path: + if not isinstance(relative_path, str) or not relative_path: + raise SessionIntegrityError("artifact relative_path must be a non-empty string") + relative = Path(relative_path) + if relative.is_absolute() or ".." in relative.parts: + raise SessionIntegrityError("artifact path must stay inside the session") + path = session_dir / relative + if path.is_symlink() or not path.is_file(): + raise SessionIntegrityError(f"artifact is missing or is a symlink: {relative_path}") + return path + + +def _record(path: Path, *, kind: str, session_dir: Path) -> dict[str, Any]: + before = path.stat() + digest = sha256_file(path) + after = path.stat() + if (before.st_size, before.st_mtime_ns) != (after.st_size, after.st_mtime_ns): + raise SessionIntegrityError(f"artifact changed while hashing: {path.name}") + return { + "kind": kind, + "relative_path": str(path.relative_to(session_dir)), + "size_bytes": after.st_size, + "sha256": digest, + "mode_octal": f"{after.st_mode & 0o777:04o}", + } + + +def build_inventory(session_dir: Path) -> dict[str, Any]: + """Verify a completed capture and return a derived integrity inventory.""" + session_dir = session_dir.expanduser().resolve() + manifest_path = session_dir / CAPTURE_MANIFEST + if manifest_path.is_symlink() or not manifest_path.is_file(): + raise SessionIntegrityError(f"missing {CAPTURE_MANIFEST}") + + manifest = _load_object(manifest_path) + if manifest.get("status") != "completed": + raise SessionIntegrityError("only a completed capture can be sealed") + session_id = manifest.get("session_id") + if not isinstance(session_id, str) or session_id != session_dir.name: + raise SessionIntegrityError("manifest session_id does not match the directory") + artifacts = manifest.get("artifacts") + if not isinstance(artifacts, list) or not artifacts: + raise SessionIntegrityError("capture manifest has no artifacts") + + verified: list[dict[str, Any]] = [] + for item in artifacts: + if not isinstance(item, dict): + raise SessionIntegrityError("manifest artifact must be an object") + path = _safe_artifact_path(session_dir, item.get("relative_path")) + actual = _record( + path, + kind=str(item.get("kind") or "capture-manifest-artifact"), + session_dir=session_dir, + ) + if item.get("size_bytes") != actual["size_bytes"]: + raise SessionIntegrityError(f"artifact size mismatch: {actual['relative_path']}") + if item.get("sha256") != actual["sha256"]: + raise SessionIntegrityError(f"artifact hash mismatch: {actual['relative_path']}") + verified.append(actual) + + derived = [_record(manifest_path, kind="capture-manifest", session_dir=session_dir)] + for relative_name, kind in OPTIONAL_SESSION_ARTIFACTS.items(): + path = session_dir / relative_name + if path.exists(): + if path.is_symlink() or not path.is_file(): + raise SessionIntegrityError( + f"optional artifact is not a regular file: {relative_name}" + ) + derived.append(_record(path, kind=kind, session_dir=session_dir)) + + return { + "schema": INTEGRITY_SCHEMA, + "session_id": session_id, + "sealed_at_utc": datetime.now(UTC).isoformat(), + "capture_manifest_preserved": True, + "verified_manifest_artifacts": verified, + "derived_session_artifacts": derived, + } + + +def seal_session(session_dir: Path, *, overwrite: bool = False) -> Path: + """Write an ignored mode-0600 inventory next to the immutable capture.""" + os.umask(0o077) + resolved = session_dir.expanduser().resolve() + destination = resolved / INTEGRITY_INVENTORY + if destination.exists() and not overwrite: + raise SessionIntegrityError(f"{INTEGRITY_INVENTORY} already exists") + inventory = build_inventory(resolved) + temporary = resolved / f".{INTEGRITY_INVENTORY}.tmp" + temporary.write_text(json.dumps(inventory, indent=2) + "\n", encoding="utf-8") + temporary.chmod(0o600) + temporary.replace(destination) + destination.chmod(0o600) + return destination + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Verify and seal one completed Mission Core iPhone capture session" + ) + parser.add_argument("session", type=Path, help="completed ignored session directory") + parser.add_argument( + "--overwrite", + action="store_true", + help="replace only the derived inventory; never changes the capture manifest", + ) + return parser.parse_args() + + +def main() -> int: + args = parse_args() + try: + destination = seal_session(args.session, overwrite=args.overwrite) + except SessionIntegrityError as exc: + raise SystemExit(str(exc)) from exc + print(f"sealed: {destination}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/plugins/xgrids-k1/lab/iphone-capture/uv.lock b/plugins/xgrids-k1/lab/iphone-capture/uv.lock new file mode 100644 index 0000000..c87f3ff --- /dev/null +++ b/plugins/xgrids-k1/lab/iphone-capture/uv.lock @@ -0,0 +1,1349 @@ +version = 1 +revision = 3 +requires-python = "==3.12.*" + +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +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 = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "ansicon" +version = "1.89.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/e2/1c866404ddbd280efedff4a9f15abfe943cb83cde6e895022370f3a61f85/ansicon-1.89.0.tar.gz", hash = "sha256:e4d039def5768a47e4afec8e89e83ec3ae5a26bf00ad851f914d1240b444d2b1", size = 67312, upload-time = "2019-04-29T20:23:57.314Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/f9/f1c10e223c7b56a38109a3f2eb4e7fe9a757ea3ed3a166754fb30f65e466/ansicon-1.89.0-py2.py3-none-any.whl", hash = "sha256:f1def52d17f65c2c9682cf8370c03f541f410c1752d6a14029f97318e4b9dfec", size = 63675, upload-time = "2019-04-29T20:23:53.83Z" }, +] + +[[package]] +name = "anyio" +version = "4.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" }, +] + +[[package]] +name = "apple-compress" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "loguru" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/14/cdb0bdb6147a019d2d8a1e144910c6a8d257d0e58f63bbd105bda0fce7dc/apple_compress-0.2.3.tar.gz", hash = "sha256:a1c855cec8bd73210ae9522c53a841ca515ded2f0c9d5b4e728d0d93fee46a29", size = 4399, upload-time = "2024-04-05T00:06:29.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/b3/ba911999bf7146457e9bf85d5ccdb9adc0d970250d451954f587f5511123/apple_compress-0.2.3-py3-none-any.whl", hash = "sha256:b4eb22fb45e2e781bb44dc76b94dea7f616f3583fcbb4737ecc53e2a46ab7434", size = 6235, upload-time = "2024-04-05T00:06:28.337Z" }, +] + +[[package]] +name = "arrow" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931, upload-time = "2025-10-18T17:46:46.761Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" }, +] + +[[package]] +name = "asn1" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "enum-compat" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/35/9d0709bf09340ddb1796dfc52d1b1ef2f18e690b5cbf32b7be02c702532c/asn1-2.8.0.tar.gz", hash = "sha256:adf77ddc2707cf420c0eae3b99ee30e913afcf0936467d42669820ce6b7d150a", size = 29584, upload-time = "2025-02-20T14:53:44.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/44/a156ba1d955c2251b14998a356043247f9e01b5f63c133987d27634ba613/asn1-2.8.0-py2.py3-none-any.whl", hash = "sha256:1438ac9a53cbc4064330af43b054ae042374f7c8ab46c55358241c15e29f1461", size = 10304, upload-time = "2025-02-20T14:53:38.849Z" }, +] + +[[package]] +name = "asttokens" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/25/1e/faf0f247f6f881b98fc4d6d07e14085cb89d13665084e6d6ac1dc2c03d0b/asttokens-3.0.2.tar.gz", hash = "sha256:3ecdbd8f2cc195f53ccada3a613538bb5f9ef6f6869129f13e03c30a677b8fe2", size = 63136, upload-time = "2026-07-12T03:31:49.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/2b/04b8a15f3a1c77bc79ddf5c73875327f34b4fa75982df2b76e45e402d364/asttokens-3.0.2-py3-none-any.whl", hash = "sha256:9da13157f5b28becde0bd374fc677dcd3c290614264eff096f167c469cd9f933", size = 28702, upload-time = "2026-07-12T03:31:47.542Z" }, +] + +[[package]] +name = "av" +version = "18.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/a4/570a5a35c8638aba01e739925846c35fdd6b0756a15526766d0a4dd3b7df/av-18.0.0.tar.gz", hash = "sha256:4ef7e72c3d3a872584a1215173b16e0226811037f40dcdbf75992631098df1ba", size = 4340222, upload-time = "2026-07-02T06:37:58.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/74/6732f17b96dc23fd23b876b2805435855abdc8a3b397142be4e581165de8/av-18.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4d683b7747a0ba9222b8a5f81e41db5f796e7f64473454ec4fe2548e083c2fa0", size = 33387843, upload-time = "2026-07-02T06:37:05.097Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b9/7708c43fed7ae28b4a1bad060b4221e3334cd827cec24f7165902a6ac1f4/av-18.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ae56b40b6f8b067a8ad2dac664fbfbabac7f7a55b9a7bb031eb99289252bc017", size = 35536910, upload-time = "2026-07-02T06:37:08.806Z" }, + { url = "https://files.pythonhosted.org/packages/5a/94/eba99691d184f6a395a242d54dc370e2fd2265e95bbc98e2963a0fdbdd6c/av-18.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:ea2e8ebbce521f21b55df9400e00d721623c9020ef158f5a188a96130be0743f", size = 38984619, upload-time = "2026-07-02T06:37:11.861Z" }, + { url = "https://files.pythonhosted.org/packages/c9/cf/0d7aee07fe16aa9ffdf96043c14bed5485a52c0dea4259de87aa306ecab4/av-18.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef96dabb3e50dac249913145dff5424b302b257fd95dcb64be3c7b7a8aef16d1", size = 34451176, upload-time = "2026-07-02T06:37:15.154Z" }, + { url = "https://files.pythonhosted.org/packages/76/92/810da80b12680d4c4fe235bd1b4003289be9213ac7f114b77b8ecf0e3b3e/av-18.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:0f65518a184613e41536f29e8758c8e3d8293e46bf5bef108f04f925bbfa3f44", size = 36619869, upload-time = "2026-07-02T06:37:18.495Z" }, + { url = "https://files.pythonhosted.org/packages/11/85/0f121ff43dc5a70696676c98a8f1674e2fa787614c2abaacb15fa1a9bc99/av-18.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:aaf4d354d2beaa6651e4f92e54409a578bde64f79c0beef9a30b388d06f7c629", size = 27556236, upload-time = "2026-07-02T06:37:21.388Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f6/2509754d4d2356abc6fc0ea3d57c12ade29bac23a1fb7fc215a53ca518fb/av-18.0.0-cp311-abi3-win_arm64.whl", hash = "sha256:adac2b3833b6cb9bd6cb52664a522b94db453615b3675b1dbb26e13fe1c80da6", size = 20221133, upload-time = "2026-07-02T06:37:23.88Z" }, +] + +[[package]] +name = "blessed" +version = "1.47.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinxed" }, + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/45/ad23d265373cdb7f255d2e3ed5f122b62914bd3c425bb21bca01ef699e5c/blessed-1.47.0.tar.gz", hash = "sha256:ea13e06ae40f24710325411c5fa9b689d215cf170276cf1fda41feddaec8d3e0", size = 14035743, upload-time = "2026-07-09T00:43:10.055Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/d8/374001cbc3fb49d0c99e7b115498696c4beaf7468287aa84d36c34bb4e97/blessed-1.47.0-py3-none-any.whl", hash = "sha256:f4df54a32289b6a3eaca49387b4f6823ba7e04ddb5ffa18f5e1fde44e8b79681", size = 131212, upload-time = "2026-07-09T00:43:07.609Z" }, +] + +[[package]] +name = "bpylist2" +version = "4.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/34/eb90ff6be953f6e4df08d4e8c0b761bea144242b6d711e922113411cc631/bpylist2-4.1.1.tar.gz", hash = "sha256:0cc63284aee42f5c7e0ec87f8f59cdd35aaed05ad12d866b1868ea0c0caaafe1", size = 18000, upload-time = "2023-09-11T16:58:00.758Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/e5/b552c94fc965153c9dcad4b64202e5170d4918716bc082d4fa6c6230579c/bpylist2-4.1.1-py3-none-any.whl", hash = "sha256:4862eab78d9d908d532393208b6771cebc8debef99ab851b54a0a0e28e2bec6b", size = 16596, upload-time = "2023-09-11T16:57:59.526Z" }, +] + +[[package]] +name = "certifi" +version = "2026.6.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" }, +] + +[[package]] +name = "cffi" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/5f/ff100cae70ebe9d8df1c01a00e510e45d9adb5c1fdda84791b199141de97/cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9", size = 531036, upload-time = "2026-07-06T21:34:30.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/85/990925db5df586ec90beb97529c853497e7f85ba0234830447faf41c3057/cffi-2.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:df2b82571a1b30f58a87bf4e5a9e78d2b1eff6c6ce8fd3aa3757221f93f0863f", size = 184829, upload-time = "2026-07-06T21:32:44.324Z" }, + { url = "https://files.pythonhosted.org/packages/4b/92/e7bb136ad6b5352603732cf907ef862ca103f20f2031c1735a46300c20c9/cffi-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78474632761faa0fb96f30b1c928c84ebcf68713cbb80d15bab09dfe61640fde", size = 184728, upload-time = "2026-07-06T21:32:45.683Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c0/d1ec30ffb370f748f2fb54425972bfef9871e0132e82fb589c46b6676049/cffi-2.1.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5972433ad71a9e46516584ef60a0fda12d9dc459938d1539c3ddecf9bdc1368d", size = 214815, upload-time = "2026-07-06T21:32:48.557Z" }, + { url = "https://files.pythonhosted.org/packages/1b/dc/5620cf930688be01f2d673804291de757a934c90b946dbdc3d84130c2ea4/cffi-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6422532152adf4e59b110cb2808cee7a033800952f5c036b4af047ee43199e7", size = 222429, upload-time = "2026-07-06T21:32:49.848Z" }, + { url = "https://files.pythonhosted.org/packages/4b/a4/77b53abbf7a1e0beb9637edbef2a94d15f9c822f591e85d439ffd91519a6/cffi-2.1.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:46b1c8db8f6122420f32d02fffb924c2fe9bc772d228c7c711748fff56aabb2b", size = 210315, upload-time = "2026-07-06T21:32:51.221Z" }, + { url = "https://files.pythonhosted.org/packages/58/0c/f528df19cc94b675087324d4760d9e6d5bfae97d6217aa4fac43de4f5fcc/cffi-2.1.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9fafc5aa2e2a39aaf7f8cc0c1f044a9b07fca12e558dca53a3cc5c654ad67a7", size = 208859, upload-time = "2026-07-06T21:32:52.512Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/c9522a81c32132799a1972c39f5c5f8b4c8b9f00488a23feaa6c06f07741/cffi-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e9f50d192a3e525b15a75ab5114e442d83d657b7ec29182a991bc9a88fd3a66", size = 221844, upload-time = "2026-07-06T21:32:53.704Z" }, + { url = "https://files.pythonhosted.org/packages/6e/28/bd53988b9833e8f8ad539d26f4c07a6b3f6bcb1e9e02e7ca038250b3428d/cffi-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98fff996e983a36d3aa2eca83af40c5821202e7e6f32d13ae94e3d2286f10cfe", size = 225287, upload-time = "2026-07-06T21:32:54.907Z" }, + { url = "https://files.pythonhosted.org/packages/79/99/0d0fd37f055224085f42bbb2c022d002e17dde4a97972822327b07d84101/cffi-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:379de10ce1ba048b1448599d1b37b24caee16309d1ac98d3982fc997f768700b", size = 223681, upload-time = "2026-07-06T21:32:56.329Z" }, + { url = "https://files.pythonhosted.org/packages/b0/80/c138990aa2a70b1a269f6e06348729836d733d6f970867943f61d367f8cc/cffi-2.1.0-cp312-cp312-win32.whl", hash = "sha256:9b8f0f26ca4e7513c534d351eca551947d053fac438f2a04ac96d882909b0d3a", size = 175269, upload-time = "2026-07-06T21:32:57.777Z" }, + { url = "https://files.pythonhosted.org/packages/a8/eb/f636456ff21a83fc13c032b58cc5dde061691546ac79efa284b2989b7982/cffi-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:c97f080ea627e2863524c5af3836e2270b5f5dfff1f104392b959f8df0c5d384", size = 185881, upload-time = "2026-07-06T21:32:59.253Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/400ea43e721727dca8a65c4521390e9196757caba4a45643acb2b63271b8/cffi-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:6d194185eabd279f1c05ebe3504265ddfc5ad2b58d0714f7db9f01da592e9eb6", size = 180088, upload-time = "2026-07-06T21:33:02.278Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/2a/23f34ec9d04624958e137efdc394888716353190e75f25dd22c7a2c7a8aa/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", size = 152439, upload-time = "2026-07-07T14:34:58.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0", size = 319300, upload-time = "2026-07-07T14:33:15.666Z" }, + { url = "https://files.pythonhosted.org/packages/ec/96/5d9364e3342d69f3a045e1777bc47c85c383e6e9466d561b33fdb419d1f9/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9", size = 215802, upload-time = "2026-07-07T14:33:17.031Z" }, + { url = "https://files.pythonhosted.org/packages/4b/4c/5361f9aa7f2cb58d94f2ab831b3d493f69efb1d239654b4744e3c09527cb/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44", size = 237171, upload-time = "2026-07-07T14:33:18.576Z" }, + { url = "https://files.pythonhosted.org/packages/50/78/ce342ca4ff30b2eb49fe6d9578df85974f90c67d294113e94efdd9664cbd/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9", size = 233075, upload-time = "2026-07-07T14:33:20.084Z" }, + { url = "https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd", size = 224256, upload-time = "2026-07-07T14:33:21.747Z" }, + { url = "https://files.pythonhosted.org/packages/87/3a/ad914516df7e358a81aae018caa5e0470ba827fa6d763b1d2e87d920a5f6/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84", size = 208784, upload-time = "2026-07-07T14:33:23.313Z" }, + { url = "https://files.pythonhosted.org/packages/d7/74/3c12f9755717dfe5c5c87da63f35d765fa0c00382ec26bf23f7fae34f2ba/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b", size = 219928, upload-time = "2026-07-07T14:33:24.814Z" }, + { url = "https://files.pythonhosted.org/packages/33/9a/895095b83e7907abd6d3d99aad3a38ad0d9686cc186cb0c94c24320fe63e/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde", size = 218489, upload-time = "2026-07-07T14:33:26.42Z" }, + { url = "https://files.pythonhosted.org/packages/a1/34/ef5c05f412f42520d7709b7d3784d19640839eb7366ded1755511585429f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39", size = 210267, upload-time = "2026-07-07T14:33:27.952Z" }, + { url = "https://files.pythonhosted.org/packages/83/dc/9b29fa4412b318bf3bfea985c35d67eb55e04b59a7c3f2237168b0e0be6f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62", size = 226030, upload-time = "2026-07-07T14:33:29.397Z" }, + { url = "https://files.pythonhosted.org/packages/0e/42/6dbc00b8cd16011691203e33570fa42ed5746599a2e878112d16eab403a3/charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642", size = 151185, upload-time = "2026-07-07T14:33:30.781Z" }, + { url = "https://files.pythonhosted.org/packages/80/cc/f920afd1a23c58ccd53c1d36085a71893a4737ff5e66e0371efab6809850/charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0", size = 162557, upload-time = "2026-07-07T14:33:32.176Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/0386d43a261ff4e4b30c5857af7df877254b46bec7b9d1b74b6bf969a90b/charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2", size = 152665, upload-time = "2026-07-07T14:33:33.711Z" }, + { url = "https://files.pythonhosted.org/packages/98/2b/f97f1c193fb855c345d678f5077d6926034db0722df74c8f057020e05a25/charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5", size = 64538, upload-time = "2026-07-07T14:34:56.993Z" }, +] + +[[package]] +name = "click" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coloredlogs" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "humanfriendly" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520, upload-time = "2021-06-11T10:22:45.202Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018, upload-time = "2021-06-11T10:22:42.561Z" }, +] + +[[package]] +name = "construct" +version = "2.10.70" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/77/8c84b98eca70d245a2a956452f21d57930d22ab88cbeed9290ca630cf03f/construct-2.10.70.tar.gz", hash = "sha256:4d2472f9684731e58cc9c56c463be63baa1447d674e0d66aeb5627b22f512c29", size = 86337, upload-time = "2023-11-29T08:44:49.545Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/fb/08b3f4bf05da99aba8ffea52a558758def16e8516bc75ca94ff73587e7d3/construct-2.10.70-py3-none-any.whl", hash = "sha256:c80be81ef595a1a821ec69dc16099550ed22197615f4320b57cc9ce2a672cb30", size = 63020, upload-time = "2023-11-29T08:44:46.876Z" }, +] + +[[package]] +name = "construct-typing" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "construct" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/ae/659fe4866d89ef5a3a65cddbdd7b35882f4feb72db383821965f2fcea934/construct_typing-0.7.0.tar.gz", hash = "sha256:71d110dedff39bd3b603c734077032a7065bc597a49db1f5b03a211d05dbac23", size = 45104, upload-time = "2025-10-27T19:30:29.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/0c/2db6f7e1ae9795e436c6a0dc0bc38b12b8c8a228cb63203e24190b755b3b/construct_typing-0.7.0-py3-none-any.whl", hash = "sha256:c92383c6e8e5d07ba25811c8d5163820458d821e73bb1006541f43f89788646c", size = 24350, upload-time = "2025-10-27T19:30:27.505Z" }, +] + +[[package]] +name = "cryptography" +version = "49.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/99/d1c90d6041656cc6ee229dc99cd67fd0cd5aec3c5f7d72fffc27cc750054/cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493", size = 854345, upload-time = "2026-06-12T20:02:30.512Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/22/adf66990e63584a68dfb50c24f48a125c07b1699899381c8151e63ed458c/cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db", size = 4032100, upload-time = "2026-06-12T20:02:32.143Z" }, + { url = "https://files.pythonhosted.org/packages/09/41/3797cfaf69cae04a13ee78ebd83f0678d9c02b4779d21ce24445326f1a69/cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db", size = 4692978, upload-time = "2026-06-12T20:01:21.305Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8b/43011f7ebe515a8aa20d61f290a326cd890c2e738e16e59eaff8d9c3a412/cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325", size = 4716422, upload-time = "2026-06-12T20:01:48.566Z" }, + { url = "https://files.pythonhosted.org/packages/4a/91/01ce7303a4579e6d3a6abef01bd322848e9ea7a219adcabc5048b9033571/cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2", size = 4700503, upload-time = "2026-06-12T20:02:47.091Z" }, + { url = "https://files.pythonhosted.org/packages/62/99/a2c95cf8293f07491e9e27c20cc4dcd18176d944e674679adeb1d0173fd6/cryptography-49.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b", size = 5309779, upload-time = "2026-06-12T20:02:08.987Z" }, + { url = "https://files.pythonhosted.org/packages/20/2c/0622f20ff02b2ef32558733443805dc82fd4c275be01b2d19d14676f3a1b/cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6", size = 4749683, upload-time = "2026-06-12T20:02:03.335Z" }, + { url = "https://files.pythonhosted.org/packages/a3/5b/c5246635d5fd3b64e0d45ae10e99fd32fe9676a79915ccfe5a61ba9af1a5/cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c", size = 4337874, upload-time = "2026-06-12T20:02:54.323Z" }, + { url = "https://files.pythonhosted.org/packages/6d/88/05563c7fe2e914e87d1a536d06fe83e66b4e1d95cb593e05aea375531da8/cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7", size = 4700283, upload-time = "2026-06-12T20:01:34.822Z" }, + { url = "https://files.pythonhosted.org/packages/c4/b6/d7696e4e890d6ae1469935164c9e5215c557671cb78d6e3f458ccceaa632/cryptography-49.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68", size = 5265844, upload-time = "2026-06-12T20:01:24.09Z" }, + { url = "https://files.pythonhosted.org/packages/a9/3c/f3ad17eecc1a57b0ba236dc01f90e783c51f4a2f35f64777cc4f47a184b2/cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9", size = 4749290, upload-time = "2026-06-12T20:01:30.848Z" }, + { url = "https://files.pythonhosted.org/packages/4f/01/339573cf1023163a400b0b5d16f6d507de413b9f60be6fd1b77feeaf6737/cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f", size = 4834612, upload-time = "2026-06-12T20:01:29.246Z" }, + { url = "https://files.pythonhosted.org/packages/71/fd/577302e213a1be9468f92d1afef66fcf1ef83d516819d9992ca547f592bd/cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459", size = 4980804, upload-time = "2026-06-12T20:01:42.853Z" }, + { url = "https://files.pythonhosted.org/packages/1f/09/f42b1d190c5ba75f72062a387f8030d1d75f6ab035788f1d9c4b01de6525/cryptography-49.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e", size = 3810026, upload-time = "2026-06-12T20:02:39.262Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/5bb823f5bedcf80718cea7fbc95ec5515cca3769633c4b01a32be7f30e7c/cryptography-49.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a", size = 4025947, upload-time = "2026-06-12T20:01:25.745Z" }, + { url = "https://files.pythonhosted.org/packages/3d/df/40577043ca124e17012f408ddddaeb213b856336ac82ddb3bc915f39e29f/cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4", size = 4692429, upload-time = "2026-06-12T20:01:53.628Z" }, + { url = "https://files.pythonhosted.org/packages/2c/99/2d13299eb3dd27b02dcfaafcc91d6b5cb3329f7cbd6d8f51921acd566c1a/cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18", size = 4700968, upload-time = "2026-06-12T20:02:45.383Z" }, + { url = "https://files.pythonhosted.org/packages/a5/4d/9c0cd02f95e2602dd5e563da149ee0830abef3537be8b34dc56281ebe27a/cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69", size = 4697758, upload-time = "2026-06-12T20:01:41.13Z" }, + { url = "https://files.pythonhosted.org/packages/24/01/186c825898477d77e2324d5360fefe622ff1d8d1963ec0554e2cada8ec77/cryptography-49.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64", size = 5298863, upload-time = "2026-06-12T20:02:24.579Z" }, + { url = "https://files.pythonhosted.org/packages/b8/7b/62cbbab75d0659865bf0273790031544a0b16c8072d258f9428dcd8190dc/cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21", size = 4735983, upload-time = "2026-06-12T20:01:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/6c/72/3e798c064bc39e471008075d0f9bc9daf77a80879c092e4a8e170c585ed4/cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9", size = 4334173, upload-time = "2026-06-12T20:01:44.743Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ee/6fca21d1ac73e06f8bef71940abfd4d2f6472b4bca284d770f32bd4086f6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc", size = 4697298, upload-time = "2026-06-12T20:02:20.918Z" }, + { url = "https://files.pythonhosted.org/packages/67/d0/a5fcd3515f0bae49a7b6d0413cc1bdccdcc1fc0047037a0d480642cdc5d6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8", size = 5254338, upload-time = "2026-06-12T20:02:22.737Z" }, + { url = "https://files.pythonhosted.org/packages/a0/84/84fe36f19caf857d61cb7fc9c63035a47ffabd84ea12d1d393148efa3615/cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36", size = 4735650, upload-time = "2026-06-12T20:02:41.389Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a0/db537264e234f7273a73ec020873d6d6b39dfd8a53db78b550ca8320440e/cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e", size = 4834820, upload-time = "2026-06-12T20:01:51.847Z" }, + { url = "https://files.pythonhosted.org/packages/93/77/8df9eb486495979bccecd1062e2eaf435250e84437040295b57d09048b0b/cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b", size = 4967968, upload-time = "2026-06-12T20:02:12.524Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" }, +] + +[[package]] +name = "daemonize" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/20/96f7dbc23812cfe4cf479c87af3e4305d0d115fd1fffec32ddeee7b9c82b/daemonize-2.5.0.tar.gz", hash = "sha256:dd026e4ff8d22cb016ed2130bc738b7d4b1da597ef93c074d2adb9e4dea08bc3", size = 8759, upload-time = "2018-12-12T19:47:39.57Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/ad/1b20db02287afd40d3130a218ac5ce2f7d2ab581cfda29bada5e1c4bee17/daemonize-2.5.0-py2.py3-none-any.whl", hash = "sha256:9b6b91311a9d934ff3f5f766666635ca280d3de8e7137e4cd7d3f052543b989f", size = 5231, upload-time = "2018-12-12T19:47:37.831Z" }, +] + +[[package]] +name = "decorator" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/60/8b/32f9823da46cde7df2087faa08cd98d01b908f8dcab982cdba9c84e85355/decorator-5.3.1.tar.gz", hash = "sha256:4cbcdd55a6efadb9dbea26b858f4fb3264567b52d69ca0d25b721b553f60ea82", size = 58084, upload-time = "2026-05-18T06:03:28.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/7f/798705f5296a58ca505d600456748d1be48078eac8a7050d8a98bc9edb89/decorator-5.3.1-py3-none-any.whl", hash = "sha256:f47fe6fdbd2edd623ecfe36875d37aba411624e2670dd395dddae1358689bb3c", size = 10365, upload-time = "2026-05-18T06:03:26.517Z" }, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, +] + +[[package]] +name = "developer-disk-image" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/2c/9862a8437947bb78f32858cbf0e7f01add855001912266a7f85dcbef25e5/developer_disk_image-0.2.0.tar.gz", hash = "sha256:db568b22ec339d8b56b29ae9b420230eae322fe69be74cd99fd0effb0ef2e28f", size = 6563, upload-time = "2025-07-23T19:50:48.575Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/6b/a1e24aae1b67f80814076ad603431a2f43c7d193910823ecb69003fe3897/developer_disk_image-0.2.0-py3-none-any.whl", hash = "sha256:9c6e6f4e11831cc9ae0a9284222c538c90927434bd26f87398c64ec976aa94dd", size = 4574, upload-time = "2025-07-23T19:50:46.713Z" }, +] + +[[package]] +name = "editor" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "runs" }, + { name = "xmod" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/5f/fe06c2a13a5282dcef4c7133bb348d4125a9aa69c5fb49037a004599d73a/editor-1.8.0.tar.gz", hash = "sha256:b07e1bbcb8b33f05c2e6ed3ce77ee9756354ada840a18aad7c0536d967fe4c0b", size = 27455, upload-time = "2026-05-09T13:42:59.796Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/b8/6648cadf38b61045262bf2a534e526f04eb833275ad1fc77a2026e9f7e3a/editor-1.8.0-py3-none-any.whl", hash = "sha256:7d47ff88ae6c5f6c43d28c30b6f7fd59a24741175a1771ab06c969d946d7dfd0", size = 4012, upload-time = "2026-05-09T13:43:00.847Z" }, +] + +[[package]] +name = "enum-compat" +version = "0.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/46/8ed2368976d934652d8f33f1fdd86f5580fab45e890c90a848e83097a093/enum-compat-0.0.3.tar.gz", hash = "sha256:3677daabed56a6f724451d585662253d8fb4e5569845aafa8bb0da36b1a8751e", size = 1389, upload-time = "2019-10-14T18:48:35.264Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/ae/467bc4509246283bb59746e21a1a2f5a8aecbef56b1fa6eaca78cd438c8b/enum_compat-0.0.3-py3-none-any.whl", hash = "sha256:88091b617c7fc3bbbceae50db5958023c48dc40b50520005aa3bf27f8f7ea157", size = 1322, upload-time = "2019-10-14T18:48:37.663Z" }, +] + +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + +[[package]] +name = "fastapi" +version = "0.139.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/71/f4cfcd72fd94af40d1c76141b69c3d27acaa3641e8fd64872929ff6998b9/fastapi-0.139.1.tar.gz", hash = "sha256:99461bde7ac3fc34c78443da1f4dad3ca8f3182580029a2827692db216a8d7ae", size = 422782, upload-time = "2026-07-16T09:18:34.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/4d/73d2e5e891d56722f5b6def27f67e91332342a18468f2ea20aa5da9ff64d/fastapi-0.139.1-py3-none-any.whl", hash = "sha256:17faa81907751a8a85cd44c46f37fb576bde0078cb37de40bf1cd55de7104d87", size = 130147, upload-time = "2026-07-16T09:18:32.723Z" }, +] + +[[package]] +name = "gpxpy" +version = "1.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/ad/6f1a34e702c72cb495bb258396f237ded76c00f9fe67054a44d778d24ed9/gpxpy-1.6.2.tar.gz", hash = "sha256:a72c484b97ec42b80834353b029cc8ee1b79f0ffca1179b2210bb3baf26c01ae", size = 111695, upload-time = "2023-11-29T17:25:38.391Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/9f/62df6c1e52462bdd04275b36cec49efa9e8af7e7b834499eb288f73dcfbc/gpxpy-1.6.2-py3-none-any.whl", hash = "sha256:289bc2d80f116c988d0a1e763fda22838f83005573ece2bbc6521817b26fb40a", size = 42649, upload-time = "2023-11-29T17:25:35.76Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hexdump" +version = "3.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/55/b3/279b1d57fa3681725d0db8820405cdcb4e62a9239c205e4ceac4391c78e4/hexdump-3.3.zip", hash = "sha256:d781a43b0c16ace3f9366aade73e8ad3a7bd5137d58f0b45ab2d3f54876f20db", size = 12658, upload-time = "2016-01-22T14:40:19.589Z" } + +[[package]] +name = "humanfriendly" +version = "10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyreadline3", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702, upload-time = "2021-09-17T21:40:43.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794, upload-time = "2021-09-17T21:40:39.897Z" }, +] + +[[package]] +name = "hyperframe" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566, upload-time = "2025-01-22T21:41:49.302Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007, upload-time = "2025-01-22T21:41:47.295Z" }, +] + +[[package]] +name = "idna" +version = "3.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, +] + +[[package]] +name = "ifaddr" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/ac/fb4c578f4a3256561548cd825646680edcadb9440f3f68add95ade1eb791/ifaddr-0.2.0.tar.gz", hash = "sha256:cc0cbfcaabf765d44595825fb96a99bb12c79716b73b44330ea38ee2b0c4aed4", size = 10485, upload-time = "2022-06-15T21:40:27.561Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/1f/19ebc343cc71a7ffa78f17018535adc5cbdd87afb31d7c34874680148b32/ifaddr-0.2.0-py3-none-any.whl", hash = "sha256:085e0305cfe6f16ab12d72e2024030f5d52674afad6911bb1eee207177b8a748", size = 12314, upload-time = "2022-06-15T21:40:25.756Z" }, +] + +[[package]] +name = "inquirer3" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blessed" }, + { name = "editor" }, + { name = "readchar" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/4c/12a087bac2a235d3f134daa6186199e82abf512a0961d0012cdd1c74f737/inquirer3-0.6.1.tar.gz", hash = "sha256:aca0e26922e0ba38c83bce65a54b3ed1c5f91c4c9fb72068c96a885e2749babc", size = 15151, upload-time = "2024-07-02T00:02:59.091Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/62/545e48a919c685cf4c2ce423c1adb88c8cfa747237ca4e107e0870c758da/inquirer3-0.6.1-py3-none-any.whl", hash = "sha256:27b4ef0beb518ac4cc7df765304663b728ba0bd32f3346bede458644b56f0146", size = 18904, upload-time = "2024-07-02T00:02:58.01Z" }, +] + +[[package]] +name = "ipsw-parser" +version = "1.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coloredlogs" }, + { name = "construct" }, + { name = "plumbum" }, + { name = "pyimg4" }, + { name = "remotezip2" }, + { name = "requests" }, + { name = "typer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/0d/27e277301312cbb56a46ec7787e34c2d8b16ddcca201d0f858894693dcbc/ipsw_parser-1.7.3.tar.gz", hash = "sha256:412bbbb773b434b0f994bd043cdb57d90ab1a4ae72fa8489b71900cd856d36ea", size = 55085, upload-time = "2026-06-15T11:59:01.242Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/4b/7a9b2834a1c0933b869e1885aefa936a4223ae991f770a0d2a29f40fc3f0/ipsw_parser-1.7.3-py3-none-any.whl", hash = "sha256:8ae1c659e231ce25d2e376576dcf3c6120fe733b9211edb2257653a0855ae8bd", size = 39452, upload-time = "2026-06-15T11:59:00.238Z" }, +] + +[[package]] +name = "ipython" +version = "9.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "ipython-pygments-lexers" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "psutil", marker = "sys_platform != 'cygwin' and sys_platform != 'emscripten'" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/59/165d3b4d75cc34add3122c4417ecb229085140ac573103c223cd01dde96f/ipython-9.15.0.tar.gz", hash = "sha256:da2819ce2aa83135257df830660b1176d986c3d2876db24df01974fa955b2756", size = 4442580, upload-time = "2026-06-26T11:03:35.913Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/3a/948263ca3b9d65bb2b1b0c521b3a49fad5d59ada58724bd87d2bd5ff3f36/ipython-9.15.0-py3-none-any.whl", hash = "sha256:515ad9c3cdf0c932a5a9f6245419e8aba706b7bd03c3e1d3a1c83d9351d6aa6e", size = 630895, upload-time = "2026-06-26T11:03:33.809Z" }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, +] + +[[package]] +name = "jedi" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/b7/a3635f6a2d7cf5b5dd98064fc1d5fbbafcb25477bcea204a3a92145d158b/jedi-0.20.0.tar.gz", hash = "sha256:c3f4ccbd276696f4b19c54618d4fb18f9fc24b0aef02acf704b23f487daa1011", size = 3119416, upload-time = "2026-05-01T23:38:47.814Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/93/242e2eab5fe682ffcb8b0084bde703a41d51e17ee0f3a31ff0d9d813620a/jedi-0.20.0-py2.py3-none-any.whl", hash = "sha256:7bdd9c2634f56713299976f4cbd59cb3fa92165cc5e05ea811fb253480728b67", size = 4884812, upload-time = "2026-05-01T23:38:43.919Z" }, +] + +[[package]] +name = "jinxed" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ansicon", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/d7/6e6d474ec5eaeca6a61acc17766bb19563b3a372b4b9d92910078f5fe49f/jinxed-2.1.0.tar.gz", hash = "sha256:7e755b831faa2443d44fb4ce7c0202eb9c3ed39bd5bf1193365888f4f6092b54", size = 61287, upload-time = "2026-07-03T15:46:48.153Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/1d/0a6be99b69bb448448403a45a241aff0dca751832ae54f027ed940d2eb52/jinxed-2.1.0-py2.py3-none-any.whl", hash = "sha256:43b802d18b70e405d410fb66eb2837d1101e7e5ea922e666507bb43f34d11d09", size = 104999, upload-time = "2026-07-03T15:46:47.114Z" }, +] + +[[package]] +name = "loguru" +version = "0.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "python_version < '0'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" }, +] + +[[package]] +name = "lzfse" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/c6/a57f4134fa1afb7e658c58a2401838c811445a9fdc657008b4850d4e47d7/lzfse-0.4.2.tar.gz", hash = "sha256:c6895f8ca13eedd2e18b6e0c987c9468115030898409bc44a7aa8d4f4a42ceab", size = 48248, upload-time = "2024-04-19T21:30:31.015Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/5f/6d3d7ea34770a7e29d51960c74078e59cac967dc9546f18b0dacd0606fb2/lzfse-0.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b61c0cbae72b851aa0017c7285fa5d5ae3f3091768d7165aa96a068c08fdb6bf", size = 163304, upload-time = "2024-04-19T21:30:04.781Z" }, + { url = "https://files.pythonhosted.org/packages/76/a8/c219070d5f294d1b9ef690c64d1f10e62166ed068498152c46b45590983f/lzfse-0.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1dec0a096f2f938458fd6eb287a659695c0a99816f3fd05139e9084b4886dd3d", size = 166788, upload-time = "2024-04-19T21:30:06.4Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ec/f3b658e3d2708d781e16079ccdf3b2105a217881001cb4049c80b6b59da1/lzfse-0.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9959446028788cac43f187c73a226ea931d103f85d782307729fae819109d8d4", size = 166500, upload-time = "2024-04-19T21:30:03.416Z" }, + { url = "https://files.pythonhosted.org/packages/17/4f/41213324ee350c198101f85de819e4df840c206d70c3ae3283947b96d897/lzfse-0.4.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d355506c525c3147a6d6746c00289cb65179e7e3bf4a29497c7cf306daa9bcdb", size = 164415, upload-time = "2024-04-19T21:30:07.52Z" }, + { url = "https://files.pythonhosted.org/packages/8d/86/d8931d6031f1bae45077be8c9d49d0237aa15a07398a95cb2d64c3bb1902/lzfse-0.4.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fad5b6a5edc8f660fe25bb823ffed435417aca8f90ab45299e8161cfb56ee6c0", size = 161896, upload-time = "2024-04-19T21:30:09.257Z" }, + { url = "https://files.pythonhosted.org/packages/87/e9/09a8aee39daaeef123750f80db7ac8edbc790bdd66e783f82458a158eae8/lzfse-0.4.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0d28bd418228f3ff8a33c62995341a94081c1659179f53598ca5fe021520ad79", size = 168742, upload-time = "2024-04-19T21:30:10.745Z" }, + { url = "https://files.pythonhosted.org/packages/15/38/76aa6e4f4ef1ac99a538041de9e816e25c5284ff629808c23a75919822c0/lzfse-0.4.2-cp312-cp312-win32.whl", hash = "sha256:dddcecbeaa27eab0f9237aa46c6fbc27c482bdc4e48e4f6bc62ea253450d564e", size = 80046, upload-time = "2024-04-19T21:30:12.111Z" }, + { url = "https://files.pythonhosted.org/packages/63/f5/e693069835328732bc151912447e2a49594606f2965b35ba4af4c64e5fc4/lzfse-0.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:73ccb8d48897a458d6efe1fbccc942a583e2a3e822540e941b845207ac7f0ab5", size = 83542, upload-time = "2024-04-19T21:30:13.196Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/c0/9f7c9a46090390368a4d7bcb76bb87a4a36c421e4c0792cdb53486ffac7a/matplotlib_inline-0.2.2.tar.gz", hash = "sha256:72f3fe8fce36b70d4a5b612f899090cd0401deddc4ea90e1572b9f4bfb058c79", size = 8150, upload-time = "2026-05-08T17:33:33.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/09/5b161152e2d90f7b87f781c2e1267494aef9c32498df793f73ad0a0a494a/matplotlib_inline-0.2.2-py3-none-any.whl", hash = "sha256:3c821cf1c209f59fb2d2d64abbf5b23b67bcb2210d663f9918dd851c6da1fcf6", size = 9534, upload-time = "2026-05-08T17:33:32.055Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mission-core-ios-capture-lab" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "pymobiledevice3" }, + { name = "python-pcapng" }, +] + +[package.metadata] +requires-dist = [ + { name = "pymobiledevice3", specifier = "==9.36.0" }, + { name = "python-pcapng", specifier = "==2.1.1" }, +] + +[[package]] +name = "opack2" +version = "0.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "arrow" }, + { name = "construct" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/ac/8e7d6698282a2130c99fab2fffa3cb1910688ffee16ec36e3d1f71375adf/opack2-0.0.1.tar.gz", hash = "sha256:0deb0b5d7094253f631db201f3493094a5821404f736ab0234ba805ee93d57b2", size = 42047, upload-time = "2024-12-15T22:17:14.385Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/2b/92c2667b9d1d955d0c79e7bc4e38a9dfc51ef462617d5e604ffa3e0ceba2/opack2-0.0.1-py3-none-any.whl", hash = "sha256:11fd1ccc3f0370cf913ca51e96173ba4042bd4d131547d34c81b27a3cac3773c", size = 29792, upload-time = "2024-12-15T22:17:12.085Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "parameter-decorators" +version = "0.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/40/b9/03b5e409930cb4cff0f25f6f3199145f4319a88d5f9be7ee17882359f72d/parameter_decorators-0.0.2.tar.gz", hash = "sha256:499ec96e71394705be9e3eeb28542aab4875694042516c31051d5dcb0488028e", size = 40958, upload-time = "2023-01-31T20:30:57.438Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/7b/1fa9000dbe427745af3eda3f1690d86e2161b6cfcd56b16cd76008a8f2c5/parameter_decorators-0.0.2-py2.py3-none-any.whl", hash = "sha256:8f89280356f7ea983725bf47534c22171fbd10a13d98159f44f3c55ba8ae73e7", size = 30442, upload-time = "2023-01-31T20:30:55.579Z" }, +] + +[[package]] +name = "parso" +version = "0.8.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/4b/90c937815137d43ce71ba043cd3566221e9df6b9c805f24b5d138c9d40a7/parso-0.8.7.tar.gz", hash = "sha256:eaaac4c9fdd5e9e8852dc778d2d7405897ec510f2a298071453e5e3a07914bb1", size = 401824, upload-time = "2026-05-01T23:13:02.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/5d/8268b644392ee874ee82a635cd0df1773de230bde356c38de28e298392cc/parso-0.8.7-py2.py3-none-any.whl", hash = "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c", size = 107025, upload-time = "2026-05-01T23:12:58.867Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[package]] +name = "pillow" +version = "12.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/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" }, +] + +[[package]] +name = "plumbum" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/6a/1d1b143420fcdfc8902f2db6b7d1d2325211461c5f2a43c849de7afad688/plumbum-2.0.1.tar.gz", hash = "sha256:61623f856dcb09eb20dcd5aa708dfb3cd04b6f4ab10224d39303b163bb1c4c61", size = 377668, upload-time = "2026-06-08T14:44:06.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/2d/d741fbbbcba7eb6f9f1a829373c558114d59cb882b95f941aa0dc060861f/plumbum-2.0.1-py3-none-any.whl", hash = "sha256:27a454980f91689aae8f18242a36daaf2636219171cf0e6a849744aa1d6fff85", size = 164460, upload-time = "2026-06-08T14:44:04.75Z" }, +] + +[[package]] +name = "pmd-net-addr" +version = "0.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/67/a500d983118cbd41ee7dda4cbbbfc3978df108ce842035181402f973fdab/pmd_net_addr-0.0.2.tar.gz", hash = "sha256:89d93332ff25f5cf40d55cd517ef7198088c4dd443d5d0229a35071afc60c577", size = 48355, upload-time = "2026-06-23T17:10:46.1Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/77/7dbbaa53ebd5c83add79ee65a2bffbb7cbd7572a9ff72c83717bee868ba9/pmd_net_addr-0.0.2-py3-none-any.whl", hash = "sha256:d3eb9630787042ae4387a1cbee1336414743fb3088284c61b4a034f72a3f4b22", size = 69869, upload-time = "2026-06-23T17:10:41.456Z" }, +] + +[[package]] +name = "pmd-net-proto" +version = "0.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pmd-net-addr" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/07/1da65310c8b10ce753ad25eb78a137849d2aa943d2336922c7bed5339774/pmd_net_proto-0.0.2.tar.gz", hash = "sha256:e26d9bd2ff16f7a0bf22b8ab3531e8c0f245cb4fadfdc62ca9ea3f89f25e5697", size = 197215, upload-time = "2026-06-23T17:10:47.093Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/56/dc6fc426de59c3994ba1842b16152153c253ec37f8ae0fca89e1f30f46de/pmd_net_proto-0.0.2-py3-none-any.whl", hash = "sha256:619dd5e772046c5a5920650d9d34534e2c464b39130aea372727b8251a0d8679", size = 500360, upload-time = "2026-06-23T17:10:42.834Z" }, +] + +[[package]] +name = "pmd-pytcp" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pmd-net-addr" }, + { name = "pmd-net-proto" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2b/61/624e1edc1824409bb08f4ec14953b7df728d295a649f1d47e57599575384/pmd_pytcp-0.1.0.tar.gz", hash = "sha256:2a72bcf1d14390f4a6bf08ad6ab1f934c607ca311d3fdb8092aedc17c630d552", size = 556347, upload-time = "2026-07-13T07:51:05.489Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/03/94743bf0c774129ea7d9516d4d6fda705ab1fe81dcd211d0dfbfd251c1cc/pmd_pytcp-0.1.0-py3-none-any.whl", hash = "sha256:3b87e3fc228c628893557cce5f6622078193594ecbe3a57eb81dc95610950d4f", size = 755983, upload-time = "2026-07-13T07:51:03.71Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + +[[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/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 = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pycrashreport" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/9c/1e1c2d84f746972cbe4d5e65f816008c64e427fc680f55115aa313683c23/pycrashreport-2.0.0.tar.gz", hash = "sha256:31d5e32faa3a047fe01e923bde3eaf1ec86b23e264babf2fc8f7b61fe4812342", size = 102682, upload-time = "2026-03-30T22:35:19.705Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/ec/be636536ab13a63e3385a82c841d08ce2a51712ffc2a337cd24afc12e038/pycrashreport-2.0.0-py3-none-any.whl", hash = "sha256:12feb922c3349cdb33b468d105915f5f829c53df25640c5c2241bc4182bdb422", size = 34244, upload-time = "2026-03-30T22:35:18.478Z" }, +] + +[[package]] +name = "pycryptodome" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/a6/8452177684d5e906854776276ddd34eca30d1b1e15aa1ee9cefc289a33f5/pycryptodome-3.23.0.tar.gz", hash = "sha256:447700a657182d60338bab09fdb27518f8856aecd80ae4c6bdddb67ff5da44ef", size = 4921276, upload-time = "2025-05-17T17:21:45.242Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/6c/a1f71542c969912bb0e106f64f60a56cc1f0fabecf9396f45accbe63fa68/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:187058ab80b3281b1de11c2e6842a357a1f71b42cb1e15bce373f3d238135c27", size = 2495627, upload-time = "2025-05-17T17:20:47.139Z" }, + { url = "https://files.pythonhosted.org/packages/6e/4e/a066527e079fc5002390c8acdd3aca431e6ea0a50ffd7201551175b47323/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cfb5cd445280c5b0a4e6187a7ce8de5a07b5f3f897f235caa11f1f435f182843", size = 1640362, upload-time = "2025-05-17T17:20:50.392Z" }, + { url = "https://files.pythonhosted.org/packages/50/52/adaf4c8c100a8c49d2bd058e5b551f73dfd8cb89eb4911e25a0c469b6b4e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490", size = 2182625, upload-time = "2025-05-17T17:20:52.866Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e9/a09476d436d0ff1402ac3867d933c61805ec2326c6ea557aeeac3825604e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8987bd3307a39bc03df5c8e0e3d8be0c4c3518b7f044b0f4c15d1aa78f52575", size = 2268954, upload-time = "2025-05-17T17:20:55.027Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c5/ffe6474e0c551d54cab931918127c46d70cab8f114e0c2b5a3c071c2f484/pycryptodome-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0698f65e5b570426fc31b8162ed4603b0c2841cbb9088e2b01641e3065915b", size = 2308534, upload-time = "2025-05-17T17:20:57.279Z" }, + { url = "https://files.pythonhosted.org/packages/18/28/e199677fc15ecf43010f2463fde4c1a53015d1fe95fb03bca2890836603a/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:53ecbafc2b55353edcebd64bf5da94a2a2cdf5090a6915bcca6eca6cc452585a", size = 2181853, upload-time = "2025-05-17T17:20:59.322Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ea/4fdb09f2165ce1365c9eaefef36625583371ee514db58dc9b65d3a255c4c/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:156df9667ad9f2ad26255926524e1c136d6664b741547deb0a86a9acf5ea631f", size = 2342465, upload-time = "2025-05-17T17:21:03.83Z" }, + { url = "https://files.pythonhosted.org/packages/22/82/6edc3fc42fe9284aead511394bac167693fb2b0e0395b28b8bedaa07ef04/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:dea827b4d55ee390dc89b2afe5927d4308a8b538ae91d9c6f7a5090f397af1aa", size = 2267414, upload-time = "2025-05-17T17:21:06.72Z" }, + { url = "https://files.pythonhosted.org/packages/59/fe/aae679b64363eb78326c7fdc9d06ec3de18bac68be4b612fc1fe8902693c/pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886", size = 1768484, upload-time = "2025-05-17T17:21:08.535Z" }, + { url = "https://files.pythonhosted.org/packages/54/2f/e97a1b8294db0daaa87012c24a7bb714147c7ade7656973fd6c736b484ff/pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2", size = 1799636, upload-time = "2025-05-17T17:21:10.393Z" }, + { url = "https://files.pythonhosted.org/packages/18/3d/f9441a0d798bf2b1e645adc3265e55706aead1255ccdad3856dbdcffec14/pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c", size = 1703675, upload-time = "2025-05-17T17:21:13.146Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/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 = "pygnuutils" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/03/20/6a828fe03d6586241b824ee06c7da854ea351716006a62fd2548eaa5710b/pygnuutils-0.1.1.tar.gz", hash = "sha256:51ab4f27deb59102b7a04192f7585ff2b3a9de03739982f0b19d3cd4d16bea76", size = 61704, upload-time = "2023-05-12T12:57:00.747Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/17/58ee1f079114ea360601ccd331fd73032701abfe9278495e3dec0c35ef3f/pygnuutils-0.1.1-py3-none-any.whl", hash = "sha256:3b690540cc13f2c763250ee5cc647e9c81055d1002b1bcf7ac07ea6d259a21c5", size = 46351, upload-time = "2023-05-12T12:56:58.211Z" }, +] + +[[package]] +name = "pyimg4" +version = "0.8.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "apple-compress", marker = "sys_platform == 'darwin'" }, + { name = "asn1" }, + { name = "click" }, + { name = "lzfse", marker = "sys_platform != 'darwin'" }, + { name = "pycryptodome" }, + { name = "pylzss" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3c/5c/69bad79b4e9d7be46063e9ea15e82c79ae0dec90811501453003588aab62/pyimg4-0.8.8.tar.gz", hash = "sha256:aafebb2bc7922f6cf551b1b9e982d2da039cbeb08f0a2e2048a1818d51c76da1", size = 173091, upload-time = "2025-04-01T12:50:25.494Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/49/39e043b69b633a3ea2a0c3169baf82f332c311bc0460ad4d0601673e0016/pyimg4-0.8.8-py3-none-any.whl", hash = "sha256:55eef0d61c9e612c4e213ca8467c2de868701709cc844d4308c942d6f3f724fb", size = 16273, upload-time = "2025-04-01T12:50:23.836Z" }, +] + +[[package]] +name = "pyiosbackup" +version = "0.2.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bpylist2" }, + { name = "click" }, + { name = "construct" }, + { name = "cryptography" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/ab/c7c3caf6785ced43d02c8c37be3c84720c40d6a9b237eb2ff560af4114e1/pyiosbackup-0.2.4.tar.gz", hash = "sha256:10b4d2a11c9bedc93a5467cad3addbe18bc2dc434156920e7223226ed4d0beb7", size = 49463, upload-time = "2026-02-02T14:15:55.408Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/5b/e6e7f343116e9ea067ab1c5c5dbb42b277a6340ad6fd14f692db8ca915b0/pyiosbackup-0.2.4-py3-none-any.whl", hash = "sha256:c71ae67d012c13e01a5139687eb5bfaaa4e7722e6519c3f0a246a87e015f1b9e", size = 38487, upload-time = "2026-02-02T14:15:53.41Z" }, +] + +[[package]] +name = "pykdebugparser" +version = "1.2.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "construct" }, + { name = "pygments" }, + { name = "termcolor" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6b/32/9838c8e756717fecf47f1aedd1e21ec70dbe1e4bd6d96f9060d7274aae72/pykdebugparser-1.2.8.tar.gz", hash = "sha256:fa692b469874c2bfb830f55f854648e5f5641353e3b7be16573286ef0c05fabc", size = 81088, upload-time = "2026-07-04T16:08:10.682Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/2f/1ad4bb0cc86f319c35ecbcaf23383eb61c3d8dd16b74fb3e1a1ebf145794/pykdebugparser-1.2.8-py2.py3-none-any.whl", hash = "sha256:7ed4bdb427f3d84fb4ba6ca380de6113769cd93df8d1c88da79036a2c36b6a1e", size = 77654, upload-time = "2026-07-04T16:08:09.721Z" }, +] + +[[package]] +name = "pylzss" +version = "0.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/dc/9ae75ede398b7adf538f2d1dca0f96c645c4e96789f8039340a0ed6a8a8f/pylzss-0.3.4.tar.gz", hash = "sha256:16818631742488e53a34fda0d402d80edb2b812e11877801e21a9e5ce9b9db1c", size = 25144, upload-time = "2023-11-24T00:26:37.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/d2/57c3bed9cbfad30331f10390d2e515e0b88d84d03323d789bba920482848/pylzss-0.3.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b24e51aa8272c4ea3579040623bd8dcfd5fddc75fef11226de8a0130dca6b218", size = 21072, upload-time = "2024-12-21T22:18:07.489Z" }, + { url = "https://files.pythonhosted.org/packages/1c/0d/1058c6fb4876afcf20240200400d5761f4b304070e4b1c2980341aa477f0/pylzss-0.3.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8f62bf74f808646c219d5790513898b3facdcaec51bb3f5ca0f9f8ca716bdab8", size = 21171, upload-time = "2023-11-24T00:25:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/48/96/fa400e1d2ce7e01b72400f94f7fa906890ae4f98a24f1ac26abfb8e7d5b4/pylzss-0.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:492898f2f8adddb9886a7c53b12e6b632f92d281173cf5c07a6acb5e682a83f0", size = 21530, upload-time = "2024-12-21T22:18:09.88Z" }, + { url = "https://files.pythonhosted.org/packages/b9/5f/2ce1e7ac2404e14ebe54e46322066c9fc217e4c5996add2a34320be69de9/pylzss-0.3.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b30a74e524d7e54efe67fb28150e2fce1c9fce57de664c72b25a0ea31694e369", size = 32637, upload-time = "2023-11-24T00:25:17.307Z" }, + { url = "https://files.pythonhosted.org/packages/9f/f4/7b683fa861a4c29a6cb327d4e0c390c32571bec0adb9ea5e06e12abe01f5/pylzss-0.3.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25fd33d6ff2ae4e17a5060f3b6ee474e7e2d052c9344e840806e86cc47dcd7eb", size = 31763, upload-time = "2023-11-24T00:25:18.467Z" }, + { url = "https://files.pythonhosted.org/packages/c1/70/a914e958a7c3684d2cb40683474fedcb50daf84bb87de79932a2cb7df3bc/pylzss-0.3.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c59b4a3a8a52e5294526a058e4d308879915ffaa9da56e1b19b9fa78c5de47da", size = 32297, upload-time = "2023-11-24T00:25:20.203Z" }, + { url = "https://files.pythonhosted.org/packages/dd/5d/4ac71fac7ea1fd6b028d94dcf2261295df1f9a678f12e8294c90a5e1ca8c/pylzss-0.3.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:54a3449a5fd5b81a0c908a9f30efec29e25796899900e012c8c126d5fe16857b", size = 37269, upload-time = "2023-11-24T00:25:21.371Z" }, + { url = "https://files.pythonhosted.org/packages/90/91/3ca21086e5229896c92084987f761bb713725ab511b02d2fd2e8e5e34cc4/pylzss-0.3.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:d72350845d0b827ec495a844b1c439d3f5acc2f25fcc276fa149330cfb6bc48b", size = 37083, upload-time = "2023-11-24T00:25:22.38Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e7/0ae557a5c89c28b6707503b877de6685b3c9cf40d2375ed3f1fa7bc53ea6/pylzss-0.3.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6b18c8f88081b1f29bebf77027225ef043e31cfce797e2b6cee389e895628569", size = 37241, upload-time = "2023-11-24T00:25:23.457Z" }, + { url = "https://files.pythonhosted.org/packages/13/2e/d23431e22d2659e52c59ab2bb95dd1f55dd8e9f9933aec76cc81c7f5a78e/pylzss-0.3.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c0e18f517e02dde46c91ab221ed2c7502675181a9458f99fd5314a15c6e5c2f9", size = 32514, upload-time = "2024-12-21T22:18:12.319Z" }, + { url = "https://files.pythonhosted.org/packages/0f/71/0fccf6be45a800d5a85e20c2576c3b1913bda10277eb92dca5ca8fda21ad/pylzss-0.3.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9ea4459165e4083686861cbf80b0561aebd099371362fb3fd2c90f785f9e090e", size = 31832, upload-time = "2024-12-21T22:18:13.403Z" }, + { url = "https://files.pythonhosted.org/packages/2f/8a/c8eacdb9b964f93fc7d072d533304cd877c663afa2c37fc529d4160fb80a/pylzss-0.3.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e61c21c17e8b0b3cba5c72dffc219313e12120989d93de6ca36ba1a29714ef4f", size = 31744, upload-time = "2024-12-21T22:18:14.754Z" }, + { url = "https://files.pythonhosted.org/packages/04/65/e9e3aefd0e0fb552a0d7f53acc1546ad4bce888b698d6c81334c66d4e194/pylzss-0.3.4-cp312-cp312-win32.whl", hash = "sha256:81adbc656e2143e89e23d5d3daa5452e8109c9621b3148b59d8e8fab59809197", size = 22712, upload-time = "2023-11-24T00:25:24.614Z" }, + { url = "https://files.pythonhosted.org/packages/0a/01/448093975bb32d621dd703dffe7f1dcbb25bb2335e235affa972ff05871d/pylzss-0.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:6c0f2a80e8028f8af015f2c6d1c637c888607b569c0b522e18d326a0c78236e2", size = 23748, upload-time = "2023-11-24T00:25:25.731Z" }, +] + +[[package]] +name = "pymobiledevice3" +version = "9.36.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asn1" }, + { name = "av", marker = "sys_platform != 'darwin'" }, + { name = "bpylist2" }, + { name = "coloredlogs" }, + { name = "construct" }, + { name = "construct-typing" }, + { name = "cryptography" }, + { name = "daemonize" }, + { name = "defusedxml" }, + { name = "developer-disk-image" }, + { name = "fastapi" }, + { name = "gpxpy" }, + { name = "hexdump" }, + { name = "hyperframe" }, + { name = "ifaddr" }, + { name = "inquirer3" }, + { name = "ipsw-parser" }, + { name = "ipython" }, + { name = "opack2" }, + { name = "packaging" }, + { name = "parameter-decorators" }, + { name = "pillow" }, + { name = "plumbum" }, + { name = "pmd-pytcp" }, + { name = "prompt-toolkit" }, + { name = "psutil" }, + { name = "pycrashreport" }, + { name = "pygments" }, + { name = "pygnuutils" }, + { name = "pyimg4" }, + { name = "pyiosbackup" }, + { name = "pykdebugparser" }, + { name = "python-pcapng" }, + { name = "pytun-pmd3" }, + { name = "pyusb" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "qh3" }, + { name = "requests" }, + { name = "srptools" }, + { name = "sslpsk-pmd3" }, + { name = "tqdm" }, + { name = "typer" }, + { name = "typer-injector" }, + { name = "uvicorn" }, + { name = "wsproto" }, + { name = "xonsh" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/92/f3/a11410db9e8723e4f82e2f2752f8e5b85022784bbe9af44cd8081db738a8/pymobiledevice3-9.36.0.tar.gz", hash = "sha256:b822906f2ba38c2924c8da42a6311938371d6243d49853347a718f7f28587398", size = 1010756, upload-time = "2026-07-15T05:57:47.42Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/ab/b1ef64db768651156a22a175eb145fcc859420c5e4802536288893522c8a/pymobiledevice3-9.36.0-py3-none-any.whl", hash = "sha256:18bcb18845147fe13d959b0257896abbcdaa4c6c9b9b7445498678e3fa939b5c", size = 1036895, upload-time = "2026-07-15T05:57:44.89Z" }, +] + +[[package]] +name = "pyreadline3" +version = "3.5.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/6d/f94028646d7bbe6d9d873c47ee7c246f2d29129d253f0d96cb6fcab70733/pyreadline3-3.5.6.tar.gz", hash = "sha256:61e53218b99656091ddb077df9e71f25850e72e030b6183b39c9b7e6e4f4a9bf", size = 100368, upload-time = "2026-05-14T17:55:04.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/5e/35c856e186b74678c24927847ad9895a51f1bc02a0c6126477a6c6040064/pyreadline3-3.5.6-py3-none-any.whl", hash = "sha256:8449b734232e42a5dcd74048e39b60db2839a4c38cf3ae2bf7707d58b5389c0d", size = 85243, upload-time = "2026-05-14T17:55:03.262Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-pcapng" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3c/2f/72534730a81205cd3ca32d65b4760c927e6ffc69a3d06ff3d1edda64ae3e/python-pcapng-2.1.1.tar.gz", hash = "sha256:6477e72513165954e56d5836ebbd43ac7f8d28c4640f78d63d6506d1692ddc74", size = 82317, upload-time = "2022-08-23T18:59:11.266Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/a4/141a5fbb51c8e3dee10445d02785d44f8a66a150af6916b4e1776e5065c6/python_pcapng-2.1.1-py3-none-any.whl", hash = "sha256:2c83e9f9f60d61cbb6c86f80fa9e3d722f1bb606a59a64a96d6ba0179d97ffcf", size = 33503, upload-time = "2022-08-23T18:59:08.754Z" }, +] + +[[package]] +name = "pytun-pmd3" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/8a/6e73c70ddf5fdf9cf359cdc458f38cf795c4a653c870a4a49c8768e5db86/pytun_pmd3-3.0.3.tar.gz", hash = "sha256:7fa822092139b4b77e08fad9233dbd4a6fcca2cf2423b0775db962ca5e87a017", size = 766573, upload-time = "2026-01-27T11:51:54.034Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/07/af109c676666747a68da662d0847b71c83e15377578c12d64f0495b828f4/pytun_pmd3-3.0.3-py3-none-any.whl", hash = "sha256:d3e884bb907d6050ba4f9e680392a67d8590dbee47c7ae0b638b13105ae562e1", size = 766465, upload-time = "2026-01-27T11:51:48.463Z" }, +] + +[[package]] +name = "pyusb" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/6b/ce3727395e52b7b76dfcf0c665e37d223b680b9becc60710d4bc08b7b7cb/pyusb-1.3.1.tar.gz", hash = "sha256:3af070b607467c1c164f49d5b0caabe8ac78dbed9298d703a8dbf9df4052d17e", size = 77281, upload-time = "2025-01-08T23:45:01.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/b8/27e6312e86408a44fe16bd28ee12dd98608b39f7e7e57884a24e8f29b573/pyusb-1.3.1-py3-none-any.whl", hash = "sha256:bf9b754557af4717fe80c2b07cc2b923a9151f5c08d17bdb5345dac09d6a0430", size = 58465, upload-time = "2025-01-08T23:45:00.029Z" }, +] + +[[package]] +name = "pywin32" +version = "312" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/ff/32aa7d2ed0ab12b323aaa64f9b75e6ad4f8fd09f9ccfc28c79414d46838d/pywin32-312-cp312-cp312-win32.whl", hash = "sha256:dab4f65ac9c4e48400a2a0530c46c3c579cd5905ecd11b80692373915269208b", size = 6371877, upload-time = "2026-06-04T07:49:28.836Z" }, + { url = "https://files.pythonhosted.org/packages/03/d9/77040d3b43df3f3be32ea289433d660d2727f5ba327bc73be835127d9d60/pywin32-312-cp312-cp312-win_amd64.whl", hash = "sha256:b457f6d628a47e8a7346ce22acb7e1a46a4a78b52e1d17e1af56871bd19a93bc", size = 6914841, upload-time = "2026-06-04T07:49:31.85Z" }, + { url = "https://files.pythonhosted.org/packages/e3/cc/7b1ec671775756020a0ee7f4feeaf3c568f0ab86bd3900088cf986937a92/pywin32-312-cp312-cp312-win_arm64.whl", hash = "sha256:6017c58e12f6809fbb0555b75df144c2922a9ffd18e4b9b5afa863b6c1a9d950", size = 6727901, upload-time = "2026-06-04T07:49:34.244Z" }, +] + +[[package]] +name = "qh3" +version = "1.9.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/ae/9d42d6df0ab9a014138332346fd690f7b0be0556861421d2459caec28d6f/qh3-1.9.4.tar.gz", hash = "sha256:bd2ea9baf19656c544a48a56a195f2ac257cd973b566f5f2998fa3b7446281a1", size = 346724, upload-time = "2026-07-13T06:36:32.05Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/48/646ee80a03ef7fac2b70cccea8a8f17c284fbb86fb854227f6ce70898053/qh3-1.9.4-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:26f7c8ed1a7ac0e25b654f030a4f472830c80890cc284dc40a31c17ecc21fbf0", size = 4376855, upload-time = "2026-07-13T06:34:02.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/86/d6777ff057a45debcfeb2a55b21692ce0c6f32a3c3a45531e83cf8ed4ef6/qh3-1.9.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a57b3dfe5dd2da2084adea63dba0bf587a4096ebf9207339187c90139d1f460d", size = 2164636, upload-time = "2026-07-13T06:34:04.437Z" }, + { url = "https://files.pythonhosted.org/packages/0e/7b/7ebfd6babc3aaac74fb40beb5f85c289e2dcc40116282a3e48db99558eb7/qh3-1.9.4-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7fa1a3debb5199da3c7f45b0ca6efa55174dfd6229370e774099073143ebe9e9", size = 1865942, upload-time = "2026-07-13T06:34:06.03Z" }, + { url = "https://files.pythonhosted.org/packages/62/8f/bbc0b834b1252356d49d8813156606d426eb7f90ed5bc6aa479bc8aef7c6/qh3-1.9.4-cp37-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6dc9ff40d3a06f27b47d93417666f03ba6fd6b9585d9711ebbddf84fa4b7c50f", size = 2038977, upload-time = "2026-07-13T06:34:09.165Z" }, + { url = "https://files.pythonhosted.org/packages/c4/32/fe10f124f2062cb9004c18c1b506d29961e2e4d72d2c20ea1b3503ebe6fe/qh3-1.9.4-cp37-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:010b132c33719af3df4c1d6f569e52a2a20046c55a31c5532e741019c8c53868", size = 2042505, upload-time = "2026-07-13T06:34:10.932Z" }, + { url = "https://files.pythonhosted.org/packages/c8/eb/b7b93687c033d4291c8207f47b2b0c7fdb738db89ab5beceae08d866734f/qh3-1.9.4-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:124045b0c04c05afc038a239d7717019b4c4b9d0cb967040b4331f4d588eff7d", size = 2039083, upload-time = "2026-07-13T06:34:12.952Z" }, + { url = "https://files.pythonhosted.org/packages/1b/22/b064ec3fd14f121a8710f41bb42680ed96635b55bc78f2e85d475e8dbd65/qh3-1.9.4-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a94a1726bf46a451a0295d68fafde28794e83ab6fda7457cbd9309c38151de9", size = 2092767, upload-time = "2026-07-13T06:34:14.925Z" }, + { url = "https://files.pythonhosted.org/packages/29/92/54bac0ab3aad24b84f1e3c4868399e42cb8678054650f5f2e436acc2554c/qh3-1.9.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b361e627b6a529ecb231c1d5f3f2abd6d1952b0d7cc02c7718d9657d55dae37", size = 2372367, upload-time = "2026-07-13T06:34:16.589Z" }, + { url = "https://files.pythonhosted.org/packages/4c/78/99f27d776e54724050d41a14762e25e6b8d251b20943b59928f4fca366ad/qh3-1.9.4-cp37-abi3-manylinux_2_39_riscv64.whl", hash = "sha256:43b6cfb2240836e3e54bbe61ad1b8fa5716516ff5359131a2421c55676a24f26", size = 2017823, upload-time = "2026-07-13T06:34:18.114Z" }, + { url = "https://files.pythonhosted.org/packages/9f/f7/ac383f019b69e9c7021e300cde900de36953d44e408fbd323993575ce765/qh3-1.9.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e6db3def610e9c2d2f3602d3e9c8b2156273225f6eedf74a4749d3bc21af8167", size = 2353468, upload-time = "2026-07-13T06:34:19.751Z" }, + { url = "https://files.pythonhosted.org/packages/17/db/d60b95e872368e51756877e98841b77691acb833e89997839a7adfe053d0/qh3-1.9.4-cp37-abi3-musllinux_1_1_armv7l.whl", hash = "sha256:22d5879dc93d2c7ee14cc5933b12c7780d9c7705dff4553becea152fcb767d99", size = 2136024, upload-time = "2026-07-13T06:34:21.837Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ed/a84cbc776edb96b8502d9e9093432431927dd827ca2d064b196a43418bb7/qh3-1.9.4-cp37-abi3-musllinux_1_1_i686.whl", hash = "sha256:2c2b2005fe6c605d61cac05ec2b3c019cc546184fa43444f3d1e3d22ed020ab2", size = 2235665, upload-time = "2026-07-13T06:34:23.425Z" }, + { url = "https://files.pythonhosted.org/packages/7c/0b/ef538579ba7144fadc6865df30bc9ccb3b78ae1dd61899a5da115b0ea461/qh3-1.9.4-cp37-abi3-musllinux_1_1_riscv64.whl", hash = "sha256:e991b0f20b172fea466d0ffd499a85bf2f26decdeeda537275c72b5f7b26ccfe", size = 2128989, upload-time = "2026-07-13T06:34:25.636Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ad/fb9f922d0ea410c7f7f19a746f02be0fab2b5b5c061872ca0965fe767c52/qh3-1.9.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c13775ed27a88b297f6a1aa05ffb286879453f99e1e53e0af2b22e7ed8f92ebf", size = 2593826, upload-time = "2026-07-13T06:34:27.288Z" }, + { url = "https://files.pythonhosted.org/packages/75/a5/aeef82a0edae7003d42c0587dfbceae4ff0ce375f8c2c13398a4aa4a29f3/qh3-1.9.4-cp37-abi3-win32.whl", hash = "sha256:952abe1be90c0114993886792a8a5ec2211f8391df41227f5b7a5a02ff71bc26", size = 1868838, upload-time = "2026-07-13T06:34:28.944Z" }, + { url = "https://files.pythonhosted.org/packages/05/48/77f3055a3790a515fc59b37cdf1f23c374f8a235c6fa1b110153b78668e4/qh3-1.9.4-cp37-abi3-win_amd64.whl", hash = "sha256:5543cb40a48d401f3fa4ec89398e3a213634f195716a9d06d70417c05edbbb32", size = 2130155, upload-time = "2026-07-13T06:34:30.526Z" }, + { url = "https://files.pythonhosted.org/packages/d3/bc/aec1f86da0d093a319f43d88b005b4b71efa94e8f675c2c9764098c2abb2/qh3-1.9.4-cp37-abi3-win_arm64.whl", hash = "sha256:d0bd73b3e73f4f80d2430d410b532222ecf57e9d4fe8600b7683c775cbff9479", size = 1980028, upload-time = "2026-07-13T06:34:32.376Z" }, +] + +[[package]] +name = "readchar" +version = "4.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/49/a10341024c45bed95d13197ec9ef0f4e2fd10b5ca6e7f8d7684d18082398/readchar-4.2.2.tar.gz", hash = "sha256:e3b270fe16fc90c50ac79107700330a133dd4c63d22939f5b03b4f24564d5dd8", size = 9762, upload-time = "2026-04-06T19:45:54.226Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/ca/36133653e00939922dd1416f4c56177361289172a30563fcb9552c9ccde4/readchar-4.2.2-py3-none-any.whl", hash = "sha256:92daf7e42c52b0787e6c75d01ecfb9a94f4ceff3764958b570c1dddedd47b200", size = 9401, upload-time = "2026-04-06T19:45:52.993Z" }, +] + +[[package]] +name = "remotezip2" +version = "0.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/47/94335875a4d5339f64943c1a68900f59a91ad987ab8fcd8e5201839e7cc5/remotezip2-0.0.2.tar.gz", hash = "sha256:db38fb14d0c297af6da8756808bb25f9d3b2c23c7639a14f7a74794b2fc9a261", size = 9967, upload-time = "2024-12-01T22:02:44.708Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/65/5161d4239db1dadee46f960f302bb640886da37297fe6401d804094df94c/remotezip2-0.0.2-py3-none-any.whl", hash = "sha256:9b01aacfa81329b47963e3183e65650f81438d76315695b0390b573b2512da4a", size = 6057, upload-time = "2024-12-01T22:02:43.247Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "rich" +version = "15.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, +] + +[[package]] +name = "runs" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "xmod" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/ae/095cb626504733e288a81f871f86b10530b787d77c50193c170daaca0df1/runs-1.3.0.tar.gz", hash = "sha256:cca304b631dbefec598c7bfbcfb50d6feace6d3a968734b67fd42d3c728f5a05", size = 4585, upload-time = "2026-02-03T15:59:58.974Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/b6/049c75d399ccf6e25abea0652b85bf7e7e101e0300aa9c1d284ad7061c0b/runs-1.3.0-py3-none-any.whl", hash = "sha256:e71a551cfa8da9ef882cac1d5a108bda78c9edee5b8d87e37c1003da5b6a7bed", size = 6406, upload-time = "2026-02-03T15:59:59.96Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "srptools" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/8a/d62af55a56d56216e96563bc9c29c2d16d957317742c2e5bd4e79a524b06/srptools-1.0.1.tar.gz", hash = "sha256:7fa4337256a1542e8f5bb4bed19e1d9aea98fe5ff9baf76693342a1dd6ac7c96", size = 15436, upload-time = "2020-09-12T02:06:28.244Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/b8/3f935d7f7d6bc25a88859beb0a6e246313012a12ae4844665322f771a5cb/srptools-1.0.1-py2.py3-none-any.whl", hash = "sha256:fccb44c949eb2507ba19f349a3087cb8fed2393014d7f0887b06c3186f19d1e9", size = 13183, upload-time = "2020-09-12T02:06:30.452Z" }, +] + +[[package]] +name = "sslpsk-pmd3" +version = "1.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/b0/1a652e294290fd75a9fd08f9003599a25a0be6e26918157f1128764f46ea/sslpsk-pmd3-1.0.3.tar.gz", hash = "sha256:16f1d46ce2d28e3f0c4993648decf2ce9accf5ad59781f606d6423e5e76b36b7", size = 17913, upload-time = "2024-02-09T16:07:27.448Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/8f/62fd774c050ae0b939ebf1b316f9f107839b72594e9112bd0f2e685812da/sslpsk_pmd3-1.0.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ab1bc72affa317c433249b3da8e81ec1cc707f9410b122219f1d89f711342c3d", size = 2190756, upload-time = "2024-02-09T16:04:54.926Z" }, + { url = "https://files.pythonhosted.org/packages/b6/dd/ca30e9f3cbe75176dae94360c889ff944fc0062c52b1e996f0bc14eaf5cb/sslpsk_pmd3-1.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25dbe8a557f497fa8c80c5ac4269127117c5a8a300812895971f14e99512efce", size = 25253, upload-time = "2024-02-09T16:02:34.573Z" }, + { url = "https://files.pythonhosted.org/packages/2b/01/2e08d220a83a2077a90db3691e6622776aeeb5cf2e2166135177212760da/sslpsk_pmd3-1.0.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc6a2b47ceb51cd25539f306a1142ac19e2f2725a1d686f180dcb77f662d37d8", size = 2001917, upload-time = "2024-02-09T16:01:30.293Z" }, + { url = "https://files.pythonhosted.org/packages/40/ea/4070d7fb62a263d90395b5d915ae25062864eacad0ccfe64d36ce2f65741/sslpsk_pmd3-1.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baaa6a7e4572ccc1334f5a2854b8d75820e04153cf2f8b7d8cf3d784ecbbfe8c", size = 2262108, upload-time = "2024-02-09T16:01:35.135Z" }, + { url = "https://files.pythonhosted.org/packages/df/ba/f64e1be1488e04a3bccff46fe4e4a44f71d4022ad5d0d1f3e81f388dbac8/sslpsk_pmd3-1.0.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:dd182615cb365476aabd770fb3579e39f05ad112098c16c094921a674c3c800c", size = 1060447, upload-time = "2024-02-09T16:01:33.467Z" }, + { url = "https://files.pythonhosted.org/packages/9e/82/7d0eb76100488a2185bef6bf70d63c9129cf4d47a94193f1fee7a65ca35f/sslpsk_pmd3-1.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:09d81f0c5b2ec2c533a6e627323cde005c5a2d85cda7824ab132fb0d7a10b32a", size = 996339, upload-time = "2024-02-09T16:01:37.743Z" }, + { url = "https://files.pythonhosted.org/packages/b1/08/eb04128800ea38281fd1b25ac40b57fb678c307ac7ed59eb5a8c884f0422/sslpsk_pmd3-1.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:ed5e101731e1c0a5a4d7018ef82385a986ff47ba07dd53a17bcb63b99d7b25c5", size = 30491, upload-time = "2024-02-09T16:10:02.295Z" }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + +[[package]] +name = "starlette" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/e3/7c1dc7381d9f8ab7d854328ebfa884e62cb3f3d8549ddfd37c7814f42afa/starlette-1.3.1.tar.gz", hash = "sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0", size = 2703240, upload-time = "2026-06-12T09:23:11.602Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/bb/2799cc2ede3ed41131f8975621e7213dfc7ef4acbbaadfa440f32500c370/starlette-1.3.1-py3-none-any.whl", hash = "sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6", size = 73632, upload-time = "2026-06-12T09:23:10.017Z" }, +] + +[[package]] +name = "termcolor" +version = "3.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/79/cf31d7a93a8fdc6aa0fbb665be84426a8c5a557d9240b6239e9e11e35fc5/termcolor-3.3.0.tar.gz", hash = "sha256:348871ca648ec6a9a983a13ab626c0acce02f515b9e1983332b17af7979521c5", size = 14434, upload-time = "2025-12-29T12:55:21.882Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/d1/8bb87d21e9aeb323cc03034f5eaf2c8f69841e40e4853c2627edf8111ed3/termcolor-3.3.0-py3-none-any.whl", hash = "sha256:cf642efadaf0a8ebbbf4bc7a31cec2f9b5f21a9f726f4ccbb08192c9c26f43a5", size = 7734, upload-time = "2025-12-29T12:55:20.718Z" }, +] + +[[package]] +name = "tqdm" +version = "4.68.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/5f/57ff8b434839e70dab45601284ea413e947a63799891b7553e5960a793a8/tqdm-4.68.4.tar.gz", hash = "sha256:19829c9673638f2a0b8617da4cdcb927e831cd88bcfcb6e78d42a4d1af131520", size = 792418, upload-time = "2026-07-07T09:58:18.369Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/2a/5e5e750890ada51017d18d0d4c30da696e5b5bd3180947729927628fc3cb/tqdm-4.68.4-py3-none-any.whl", hash = "sha256:5168118b2368f48c561afda8020fd79195b1bdb0bdf8086b88442c267a315dc2", size = 676612, upload-time = "2026-07-07T09:58:16.256Z" }, +] + +[[package]] +name = "traitlets" +version = "5.15.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/a9/a2584b8313b89f94869ddb3c4074617a691de1812a614d2d50e32ca5a7a6/traitlets-5.15.1.tar.gz", hash = "sha256:7b1c07854fe25acb39e009bae49f11b79ff6cbb2f27999104e9110e7a6b53722", size = 163344, upload-time = "2026-06-03T12:26:06.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/8d/1080ee4c231f361b6ce4470d556c8c435b67c7e0753aaa641497ee92f88b/traitlets-5.15.1-py3-none-any.whl", hash = "sha256:770a53705f84b81ac107e83a1b3328ff2dae16094d8fc3cfc004e4b22dfd8e92", size = 85858, upload-time = "2026-06-03T12:26:04.395Z" }, +] + +[[package]] +name = "typer" +version = "0.27.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "rich" }, + { name = "shellingham" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/37/78/fda3361b56efc27944f24225f6ecd13d96d6fcfe37bd0eb34e2f4c63f9fc/typer-0.27.0.tar.gz", hash = "sha256:629bd12ea5d13a17148125d9a264f949eb171fb3f120f9b04d85873cab054fa5", size = 203430, upload-time = "2026-07-15T19:21:07.007Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/03/26a383c9e58c213199d1aad1c3d353cfc22d4444ec6d2c0bf8ad02523843/typer-0.27.0-py3-none-any.whl", hash = "sha256:6f4b27631e47f077871b7dc30e933ec0131c1390fbe0e387ea5574b5bac9ccf1", size = 122716, upload-time = "2026-07-15T19:21:05.553Z" }, +] + +[[package]] +name = "typer-injector" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/11/7da0c6019c26cb660acc3329b44c3247286469de3dcd94013d0793598ecb/typer_injector-0.3.0.tar.gz", hash = "sha256:7fa5b411b86c31aeeb22fc180d0ebf615c2a71c8f2b1dc97ad4d06a4c235b3a5", size = 7996, upload-time = "2026-04-29T12:11:01.011Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/97/6217b630fc5ec896dc7226a36aafa26e87ae6c106043ec505490e52459fb/typer_injector-0.3.0-py3-none-any.whl", hash = "sha256:02ea50329f5e411592c5b8ef8387ffa043b58977f0431ec449f7b1f41717f90f", size = 7634, upload-time = "2026-04-29T12:10:59.526Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "tzdata" +version = "2026.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/92/ff/5a28bdfd8c3ebec42564ac7d0e54ca3db65044a9314a97f9564fa7a1e926/tzdata-2026.3.tar.gz", hash = "sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415", size = 198674, upload-time = "2026-07-10T08:50:37.887Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931", size = 348168, upload-time = "2026-07-10T08:50:36.46Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.51.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/65/b7c6c443ccc58678c91e1e973bbe2a878591538655d6e1d47f24ba1c51f3/uvicorn-0.51.0.tar.gz", hash = "sha256:f6f4b69b657c312f516dd2d268ab9ae6f254b11e4bac504f37b2ab58b24dd0b0", size = 94412, upload-time = "2026-07-08T10:59:05.962Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/ec/dbb7e5a6b91f86bfb9eb7d2988a2730907b6a729875b949c7f022e8b88fa/uvicorn-0.51.0-py3-none-any.whl", hash = "sha256:5d38af6cd620f2ae3849fb44fd4879e0890aa1febe8d47eb355fb45d93fe6a5b", size = 73219, upload-time = "2026-07-08T10:59:04.44Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/74/c6428f875774288bec1396f5bfcbc2d925700a4dad61727fd5f2b12f249d/wcwidth-0.8.2.tar.gz", hash = "sha256:91fbef97204b96a3d4d421609b80340b760cf33e26da123ff243d76b1fda8dda", size = 1466253, upload-time = "2026-06-29T18:11:11.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/42/3e5985a0a7e57de470b320c6d6a1a67c844f6737a587f3d44dd13d1819e7/wcwidth-0.8.2-py3-none-any.whl", hash = "sha256:d63947694a0539a1d51e01eda7caf800c291020e6cdd7e28ad7b14dd33ad4f85", size = 323166, upload-time = "2026-06-29T18:11:09.888Z" }, +] + +[[package]] +name = "wsproto" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/79/12135bdf8b9c9367b8701c2c19a14c913c120b882d50b014ca0d38083c2c/wsproto-1.3.2.tar.gz", hash = "sha256:b86885dcf294e15204919950f666e06ffc6c7c114ca900b060d6e16293528294", size = 50116, upload-time = "2025-11-20T18:18:01.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl", hash = "sha256:61eea322cdf56e8cc904bd3ad7573359a242ba65688716b0710a5eb12beab584", size = 24405, upload-time = "2025-11-20T18:18:00.454Z" }, +] + +[[package]] +name = "xmod" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/3b/5a0d2670bab661164e27a5c27c448ae6204458c97cb94ccf89d0c47715bc/xmod-1.10.0.tar.gz", hash = "sha256:b40b2a54d56684b01eb9627892b0c179918e8ef0bd4d7f3bac7a3fdba11cd6e6", size = 17862, upload-time = "2026-05-09T13:46:46.684Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/e9/c362d47ed2b1928d65d61888c1419fae8ef69417fba2067d2fd3da1d400b/xmod-1.10.0-py3-none-any.whl", hash = "sha256:bebf8493b7ac63097401590a329c9ed20da224de0583a522e7ccb634af122f5a", size = 4661, upload-time = "2026-05-09T13:46:45.776Z" }, +] + +[[package]] +name = "xonsh" +version = "0.24.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/82/a70fcbd8e542a55b990c61707c4f26fd42071badd3c08193931706a10323/xonsh-0.24.0.tar.gz", hash = "sha256:14cbf5bcac12bdbedd8ddb7bd2221ca869903eb039e97158ee86aa639152fff6", size = 1204432, upload-time = "2026-07-02T11:02:43.063Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/95/131aca79356daeb377287dd61c781ce635f9f59868763f9c7650002067e9/xonsh-0.24.0-py311-none-any.whl", hash = "sha256:88d32fed4fb17e656e5ff7dcc6b61f59ac4e2d155cc1d314c0889d6ac96d4bf2", size = 817351, upload-time = "2026-07-02T11:02:41Z" }, + { url = "https://files.pythonhosted.org/packages/36/f3/012e918eeadcecc07c1e06f3904d4ffea6a9a1c2dd4c24c6a37b6bf58122/xonsh-0.24.0-py312-none-any.whl", hash = "sha256:59b8e0d6d3c739aa5295f823d277888b465eb84abdcff010e04556d61f047408", size = 817374, upload-time = "2026-07-02T11:02:41.889Z" }, +] diff --git a/tests/test_iphone_capture_session_integrity.py b/tests/test_iphone_capture_session_integrity.py new file mode 100644 index 0000000..2be71b4 --- /dev/null +++ b/tests/test_iphone_capture_session_integrity.py @@ -0,0 +1,107 @@ +from __future__ import annotations + +import hashlib +import importlib.util +import json +from pathlib import Path +from types import ModuleType + +import pytest + +REPOSITORY_ROOT = Path(__file__).parents[1] +INTEGRITY_PATH = ( + REPOSITORY_ROOT / "plugins" / "xgrids-k1" / "lab" / "iphone-capture" / "session_integrity.py" +) + + +def _load_module() -> ModuleType: + spec = importlib.util.spec_from_file_location( + "iphone_capture_session_integrity", INTEGRITY_PATH + ) + assert spec is not None + assert spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +INTEGRITY = _load_module() + + +def _sha256(value: bytes) -> str: + return hashlib.sha256(value).hexdigest() + + +def _session(tmp_path: Path) -> tuple[Path, Path]: + session = tmp_path / "20260716T000000Z_test_abcd" + capture_dir = session / "captures" + capture_dir.mkdir(parents=True) + capture = capture_dir / "iphone-network.pcap" + capture_bytes = b"synthetic-private-capture" + capture.write_bytes(capture_bytes) + manifest = { + "session_id": session.name, + "status": "completed", + "artifacts": [ + { + "kind": "iphone-network-pcap-compatible", + "relative_path": "captures/iphone-network.pcap", + "size_bytes": len(capture_bytes), + "sha256": _sha256(capture_bytes), + } + ], + } + (session / "manifest.redacted.json").write_text(json.dumps(manifest), encoding="utf-8") + (session / "capture.stderr.log").write_bytes(b"") + events = session / "operator-events.private.jsonl" + events.write_text('{"sequence":1}\n', encoding="utf-8") + return session, capture + + +def test_seal_session_preserves_manifest_and_hashes_late_operator_events(tmp_path: Path) -> None: + session, _ = _session(tmp_path) + manifest = session / "manifest.redacted.json" + before = manifest.read_bytes() + + destination = INTEGRITY.seal_session(session) + inventory = json.loads(destination.read_text(encoding="utf-8")) + + assert manifest.read_bytes() == before + assert inventory["capture_manifest_preserved"] is True + assert inventory["verified_manifest_artifacts"][0]["relative_path"] == ( + "captures/iphone-network.pcap" + ) + derived = {item["kind"]: item for item in inventory["derived_session_artifacts"]} + assert derived["capture-manifest"]["sha256"] == _sha256(before) + assert derived["capture-stderr"]["size_bytes"] == 0 + assert derived["operator-events"]["relative_path"] == "operator-events.private.jsonl" + assert destination.stat().st_mode & 0o777 == 0o600 + + +def test_seal_session_rejects_capture_tampering(tmp_path: Path) -> None: + session, capture = _session(tmp_path) + capture.write_bytes(b"tampered") + + with pytest.raises(INTEGRITY.SessionIntegrityError, match="size mismatch"): + INTEGRITY.seal_session(session) + + +def test_seal_session_refuses_to_replace_inventory_without_explicit_flag( + tmp_path: Path, +) -> None: + session, _ = _session(tmp_path) + INTEGRITY.seal_session(session) + + with pytest.raises(INTEGRITY.SessionIntegrityError, match="already exists"): + INTEGRITY.seal_session(session) + + +def test_seal_session_rejects_manifest_path_escape(tmp_path: Path) -> None: + session, _ = _session(tmp_path) + manifest_path = session / "manifest.redacted.json" + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + manifest["artifacts"][0]["relative_path"] = "../outside.pcap" + manifest_path.write_text(json.dumps(manifest), encoding="utf-8") + + with pytest.raises(INTEGRITY.SessionIntegrityError, match="stay inside"): + INTEGRITY.seal_session(session)