chore: initialize K1 connector pre-production scaffold
This commit is contained in:
commit
4c401bc59b
|
|
@ -0,0 +1,12 @@
|
||||||
|
* text=auto eol=lf
|
||||||
|
*.md text diff=markdown
|
||||||
|
*.json text
|
||||||
|
*.jsonl text
|
||||||
|
*.toml text
|
||||||
|
*.pcap binary
|
||||||
|
*.pcapng binary
|
||||||
|
*.snoop binary
|
||||||
|
*.xbin binary
|
||||||
|
*.las binary
|
||||||
|
*.lcc binary
|
||||||
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
.DS_Store
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Project-local Python environment and caches
|
||||||
|
.venv/
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
.pytest_cache/
|
||||||
|
.mypy_cache/
|
||||||
|
.ruff_cache/
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
*.egg-info/
|
||||||
|
|
||||||
|
# Local configuration and secrets
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
config/local.toml
|
||||||
|
private/
|
||||||
|
|
||||||
|
# Real laboratory captures and decoded artifacts are sensitive and large.
|
||||||
|
captures/
|
||||||
|
sessions/
|
||||||
|
artifacts/raw/
|
||||||
|
artifacts/decoded/
|
||||||
|
*.pcap
|
||||||
|
*.pcapng
|
||||||
|
*.snoop
|
||||||
|
*.xbin
|
||||||
|
*.las
|
||||||
|
*.lcc
|
||||||
|
*.ply
|
||||||
|
*.pcd
|
||||||
|
*.npz
|
||||||
|
*.raw
|
||||||
|
|
||||||
|
# Small synthetic/redacted fixtures under tests/fixtures are allowed.
|
||||||
|
!tests/fixtures/**/*.pcap
|
||||||
|
!tests/fixtures/**/*.pcapng
|
||||||
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
3.12
|
||||||
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
# Repository operating rules
|
||||||
|
|
||||||
|
This repository investigates an owner-controlled XGRIDS/LixelKity K1 as a
|
||||||
|
black-box sensor. Preserve the device, the Mac, the working LAN, and raw
|
||||||
|
evidence.
|
||||||
|
|
||||||
|
## Non-negotiable safety boundaries
|
||||||
|
|
||||||
|
- Default to passive or read-only operations.
|
||||||
|
- Do not send BLE writes until the exact service, characteristic, framing,
|
||||||
|
payload semantics, rollback, and expected state transition are documented.
|
||||||
|
- BLE notification subscription may perform the standard temporary CCCD write;
|
||||||
|
disclose it explicitly and do not conflate it with provisioning writes.
|
||||||
|
- Do not fuzz, brute-force, upload firmware, delete device files, guess SSH/ADB
|
||||||
|
credentials, or probe unrelated LAN devices.
|
||||||
|
- Network probes must target the confirmed K1 IP. A full home-subnet scan is
|
||||||
|
not a default operation.
|
||||||
|
- Never place a Wi-Fi password in CLI arguments, logs, manifests, source, test
|
||||||
|
fixtures, or Git history.
|
||||||
|
- Do not install Python packages globally. Use the repository-local `.venv`
|
||||||
|
managed by `uv`.
|
||||||
|
- Do not install or alter Homebrew/system components unless the user explicitly
|
||||||
|
authorizes that concrete change.
|
||||||
|
|
||||||
|
## Evidence rules
|
||||||
|
|
||||||
|
- Reference inputs under `docs/reference/` are immutable; write corrections in
|
||||||
|
audits or ADRs.
|
||||||
|
- Every real experiment gets a session ID, UTC and monotonic timestamps, a
|
||||||
|
redacted manifest, operator notes, and SHA-256 hashes for raw artifacts.
|
||||||
|
- Real PCAP, K1 projects, logs, images, point clouds, credentials, serials, and
|
||||||
|
router client lists stay out of normal Git. Git LFS solves size, not secrecy.
|
||||||
|
- Synthetic or explicitly redacted fixtures may be committed under
|
||||||
|
`tests/fixtures/`.
|
||||||
|
|
||||||
|
## Implementation order
|
||||||
|
|
||||||
|
Follow the gates in `docs/01_IMPLEMENTATION_PLAN.md`. Do not build heavy
|
||||||
|
decoders before BLE/Wi-Fi/data-session evidence exists.
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
# NDC XGRIDS K1 Connector
|
||||||
|
|
||||||
|
Pre-production research project for connecting an owner-controlled
|
||||||
|
XGRIDS/LixelKity K1 to a Mac without LixelGO, firmware changes, device opening,
|
||||||
|
or speculative writes.
|
||||||
|
|
||||||
|
Current status: repository scaffold and implementation plan. No command in the
|
||||||
|
repository currently writes to the K1, changes the router, or installs system
|
||||||
|
packages.
|
||||||
|
|
||||||
|
## Available stand
|
||||||
|
|
||||||
|
- one XGRIDS/LixelKity K1;
|
||||||
|
- one Apple Silicon MacBook running macOS;
|
||||||
|
- one ordinary TP-Link Deco/mesh network used by other devices;
|
||||||
|
- no LixelGO, phone, Linux host, dedicated AP, OpenWrt, or vendor SDK.
|
||||||
|
|
||||||
|
The ordinary router is sufficient for the first gates. We first observe the
|
||||||
|
existing LAN without changing it. A Guest/IoT SSID is optional and may be
|
||||||
|
counterproductive if Deco isolates clients; Mac and K1 must ultimately be able
|
||||||
|
to reach each other.
|
||||||
|
|
||||||
|
## What is actually being proved
|
||||||
|
|
||||||
|
The project has three independent gates:
|
||||||
|
|
||||||
|
1. K1 is operational and can record a project autonomously.
|
||||||
|
2. Mac can discover and inspect the K1 BLE/GATT surface safely.
|
||||||
|
3. Without LixelGO, K1 can be associated with Wi-Fi and a proprietary data
|
||||||
|
session can be opened.
|
||||||
|
|
||||||
|
Only after gate 3 do point-cloud, pose, status, and camera stream decoders become
|
||||||
|
meaningful implementation work. Local `map.las` and `poses.csv` prove internal
|
||||||
|
capabilities, not an externally accessible network format.
|
||||||
|
|
||||||
|
## Local environment
|
||||||
|
|
||||||
|
The project uses Python 3.12 in a repository-local `.venv` managed by `uv`.
|
||||||
|
This does not install Python packages globally and does not modify neighboring
|
||||||
|
repositories.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /Users/dcconstructions/Downloads/mnt/NODEDC/NDC_xgrids-k1-connector
|
||||||
|
uv sync --group dev
|
||||||
|
uv run k1link doctor
|
||||||
|
uv run pytest
|
||||||
|
```
|
||||||
|
|
||||||
|
`doctor` is intentionally non-invasive. It checks the local Python environment
|
||||||
|
and reports external tools; it does not request Bluetooth permission, scan the
|
||||||
|
LAN, touch the K1, alter Homebrew, or change capture permissions.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- [Technical audit](docs/00_TECHNICAL_AUDIT.md)
|
||||||
|
- [Implementation gates](docs/01_IMPLEMENTATION_PLAN.md)
|
||||||
|
- [First lab runbook](docs/02_FIRST_LAB_RUNBOOK.md)
|
||||||
|
- [Artifact and secret policy](docs/03_ARTIFACT_POLICY.md)
|
||||||
|
- [Session manifest schema](schemas/session-manifest.schema.json)
|
||||||
|
- [Reference input provenance](docs/reference/README.md)
|
||||||
|
|
||||||
|
The two supplied source documents are retained unchanged under
|
||||||
|
`docs/reference/`. Corrections and decisions are recorded separately so their
|
||||||
|
provenance remains clear.
|
||||||
|
|
||||||
|
## Safety boundary
|
||||||
|
|
||||||
|
Allowed initial work is passive discovery, standard device-information reads,
|
||||||
|
controlled notification listening, autonomous button operation, targeted
|
||||||
|
capture of traffic to or from the confirmed K1 address, and offline analysis of
|
||||||
|
owned artifacts.
|
||||||
|
|
||||||
|
BLE writes, provisioning, application-session packets, active service probes,
|
||||||
|
and router configuration changes require evidence and an explicit reviewed
|
||||||
|
step. Random writes, fuzzing, brute force, firmware operations, destructive file
|
||||||
|
access, and credential guessing are out of scope.
|
||||||
|
|
||||||
|
Real captures, projects, router metadata, serials, credentials, maps, images,
|
||||||
|
and logs are ignored by normal Git. Redacted manifests and SHA-256 inventories
|
||||||
|
are committed; encrypted artifact storage will be selected only when real data
|
||||||
|
exists.
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
# Laboratory artifacts
|
||||||
|
|
||||||
|
Only redacted manifests and SHA-256 inventories belong in ordinary Git.
|
||||||
|
|
||||||
|
Real captures, K1 project copies, logs, point clouds, images, router client
|
||||||
|
lists, and decoded output must stay in ignored paths such as `artifacts/raw/`
|
||||||
|
or `sessions/`. If long-term versioned storage becomes necessary, choose and
|
||||||
|
document an encrypted artifact store. Git LFS alone is not encryption.
|
||||||
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
# Technical audit
|
||||||
|
|
||||||
|
Status: pre-production planning, 2026-07-15.
|
||||||
|
|
||||||
|
## Executive finding
|
||||||
|
|
||||||
|
The project is feasible as a disciplined black-box investigation, but the
|
||||||
|
existence of a complete K1-to-Mac realtime link is not yet established. The
|
||||||
|
first hard problem is not decoding points or images. It is bootstrapping the K1
|
||||||
|
from BLE availability into Wi-Fi association and then opening the proprietary
|
||||||
|
application data session without LixelGO.
|
||||||
|
|
||||||
|
The supplied Bible is useful as an OSINT dossier. It is not an executable plan
|
||||||
|
for the actual stand because many experiments assume a phone and LixelGO. The
|
||||||
|
implementation must proceed through explicit gates and stop before speculative
|
||||||
|
writes.
|
||||||
|
|
||||||
|
## Evidence classes
|
||||||
|
|
||||||
|
### Version-scoped facts to verify on the physical unit
|
||||||
|
|
||||||
|
- K1 has LiDAR, panoramic cameras, onboard compute, BLE and Wi-Fi.
|
||||||
|
- Official workflows use BLE before Wi-Fi network/data connection.
|
||||||
|
- Autonomous scan start/stop by the physical button is documented.
|
||||||
|
- A scan project may contain local `map.las`, `poses.csv`, `project.json`, logs,
|
||||||
|
preview/model and raw sensor artifacts depending on firmware and settings.
|
||||||
|
- macOS exposes a CoreBluetooth device UUID rather than a portable hardware MAC.
|
||||||
|
- A normal switched/mesh LAN does not mirror arbitrary client-to-client unicast
|
||||||
|
to the Mac, but the Mac can capture its own future K1 session completely.
|
||||||
|
|
||||||
|
Each of these remains scoped to the actual firmware/hardware state observed in
|
||||||
|
the lab. The physical unit is authoritative.
|
||||||
|
|
||||||
|
### Strong hypotheses
|
||||||
|
|
||||||
|
- BLE is the bootstrap/control plane and Wi-Fi is the likely high-rate data
|
||||||
|
plane.
|
||||||
|
- The user-provided SSID and PSK are transported inside a vendor-defined GATT
|
||||||
|
protocol.
|
||||||
|
- The application may need a second token, certificate, handshake, or stream
|
||||||
|
subscription after ordinary Wi-Fi association.
|
||||||
|
- The externally exposed point cloud is more likely a processed/downsampled
|
||||||
|
preview than raw LiDAR packets.
|
||||||
|
|
||||||
|
XGRIDS provides stronger product evidence for an external point-cloud path than
|
||||||
|
the local LAS alone: current LixelStudio materials describe K1 remote control
|
||||||
|
and realtime point-cloud streaming over USB/Wi-Fi. LixelStudio is Windows-only
|
||||||
|
and this does not disclose or guarantee access to the protocol from macOS. It
|
||||||
|
does, however, justify keeping realtime point cloud as the primary stream target.
|
||||||
|
No comparable official evidence proves an exportable raw panorama/camera stream.
|
||||||
|
|
||||||
|
### Unsupported until measured
|
||||||
|
|
||||||
|
- K1 remembers an existing Wi-Fi profile.
|
||||||
|
- K1 exposes its own access point.
|
||||||
|
- Provisioning fields are plain UTF-8, JSON, TLV, CBOR, or protobuf.
|
||||||
|
- A writable GATT characteristic can be used without bonding or an app token.
|
||||||
|
- Local `map.las` layout resembles the network stream.
|
||||||
|
- Live pose or camera frames leave the device.
|
||||||
|
- A point stream contains simple float32 XYZ tuples.
|
||||||
|
- Network payloads are unencrypted.
|
||||||
|
|
||||||
|
## Major corrections to the source plan
|
||||||
|
|
||||||
|
1. Experiments requiring LixelGO are removed from the active critical path.
|
||||||
|
There will be no app-session capture, provisioning diff, app-start comparison,
|
||||||
|
or temporary broker mode.
|
||||||
|
2. The first go/no-go is device activation and autonomous operation. An
|
||||||
|
unactivated unit may be a hard blocker before BLE provisioning is relevant.
|
||||||
|
3. Provisioning is not implemented merely because a characteristic is writable.
|
||||||
|
We need target UUID, framing, field encoding, ordering, integrity/auth fields,
|
||||||
|
connect/commit semantics, status response, and rollback.
|
||||||
|
4. Wi-Fi association and application data session are modeled as separate
|
||||||
|
states. Obtaining an IP is not success for the realtime connector.
|
||||||
|
5. `tcpdump` does not create a stream. A useful capture requires either K1 to
|
||||||
|
initiate traffic or Mac to become the authenticated/subscribed endpoint.
|
||||||
|
6. Full port/version/UDP scans are active operations, not passive safe defaults.
|
||||||
|
Discovery begins with power on/off diffs, router client list, ARP and
|
||||||
|
broadcast/multicast observations, then targets only the confirmed K1 IP.
|
||||||
|
7. Heavy packages are deferred. OpenCV, Open3D, pandas, Scapy, PyShark and NumPy
|
||||||
|
are not prerequisites for proving bootstrap connectivity.
|
||||||
|
8. Wi-Fi passwords must never be CLI arguments. Future provisioning will use a
|
||||||
|
hidden interactive prompt or macOS Keychain and must redact payloads.
|
||||||
|
|
||||||
|
## Router audit
|
||||||
|
|
||||||
|
The existing TP-Link Deco/mesh router is adequate for the initial work. No
|
||||||
|
router setting needs to change before the first observations.
|
||||||
|
|
||||||
|
We need only DHCP, a shared reachable network, and ideally a way to view the
|
||||||
|
client list. Band steering and a single combined SSID are acceptable. A separate
|
||||||
|
Guest/IoT SSID is optional; on some Deco modes it isolates clients, which would
|
||||||
|
prevent Mac-to-K1 traffic. If a separate SSID is later used, peer-to-peer reachability
|
||||||
|
must be tested before provisioning K1 onto it.
|
||||||
|
|
||||||
|
The main-LAN fallback is acceptable for a narrow, target-filtered experiment.
|
||||||
|
It must not include a broad scan or unfiltered capture of household traffic.
|
||||||
|
|
||||||
|
## Environment audit
|
||||||
|
|
||||||
|
Observed host baseline:
|
||||||
|
|
||||||
|
- Apple Silicon (`arm64`);
|
||||||
|
- macOS 26.5.1;
|
||||||
|
- `uv` and Homebrew present;
|
||||||
|
- system Python is 3.13.5, while the project requires 3.12;
|
||||||
|
- `tcpdump` and `ffmpeg` present;
|
||||||
|
- `tshark` and `nmap` not currently found.
|
||||||
|
- the current default route is a VPN tunnel (`utun`), while Wi-Fi is `en0`.
|
||||||
|
|
||||||
|
The project therefore pins Python 3.12 via `.python-version`; `uv` creates a
|
||||||
|
local `.venv`. Nothing in setup installs global Python packages. Missing external
|
||||||
|
network-analysis tools are reported by `k1link doctor` and are not installed
|
||||||
|
until their gate requires them and the change is explicitly accepted.
|
||||||
|
Future network commands must determine the route for the confirmed K1 IP;
|
||||||
|
neither the default route nor `en0` may be hardcoded.
|
||||||
|
|
||||||
|
## Primary blockers
|
||||||
|
|
||||||
|
| Gate | Blocking condition | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| Device | fault, activation lock, no autonomous project | Stop before networking |
|
||||||
|
| BLE | no advertisement or only inaccessible/authenticated surface | Investigate state/permissions before writes |
|
||||||
|
| Wi-Fi | no remembered network/AP and opaque custom GATT | Provisioning research is the hard block |
|
||||||
|
| Data session | IP exists but service requires unknown token/handshake | Association succeeded; connector has not |
|
||||||
|
| Streams | session opens but no useful external stream | Realtime goal may be unsupported |
|
||||||
|
| Camera | point cloud works but frames never appear | Camera branch may be unsupported; not an MVP failure |
|
||||||
|
|
||||||
|
## Implementation consequence
|
||||||
|
|
||||||
|
The correct order is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
device baseline
|
||||||
|
-> autonomous project + USB metadata
|
||||||
|
-> passive BLE surface
|
||||||
|
-> remembered Wi-Fi / device AP check
|
||||||
|
-> evidence-led provisioning gate
|
||||||
|
-> application-session gate
|
||||||
|
-> targeted capture
|
||||||
|
-> flow classification
|
||||||
|
-> point cloud / pose / status / optional camera decoders
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verified primary references
|
||||||
|
|
||||||
|
- [XGRIDS K1 firmware and release notes](https://www.xgrids.com/intl/support/download?page=K1)
|
||||||
|
- [XGRIDS LixelStudio](https://www.xgrids.com/intl/lixelstudio)
|
||||||
|
- [XGRIDS LixelStudio releases](https://www.xgrids.com/intl/support/download?page=LixelStudio)
|
||||||
|
- [XGRIDS device activation and connection](https://docs.xgrids.com/en-us/02-lingguang-k/01-lingguang-k1/v2.4.0/03-device-activation-and-connection.html)
|
||||||
|
- [Apple packet-trace guidance](https://developer.apple.com/documentation/network/recording-a-packet-trace)
|
||||||
|
- [Bleak macOS backend](https://bleak.readthedocs.io/en/latest/backends/macos.html)
|
||||||
|
- [TP-Link Deco Guest Network behavior](https://www.tp-link.com/ca/support/faq/1460/)
|
||||||
|
- [TP-Link Deco IoT Network](https://www.tp-link.com/us/support/faq/4420/)
|
||||||
|
- [uv project environments](https://docs.astral.sh/uv/guides/projects/)
|
||||||
|
|
||||||
|
Reference-document statements not independently repeated here remain inputs or
|
||||||
|
hypotheses, not verified conclusions of this audit.
|
||||||
|
|
@ -0,0 +1,188 @@
|
||||||
|
# Implementation plan
|
||||||
|
|
||||||
|
This plan supersedes the app-dependent experiment order in the reference Bible.
|
||||||
|
Each gate produces evidence and an explicit GO, PAUSE or BLOCKED result.
|
||||||
|
|
||||||
|
## Stage 0 — repository and host baseline
|
||||||
|
|
||||||
|
Deliverables:
|
||||||
|
|
||||||
|
- local Python 3.12 `.venv` and lock-file;
|
||||||
|
- `k1link doctor` with no side effects;
|
||||||
|
- immutable reference documents and checksums;
|
||||||
|
- safety policy, artifact policy, session manifest schema;
|
||||||
|
- no Homebrew changes and no K1 interaction.
|
||||||
|
|
||||||
|
Acceptance:
|
||||||
|
|
||||||
|
- `uv sync --group dev` succeeds;
|
||||||
|
- `uv run k1link doctor` identifies a local `.venv`;
|
||||||
|
- tests, Ruff and mypy pass;
|
||||||
|
- repository contains no secrets or real captures.
|
||||||
|
|
||||||
|
## Gate 1 — physical and USB baseline
|
||||||
|
|
||||||
|
Code needed: none initially.
|
||||||
|
|
||||||
|
Actions:
|
||||||
|
|
||||||
|
1. Record masked device identity, LED boot sequence, battery and visible state.
|
||||||
|
2. Determine whether the unit is activated; solid-yellow or an activation prompt
|
||||||
|
is treated as a distinct blocker.
|
||||||
|
3. Run one short autonomous button scan: start, keep still for at least 20 seconds,
|
||||||
|
move through a simple scene, stop.
|
||||||
|
4. Enter USB mode using the documented physical shortcut if available.
|
||||||
|
5. Copy a project tree and selected metadata/logs into an ignored session path.
|
||||||
|
6. Hash every copied file and create a redacted manifest.
|
||||||
|
|
||||||
|
GO: the unit boots normally and records a usable autonomous project.
|
||||||
|
|
||||||
|
PAUSE: USB access is unavailable but the scan succeeds; continue BLE while
|
||||||
|
documenting the missing ground truth.
|
||||||
|
|
||||||
|
STOP: serious fault, overheating, activation lock, or destructive/ambiguous
|
||||||
|
device state.
|
||||||
|
|
||||||
|
## Stage 1 — BLE read-only toolkit
|
||||||
|
|
||||||
|
Minimal dependencies: `bleak` plus the existing CLI stack. Add them only in the
|
||||||
|
repository-local environment.
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
|
||||||
|
```text
|
||||||
|
k1link ble scan
|
||||||
|
k1link ble gatt-dump
|
||||||
|
k1link ble listen
|
||||||
|
```
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
|
||||||
|
- preserve advertisement snapshots, manufacturer/service data and RSSI;
|
||||||
|
- identify by observed name + macOS UUID + advertisement fingerprint;
|
||||||
|
- inspect standby, scanning and USB states separately;
|
||||||
|
- start with standard Device Information/Battery services;
|
||||||
|
- custom characteristic reads are opt-in because proprietary reads may have
|
||||||
|
side effects;
|
||||||
|
- notifications are subscribed one characteristic at a time and logged to
|
||||||
|
timestamped JSONL;
|
||||||
|
- no arbitrary characteristic or provisioning writes in this stage. Enabling a
|
||||||
|
notification normally updates the standard CCCD descriptor; this temporary,
|
||||||
|
reversible protocol write is disclosed and handled separately from device
|
||||||
|
configuration.
|
||||||
|
|
||||||
|
GO: reproducible K1 advertisement and useful GATT map.
|
||||||
|
|
||||||
|
PAUSE: permission or pairing/authentication is required.
|
||||||
|
|
||||||
|
## Gate 2 — existing network behavior
|
||||||
|
|
||||||
|
Do not change Deco settings first.
|
||||||
|
|
||||||
|
Actions:
|
||||||
|
|
||||||
|
1. Save the Mac interface and route table.
|
||||||
|
2. Observe router client list and Mac ARP/neighbor state with K1 off.
|
||||||
|
3. Repeat after K1 boot, during standby, scanning and after one power cycle.
|
||||||
|
4. Check for new Wi-Fi SSIDs exposed by K1 without attempting to join unknown
|
||||||
|
networks automatically.
|
||||||
|
5. Confirm any candidate by disappearance/reappearance with K1 power state.
|
||||||
|
|
||||||
|
GO-A: K1 joins a remembered LAN. Provisioning leaves the immediate critical
|
||||||
|
path and work moves to the application-session gate.
|
||||||
|
|
||||||
|
GO-B: K1 exposes its own AP. Document it as a separate topology and determine
|
||||||
|
whether Mac can join without modifying router configuration.
|
||||||
|
|
||||||
|
BLOCKED: neither association nor AP exists; provisioning becomes Stage 2.
|
||||||
|
|
||||||
|
## Stage 2 — no-app provisioning research
|
||||||
|
|
||||||
|
This stage answers whether SSID/PSK can be sent safely from Mac. A generic BLE
|
||||||
|
transport does not define the vendor payload.
|
||||||
|
|
||||||
|
Evidence sources allowed before a write:
|
||||||
|
|
||||||
|
- GATT UUIDs, properties and descriptors;
|
||||||
|
- standard service identification;
|
||||||
|
- read/notify state transitions correlated with network state;
|
||||||
|
- owned K1 project logs and metadata;
|
||||||
|
- public official firmware/documentation static evidence if separately reviewed;
|
||||||
|
- deterministic structure inferred without sending device mutations.
|
||||||
|
|
||||||
|
Before enabling any write profile, document:
|
||||||
|
|
||||||
|
- exact service/characteristic and required security mode;
|
||||||
|
- packet framing, encoding, ordering and length rules;
|
||||||
|
- SSID/PSK redaction and secure input path;
|
||||||
|
- checksum, nonce, token, certificate or signature fields;
|
||||||
|
- commit/connect command and status response;
|
||||||
|
- timeout, failure state, power-cycle behavior and rollback;
|
||||||
|
- one explicit confirmation flag in addition to a named reviewed profile.
|
||||||
|
|
||||||
|
GO: a deterministic provisioning profile exists with an explained expected
|
||||||
|
state transition and safe rollback.
|
||||||
|
|
||||||
|
HARD BLOCK: K1 knows no network, exposes only opaque vendor GATT, and no evidence
|
||||||
|
source reveals the protocol. Random writes are not an alternative.
|
||||||
|
|
||||||
|
## Stage 3 — application session and targeted capture
|
||||||
|
|
||||||
|
Prerequisite: confirmed K1 IP or direct AP topology.
|
||||||
|
|
||||||
|
Implementation order:
|
||||||
|
|
||||||
|
1. Passive ARP/mDNS/SSDP and K1-filtered `tcpdump` baseline.
|
||||||
|
2. Small targeted TCP connect check against the confirmed IP.
|
||||||
|
3. Service detection only where the prior step supplies evidence.
|
||||||
|
4. Parse K1 logs for daemon names, ports and protocol strings.
|
||||||
|
5. Add protocol-specific connection attempts only for demonstrated endpoints.
|
||||||
|
6. Model `WIFI_ASSOCIATED_BUT_NO_DATA_SESSION` explicitly.
|
||||||
|
|
||||||
|
`k1link net capture` must show the exact interface/filter, request privilege in
|
||||||
|
the foreground, handle Ctrl+C, and never configure persistent `sudo` or ChmodBPF.
|
||||||
|
|
||||||
|
GO: Mac becomes a K1 endpoint or receives a reproducible scan-correlated flow.
|
||||||
|
|
||||||
|
BLOCKED: application session requires an unavailable mutual-auth token,
|
||||||
|
certificate or undocumented handshake.
|
||||||
|
|
||||||
|
## Stage 4 — session artifacts and flow analysis
|
||||||
|
|
||||||
|
Implement only after real traffic exists:
|
||||||
|
|
||||||
|
- versioned session manifest;
|
||||||
|
- UTC and monotonic timeline events;
|
||||||
|
- SHA-256 inventory;
|
||||||
|
- TCP reassembly and UDP flow grouping;
|
||||||
|
- packet/byte rates, length histograms, entropy and first-byte fingerprints;
|
||||||
|
- idle versus scanning correlation;
|
||||||
|
- negative-control flows to reduce false positives.
|
||||||
|
|
||||||
|
GO: repeatable flows can be separated and ranked without losing raw evidence.
|
||||||
|
|
||||||
|
## Stage 5 — decoders by evidence priority
|
||||||
|
|
||||||
|
Priority:
|
||||||
|
|
||||||
|
1. device/scan status;
|
||||||
|
2. point-cloud preview;
|
||||||
|
3. pose/trajectory;
|
||||||
|
4. camera/panorama/video.
|
||||||
|
|
||||||
|
Add dependencies per decoder extra, not to the base environment. Point-cloud
|
||||||
|
and video signature probes operate on reassembled flows, not isolated packet
|
||||||
|
payloads. A candidate becomes a decoder only after a controlled physical test
|
||||||
|
confirms it.
|
||||||
|
|
||||||
|
MVP GO: K1-to-Mac scan-correlated data can be captured reliably and at least one
|
||||||
|
useful stream is decoded or structurally identified.
|
||||||
|
|
||||||
|
## Deferred work
|
||||||
|
|
||||||
|
- ROS2/MCAP bridge;
|
||||||
|
- onboard computer and drone integration;
|
||||||
|
- automated scan-button electronics;
|
||||||
|
- OpenWrt/monitor-mode infrastructure;
|
||||||
|
- firmware or internal-Linux analysis;
|
||||||
|
- camera branch if no external frame stream is evidenced.
|
||||||
|
|
@ -0,0 +1,134 @@
|
||||||
|
# First lab runbook
|
||||||
|
|
||||||
|
Purpose: gather decisive evidence using only K1, the current MacBook and the
|
||||||
|
existing TP-Link Deco/mesh network. This runbook contains no BLE writes, router
|
||||||
|
changes, broad LAN scans or system package installation.
|
||||||
|
|
||||||
|
## Before powering K1
|
||||||
|
|
||||||
|
- Place K1 on a stable, ventilated surface.
|
||||||
|
- Prepare its normal battery/charger and a known data-capable USB cable.
|
||||||
|
- Ensure enough Mac disk space for one copied project and short captures.
|
||||||
|
- Do not expose the Wi-Fi password in notes, terminal arguments or screenshots.
|
||||||
|
- If the router model becomes visible in the Deco UI, record only the model and
|
||||||
|
operating mode; do not change settings yet.
|
||||||
|
|
||||||
|
Create an ignored session directory outside ordinary Git, for example:
|
||||||
|
|
||||||
|
```text
|
||||||
|
sessions/20260715T120000Z_gate1_baseline/
|
||||||
|
```
|
||||||
|
|
||||||
|
The operator notes should use masked serials and relative timestamps.
|
||||||
|
|
||||||
|
## Experiment A — boot and activation state
|
||||||
|
|
||||||
|
1. Start a phone/Mac video focused on the LED if convenient.
|
||||||
|
2. Power K1 on normally.
|
||||||
|
3. Record LED colors and durations until stable.
|
||||||
|
4. Record whether the final state is normal standby, activation warning or fault.
|
||||||
|
5. Stop immediately on serious fault, unusual heat, smell, battery problem or
|
||||||
|
undocumented state.
|
||||||
|
|
||||||
|
Expected output: `notes.md`, optional local video, and a redacted manifest.
|
||||||
|
|
||||||
|
## Experiment B — autonomous project
|
||||||
|
|
||||||
|
1. From normal standby, start scanning with the documented double-click.
|
||||||
|
2. Keep K1 completely still for at least 20 seconds.
|
||||||
|
3. Move slowly through a simple scene for about one minute.
|
||||||
|
4. Stop scanning with the documented double-click and wait for stable standby.
|
||||||
|
5. Enter USB mode using the physical shortcut only if its state is understood.
|
||||||
|
6. Copy the project tree, `project.json`, `poses.csv`, `map.las` metadata and logs
|
||||||
|
when present. Raw project data remains ignored and local.
|
||||||
|
7. Generate a SHA-256 inventory; commit only a redacted report later.
|
||||||
|
|
||||||
|
Decision:
|
||||||
|
|
||||||
|
- GO if a valid autonomous project exists.
|
||||||
|
- PAUSE USB analysis if recording works but USB is unavailable.
|
||||||
|
- STOP on activation lock or device fault.
|
||||||
|
|
||||||
|
## Experiment C — passive BLE surface
|
||||||
|
|
||||||
|
Prerequisite: Stage 1 BLE commands implemented and reviewed.
|
||||||
|
|
||||||
|
1. Run a 30-second scan in standby.
|
||||||
|
2. Save advertisement and manufacturer/service data.
|
||||||
|
3. Connect for service discovery without writes.
|
||||||
|
4. Read standard device-information/battery characteristics first.
|
||||||
|
5. Repeat advertisement/GATT observation during scanning and USB mode.
|
||||||
|
6. Subscribe to an individually selected notify/indicate characteristic only
|
||||||
|
after its properties are recorded.
|
||||||
|
|
||||||
|
Decision:
|
||||||
|
|
||||||
|
- GO if K1 has a reproducible BLE identity and observable surface.
|
||||||
|
- PAUSE if macOS asks for pairing/authentication that cannot be completed.
|
||||||
|
|
||||||
|
## Experiment D — current network, no router changes
|
||||||
|
|
||||||
|
1. Capture the Mac route/interface and its current neighbor table with K1 off.
|
||||||
|
2. Note the Deco client list if accessible.
|
||||||
|
3. Boot K1 and repeat in standby.
|
||||||
|
4. Repeat during a button scan.
|
||||||
|
5. Power-cycle K1 and confirm whether any candidate disappears/reappears.
|
||||||
|
6. Check visible SSIDs for a K1-associated AP.
|
||||||
|
|
||||||
|
Do not infer identity from one unknown LAN client. Require power-state
|
||||||
|
correlation or an independent device fingerprint.
|
||||||
|
|
||||||
|
Decision:
|
||||||
|
|
||||||
|
- remembered-LAN GO: confirmed K1 address appears;
|
||||||
|
- device-AP GO: a repeatable K1 SSID appears;
|
||||||
|
- provisioning BLOCKED: neither appears and no known GATT provisioning profile
|
||||||
|
exists.
|
||||||
|
|
||||||
|
## About Guest/IoT networks on Deco
|
||||||
|
|
||||||
|
Do not create one during this first run. Depending on Deco model and Router/AP
|
||||||
|
mode, Guest/IoT networks may isolate clients from the main LAN or from each
|
||||||
|
other. A later dedicated SSID is useful only if both K1 and Mac can communicate
|
||||||
|
and its password can be entered through a known provisioning profile.
|
||||||
|
|
||||||
|
## Minimum lab report
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# K1 Lab Report 001
|
||||||
|
|
||||||
|
## Host
|
||||||
|
macOS:
|
||||||
|
Mac model/architecture:
|
||||||
|
Router model/mode (if known):
|
||||||
|
|
||||||
|
## Device
|
||||||
|
Model:
|
||||||
|
Serial suffix only:
|
||||||
|
Activation/LED state:
|
||||||
|
Firmware evidence:
|
||||||
|
|
||||||
|
## Autonomous scan
|
||||||
|
Start/stop behavior:
|
||||||
|
Project created:
|
||||||
|
USB accessible:
|
||||||
|
Files observed:
|
||||||
|
|
||||||
|
## BLE
|
||||||
|
Advertised name:
|
||||||
|
macOS UUID: [redacted]
|
||||||
|
Service fingerprint:
|
||||||
|
Pairing/auth prompts:
|
||||||
|
|
||||||
|
## Network
|
||||||
|
Remembered LAN candidate:
|
||||||
|
Device AP candidate:
|
||||||
|
Confirmed K1 IP: [redacted]
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
Current gate:
|
||||||
|
GO / PAUSE / BLOCKED:
|
||||||
|
Evidence:
|
||||||
|
Next smallest experiment:
|
||||||
|
```
|
||||||
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
# Artifact and secret policy
|
||||||
|
|
||||||
|
The experiment should be reproducible from Git without placing household
|
||||||
|
credentials, spatial maps or device secrets into normal Git history.
|
||||||
|
|
||||||
|
## Sensitive by default
|
||||||
|
|
||||||
|
- Wi-Fi SSID and PSK;
|
||||||
|
- full K1 serial, BLE UUID, MAC/IP addresses and router client lists;
|
||||||
|
- provisioning payloads, tokens, nonces, certificates and session keys;
|
||||||
|
- PCAP/PCAPNG, Bluetooth logs and extracted flows;
|
||||||
|
- K1 logs, `project.json`, `.xbin`, LAS/point clouds and trajectories;
|
||||||
|
- camera frames, previews, panoramas and mapped interiors;
|
||||||
|
- command lines or shell history containing a credential.
|
||||||
|
|
||||||
|
Private Git is still durable replicated storage. Git LFS changes storage and
|
||||||
|
size behavior; it does not provide confidentiality.
|
||||||
|
|
||||||
|
## Commit to normal Git
|
||||||
|
|
||||||
|
- source, tests and lock-file;
|
||||||
|
- immutable reference documents and their hashes;
|
||||||
|
- schemas, runbooks, ADRs and redacted reports;
|
||||||
|
- synthetic or explicitly redacted fixtures;
|
||||||
|
- redacted manifests and SHA-256 inventories;
|
||||||
|
- commands needed to reproduce analysis from a locally supplied artifact.
|
||||||
|
|
||||||
|
## Keep outside normal Git
|
||||||
|
|
||||||
|
- `.venv`, local config and Keychain references;
|
||||||
|
- actual credentials;
|
||||||
|
- real captures and decoded output;
|
||||||
|
- complete K1 project copies;
|
||||||
|
- unredacted device and router metadata.
|
||||||
|
|
||||||
|
## Session integrity
|
||||||
|
|
||||||
|
Every real session should record:
|
||||||
|
|
||||||
|
- schema version and unique session ID;
|
||||||
|
- tool Git commit;
|
||||||
|
- UTC start/end and Mac monotonic start/end;
|
||||||
|
- masked device/host/network identities;
|
||||||
|
- physical state timeline;
|
||||||
|
- exact capture filter and interface;
|
||||||
|
- artifact path, size and SHA-256;
|
||||||
|
- redaction status and storage location classification;
|
||||||
|
- operator notes and final GO/PAUSE/BLOCKED decision.
|
||||||
|
|
||||||
|
Raw artifacts are never silently deleted by code. That does not imply infinite
|
||||||
|
retention: capacity, encryption, backup and deliberate deletion policy are an
|
||||||
|
operator decision outside the capture command.
|
||||||
|
|
||||||
|
## Future provisioning secret input
|
||||||
|
|
||||||
|
The Wi-Fi password must not be accepted as a normal CLI argument. A reviewed
|
||||||
|
provisioner should use a hidden prompt, stdin/file descriptor with strict
|
||||||
|
permissions, or macOS Keychain. Logs and manifests record only that a credential
|
||||||
|
was supplied, never its value or unredacted payload bytes.
|
||||||
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
# ADR 0001: Treat LixelGO as unavailable
|
||||||
|
|
||||||
|
Status: accepted.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
The actual stand consists of one K1, one MacBook and one ordinary TP-Link
|
||||||
|
Deco/mesh router. There is no phone, LixelGO reference session, Linux host,
|
||||||
|
dedicated capture router or public SDK.
|
||||||
|
|
||||||
|
The supplied design documents include several app-dependent experiments and
|
||||||
|
assume that provisioning payloads can be recovered by comparing LixelGO BLE
|
||||||
|
writes. That evidence source does not exist.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
- LixelGO is not a temporary dependency, broker or test oracle.
|
||||||
|
- App-dependent experiments remain only as historical reference.
|
||||||
|
- The active critical path is physical baseline, passive BLE, remembered Wi-Fi
|
||||||
|
or device AP, evidence-led provisioning, application-session discovery, then
|
||||||
|
stream capture and decoding.
|
||||||
|
- A fully opaque provisioning protocol with no evidence source is reported as a
|
||||||
|
hard black-box blocker rather than approached with random writes.
|
||||||
|
- The existing router is used without changes for the first observation gate.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
The project may reach a legitimate BLOCKED result before receiving live data.
|
||||||
|
This is still a successful pre-production conclusion because it identifies the
|
||||||
|
exact missing interface and prevents premature decoder work or risky device
|
||||||
|
mutation.
|
||||||
|
|
||||||
|
|
@ -0,0 +1,220 @@
|
||||||
|
# Codex prompt: k1-mac-link
|
||||||
|
|
||||||
|
Ты работаешь над проектом `k1-mac-link`.
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
|
||||||
|
Есть официальный XGRIDS / LixelKity K1, MacBook, Wi-Fi router. Linux как отдельного стенда нет. Цель - использовать K1 как black-box realtime computer vision sensor без модификации firmware и без вскрытия на первом этапе.
|
||||||
|
|
||||||
|
Нужны realtime/cached данные:
|
||||||
|
|
||||||
|
- point cloud / preview cloud;
|
||||||
|
- pose / trajectory / odometry-like stream, если есть;
|
||||||
|
- camera / panoramic preview / RGB stream, если есть;
|
||||||
|
- device status;
|
||||||
|
- raw packets для reverse engineering.
|
||||||
|
|
||||||
|
Не нужно в runtime:
|
||||||
|
|
||||||
|
- считать LAS;
|
||||||
|
- считать LCC;
|
||||||
|
- считать Gaussian splats;
|
||||||
|
- менять firmware;
|
||||||
|
- делать brute force;
|
||||||
|
- слать random BLE writes.
|
||||||
|
|
||||||
|
## Главная архитектура
|
||||||
|
|
||||||
|
Официальные мануалы K1 показывают такую модель:
|
||||||
|
|
||||||
|
- BLE используется для discovery/provisioning;
|
||||||
|
- SSID/password - это реквизиты нашей Wi-Fi сети;
|
||||||
|
- после подключения к Wi-Fi есть отдельный `data connection status`;
|
||||||
|
- K1 умеет автономно стартовать scanning double-click кнопкой;
|
||||||
|
- K1 пишет `map.las` как realtime point cloud output, но downsampled;
|
||||||
|
- K1 пишет `poses.csv` как trajectory recorded during scanning;
|
||||||
|
- K1 содержит 2 panoramic cameras и 360 deg LiDAR;
|
||||||
|
- live camera stream наружу не доказан, его надо искать экспериментально.
|
||||||
|
|
||||||
|
## Создай проект
|
||||||
|
|
||||||
|
Python 3.12, macOS-first, Typer CLI.
|
||||||
|
|
||||||
|
Структура:
|
||||||
|
|
||||||
|
```text
|
||||||
|
k1-mac-link/
|
||||||
|
README.md
|
||||||
|
pyproject.toml
|
||||||
|
docs/
|
||||||
|
src/k1link/
|
||||||
|
cli.py
|
||||||
|
config.py
|
||||||
|
logging.py
|
||||||
|
artifacts.py
|
||||||
|
ble/
|
||||||
|
scanner.py
|
||||||
|
gatt_dump.py
|
||||||
|
listener.py
|
||||||
|
provisioner.py
|
||||||
|
net/
|
||||||
|
discover.py
|
||||||
|
ports.py
|
||||||
|
capture.py
|
||||||
|
mdns.py
|
||||||
|
tshark.py
|
||||||
|
analysis/
|
||||||
|
pcap_summary.py
|
||||||
|
flow_classifier.py
|
||||||
|
payload_extract.py
|
||||||
|
payload_fingerprint.py
|
||||||
|
protocol_diff.py
|
||||||
|
pointcloud_probe.py
|
||||||
|
video_probe.py
|
||||||
|
timeline.py
|
||||||
|
decode/
|
||||||
|
pointcloud.py
|
||||||
|
pose.py
|
||||||
|
video.py
|
||||||
|
status.py
|
||||||
|
cache/
|
||||||
|
writer.py
|
||||||
|
manifest.py
|
||||||
|
raw_stream.py
|
||||||
|
export/
|
||||||
|
ply.py
|
||||||
|
pcd.py
|
||||||
|
npz.py
|
||||||
|
frames.py
|
||||||
|
tests/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
Use:
|
||||||
|
|
||||||
|
```text
|
||||||
|
bleak
|
||||||
|
typer
|
||||||
|
rich
|
||||||
|
pydantic
|
||||||
|
pydantic-settings
|
||||||
|
loguru
|
||||||
|
pyshark
|
||||||
|
scapy
|
||||||
|
construct
|
||||||
|
numpy
|
||||||
|
pandas
|
||||||
|
opencv-python
|
||||||
|
open3d
|
||||||
|
pillow
|
||||||
|
zeroconf
|
||||||
|
ifaddr
|
||||||
|
psutil
|
||||||
|
pytest
|
||||||
|
pytest-asyncio
|
||||||
|
ruff
|
||||||
|
mypy
|
||||||
|
```
|
||||||
|
|
||||||
|
## CLI commands
|
||||||
|
|
||||||
|
Implement:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
k1link doctor
|
||||||
|
k1link ble scan --duration 30 --out captures/ble_scan.json
|
||||||
|
k1link ble gatt-dump --device <macos_uuid> --out captures/gatt_dump.json
|
||||||
|
k1link ble listen --device <macos_uuid> --duration 120 --out captures/ble_notifications.jsonl
|
||||||
|
k1link net discover --subnet 192.168.1.0/24 --out captures/net_discover.json
|
||||||
|
k1link net ports --ip <K1_IP> --out captures/ports.json
|
||||||
|
k1link net capture --iface en0 --host <K1_IP> --out captures/session.pcap
|
||||||
|
k1link analyze summarize --pcap captures/session.pcap --out captures/session_summary.json
|
||||||
|
k1link analyze extract-flows --pcap captures/session.pcap --out-dir captures/flows
|
||||||
|
k1link analyze fingerprint --flows captures/flows --out captures/fingerprint.json
|
||||||
|
k1link analyze pointcloud-probe --flows captures/flows --out-dir captures/pointcloud_candidates
|
||||||
|
k1link analyze video-probe --flows captures/flows --out-dir captures/video_candidates
|
||||||
|
```
|
||||||
|
|
||||||
|
## Safety rules
|
||||||
|
|
||||||
|
Allowed by default:
|
||||||
|
|
||||||
|
- BLE scan;
|
||||||
|
- GATT discovery;
|
||||||
|
- read characteristics;
|
||||||
|
- notifications;
|
||||||
|
- network discovery;
|
||||||
|
- port scanning of owned device;
|
||||||
|
- tcpdump capture;
|
||||||
|
- pcap analysis.
|
||||||
|
|
||||||
|
Forbidden by default:
|
||||||
|
|
||||||
|
- BLE write;
|
||||||
|
- random fuzzing;
|
||||||
|
- brute force;
|
||||||
|
- firmware upload;
|
||||||
|
- deleting/modifying K1 files;
|
||||||
|
- SSH/ADB login attempts.
|
||||||
|
|
||||||
|
Any BLE write must require:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
--i-understand-this-writes-to-device
|
||||||
|
```
|
||||||
|
|
||||||
|
Provisioner stays NotImplemented until a known payload profile is manually added.
|
||||||
|
|
||||||
|
## Outputs
|
||||||
|
|
||||||
|
Every session creates:
|
||||||
|
|
||||||
|
```text
|
||||||
|
session_manifest.json
|
||||||
|
notes.md
|
||||||
|
captures/*.pcap
|
||||||
|
captures/*.json
|
||||||
|
flows/*.bin
|
||||||
|
flows/*.jsonl
|
||||||
|
analysis/*.json
|
||||||
|
decoded/pointcloud/*
|
||||||
|
decoded/video/*
|
||||||
|
decoded/pose/*
|
||||||
|
```
|
||||||
|
|
||||||
|
Raw captures are never deleted.
|
||||||
|
|
||||||
|
## Implement pointcloud probe
|
||||||
|
|
||||||
|
Try:
|
||||||
|
|
||||||
|
- float32 little-endian XYZ;
|
||||||
|
- offsets 0..128;
|
||||||
|
- strides 12, 16, 20, 24, 28, 32, 40, 48;
|
||||||
|
- scaled int16/int32 fallback;
|
||||||
|
- score finite ratio, coordinate range, variance, temporal continuity;
|
||||||
|
- export PLY/NPZ for top candidates.
|
||||||
|
|
||||||
|
## Implement video probe
|
||||||
|
|
||||||
|
Detect/extract:
|
||||||
|
|
||||||
|
- JPEG FF D8 FF ... FF D9;
|
||||||
|
- PNG signature;
|
||||||
|
- H264/H265 Annex B;
|
||||||
|
- MP4 ftyp/moof/mdat;
|
||||||
|
- RTSP/RTP;
|
||||||
|
- MJPEG;
|
||||||
|
- WebSocket binary large frames.
|
||||||
|
|
||||||
|
Try ffmpeg/OpenCV extraction where possible.
|
||||||
|
|
||||||
|
## README must include
|
||||||
|
|
||||||
|
- macOS setup;
|
||||||
|
- Wireshark ChmodBPF note;
|
||||||
|
- Bluetooth permission note;
|
||||||
|
- Bleak macOS UUID caveat;
|
||||||
|
- K1 lab network setup;
|
||||||
|
- experiment flow: idle capture, button scan capture, app scan capture, camera perturbation, point cloud perturbation.
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Reference inputs
|
||||||
|
|
||||||
|
These files are immutable copies of the user-supplied pre-production material.
|
||||||
|
They are retained as inputs, not treated as verified specifications.
|
||||||
|
|
||||||
|
| File | SHA-256 | Role |
|
||||||
|
|---|---|---|
|
||||||
|
| `CODEX_K1_MAC_LINK_PREPROD_PROMPT.md` | `f24537119bd76f76d1914ca7f34a6736d8c5d3b6d19ead6d0ae8a0e5966c9770` | Requested project shape and CLI scope |
|
||||||
|
| `XGRIDS_Lixel_K1_PreProduction_Bible.md` | `22ea9d0dc6f5a623ba26d6f08108a905170db08112e0531a1d80bddd6101689c` | OSINT dossier, hypotheses, experiments and long-form design |
|
||||||
|
|
||||||
|
Important contextual correction: the actual stand has no LixelGO and no mobile
|
||||||
|
device. All experiments in the source documents that require an app or a
|
||||||
|
reference app session are unavailable. The active plan is therefore defined by
|
||||||
|
`docs/01_IMPLEMENTATION_PLAN.md`.
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
"schema_version": 1,
|
||||||
|
"session_id": "20260715T120000Z_gate1_baseline",
|
||||||
|
"experiment": "autonomous_button_scan",
|
||||||
|
"tool": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"git_commit": "UNCOMMITTED"
|
||||||
|
},
|
||||||
|
"host": {
|
||||||
|
"macos_version": "26.5.1",
|
||||||
|
"machine": "arm64",
|
||||||
|
"python_version": "3.12.13",
|
||||||
|
"wifi_interface": "en0",
|
||||||
|
"vpn_active": true
|
||||||
|
},
|
||||||
|
"device": {
|
||||||
|
"model": "XGRIDS LixelKity K1",
|
||||||
|
"serial_suffix": null,
|
||||||
|
"activation_state": "unknown",
|
||||||
|
"firmware": null
|
||||||
|
},
|
||||||
|
"network": {
|
||||||
|
"topology": "unknown",
|
||||||
|
"k1_address_redacted": null
|
||||||
|
},
|
||||||
|
"timeline": [
|
||||||
|
{
|
||||||
|
"event": "operator_started_notes",
|
||||||
|
"monotonic_seconds": 0,
|
||||||
|
"utc": "2026-07-15T12:00:00Z",
|
||||||
|
"note": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"artifacts": [],
|
||||||
|
"decision": {
|
||||||
|
"status": "PAUSE",
|
||||||
|
"gate": "gate1_device_baseline",
|
||||||
|
"reason": "Example only; no physical device observation has been performed.",
|
||||||
|
"next_smallest_experiment": "Record the K1 boot LED sequence without changing the router."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling>=1.27,<2"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "ndc-xgrids-k1-connector"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "macOS-first, evidence-led research tooling for an XGRIDS Lixel K1"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.12,<3.13"
|
||||||
|
license = { text = "Proprietary" }
|
||||||
|
authors = [{ name = "NODE.DC" }]
|
||||||
|
dependencies = [
|
||||||
|
"rich>=13.9,<15",
|
||||||
|
"typer>=0.15,<1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
k1link = "k1link.cli:app"
|
||||||
|
|
||||||
|
[dependency-groups]
|
||||||
|
dev = [
|
||||||
|
"mypy>=1.15,<2",
|
||||||
|
"pytest>=8.3,<9",
|
||||||
|
"ruff>=0.11,<1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.hatch.build.targets.wheel]
|
||||||
|
packages = ["src/k1link"]
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
addopts = "-q"
|
||||||
|
testpaths = ["tests"]
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 100
|
||||||
|
target-version = "py312"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = ["E", "F", "I", "UP", "B", "SIM"]
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
python_version = "3.12"
|
||||||
|
strict = true
|
||||||
|
packages = ["k1link"]
|
||||||
|
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://git.dcserve.ru/SILVER/NDC_xgrids-k1-connector/schemas/session-manifest.schema.json",
|
||||||
|
"title": "K1 laboratory session manifest",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"schema_version",
|
||||||
|
"session_id",
|
||||||
|
"experiment",
|
||||||
|
"tool",
|
||||||
|
"host",
|
||||||
|
"device",
|
||||||
|
"network",
|
||||||
|
"timeline",
|
||||||
|
"artifacts",
|
||||||
|
"decision"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"schema_version": { "const": 1 },
|
||||||
|
"session_id": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[0-9]{8}T[0-9]{6}Z_[a-z0-9_-]+$"
|
||||||
|
},
|
||||||
|
"experiment": { "type": "string", "minLength": 1 },
|
||||||
|
"tool": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["version", "git_commit"],
|
||||||
|
"properties": {
|
||||||
|
"version": { "type": "string" },
|
||||||
|
"git_commit": { "type": "string" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"host": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["macos_version", "machine", "python_version"],
|
||||||
|
"properties": {
|
||||||
|
"macos_version": { "type": "string" },
|
||||||
|
"machine": { "type": "string" },
|
||||||
|
"python_version": { "type": "string" },
|
||||||
|
"wifi_interface": { "type": ["string", "null"] },
|
||||||
|
"vpn_active": { "type": "boolean" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"device": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["model", "activation_state", "firmware"],
|
||||||
|
"properties": {
|
||||||
|
"model": { "type": "string" },
|
||||||
|
"serial_suffix": { "type": ["string", "null"], "maxLength": 8 },
|
||||||
|
"activation_state": {
|
||||||
|
"enum": ["unknown", "active", "activation_lock", "fault"]
|
||||||
|
},
|
||||||
|
"firmware": { "type": ["string", "null"] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"network": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["topology", "k1_address_redacted"],
|
||||||
|
"properties": {
|
||||||
|
"topology": {
|
||||||
|
"enum": ["unknown", "current_lan", "device_ap", "direct"]
|
||||||
|
},
|
||||||
|
"k1_address_redacted": { "type": ["string", "null"] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timeline": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["event", "monotonic_seconds"],
|
||||||
|
"properties": {
|
||||||
|
"event": { "type": "string", "minLength": 1 },
|
||||||
|
"monotonic_seconds": { "type": "number", "minimum": 0 },
|
||||||
|
"utc": { "type": ["string", "null"], "format": "date-time" },
|
||||||
|
"note": { "type": ["string", "null"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"artifacts": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["path", "size_bytes", "sha256", "classification"],
|
||||||
|
"properties": {
|
||||||
|
"path": { "type": "string", "minLength": 1 },
|
||||||
|
"size_bytes": { "type": "integer", "minimum": 0 },
|
||||||
|
"sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
||||||
|
"classification": {
|
||||||
|
"enum": ["public", "redacted", "sensitive", "secret"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"decision": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["status", "gate", "reason"],
|
||||||
|
"properties": {
|
||||||
|
"status": { "enum": ["GO", "PAUSE", "BLOCKED"] },
|
||||||
|
"gate": { "type": "string", "minLength": 1 },
|
||||||
|
"reason": { "type": "string", "minLength": 1 },
|
||||||
|
"next_smallest_experiment": { "type": ["string", "null"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
"""NDC XGRIDS K1 connector research tooling."""
|
||||||
|
|
||||||
|
__version__ = "0.1.0"
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import platform
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import TypedDict
|
||||||
|
|
||||||
|
import typer
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.table import Table
|
||||||
|
|
||||||
|
from k1link import __version__
|
||||||
|
|
||||||
|
app = typer.Typer(
|
||||||
|
name="k1link",
|
||||||
|
help="Safe, evidence-led research tooling for an owner-controlled XGRIDS K1.",
|
||||||
|
no_args_is_help=True,
|
||||||
|
)
|
||||||
|
console = Console()
|
||||||
|
|
||||||
|
|
||||||
|
class ToolStatus(TypedDict):
|
||||||
|
name: str
|
||||||
|
available: bool
|
||||||
|
path: str | None
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkStatus(TypedDict):
|
||||||
|
wifi_interface: str | None
|
||||||
|
default_route_interface: str | None
|
||||||
|
vpn_default_route: bool
|
||||||
|
|
||||||
|
|
||||||
|
class DoctorPayload(TypedDict):
|
||||||
|
k1link_version: str
|
||||||
|
python_version: str
|
||||||
|
python_executable: str
|
||||||
|
python_is_3_12: bool
|
||||||
|
local_venv: bool
|
||||||
|
platform: str
|
||||||
|
machine: str
|
||||||
|
network: NetworkStatus
|
||||||
|
tools: list[ToolStatus]
|
||||||
|
notes: list[str]
|
||||||
|
|
||||||
|
|
||||||
|
def _tool_status(name: str) -> ToolStatus:
|
||||||
|
path = shutil.which(name)
|
||||||
|
return {"name": name, "available": path is not None, "path": path}
|
||||||
|
|
||||||
|
|
||||||
|
def _command_output(args: list[str]) -> str | None:
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
args,
|
||||||
|
check=False,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=5,
|
||||||
|
)
|
||||||
|
except (OSError, subprocess.SubprocessError):
|
||||||
|
return None
|
||||||
|
if result.returncode != 0:
|
||||||
|
return None
|
||||||
|
return result.stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def _default_route_interface() -> str | None:
|
||||||
|
output = _command_output(["route", "-n", "get", "default"])
|
||||||
|
if output is None:
|
||||||
|
return None
|
||||||
|
for line in output.splitlines():
|
||||||
|
key, separator, value = line.strip().partition(":")
|
||||||
|
if separator and key == "interface":
|
||||||
|
return value.strip()
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _wifi_interface() -> str | None:
|
||||||
|
output = _command_output(["networksetup", "-listallhardwareports"])
|
||||||
|
if output is None:
|
||||||
|
return None
|
||||||
|
blocks = output.split("\n\n")
|
||||||
|
for block in blocks:
|
||||||
|
if "Hardware Port: Wi-Fi" not in block:
|
||||||
|
continue
|
||||||
|
for line in block.splitlines():
|
||||||
|
key, separator, value = line.partition(":")
|
||||||
|
if separator and key.strip() == "Device":
|
||||||
|
return value.strip()
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _doctor_payload() -> DoctorPayload:
|
||||||
|
executable = Path(sys.executable)
|
||||||
|
default_route = _default_route_interface()
|
||||||
|
wifi_interface = _wifi_interface()
|
||||||
|
notes = [
|
||||||
|
"Bluetooth permission is intentionally not requested by doctor.",
|
||||||
|
"Missing tshark/nmap is acceptable before the network-analysis gate.",
|
||||||
|
"No Homebrew or system changes are performed by this command.",
|
||||||
|
]
|
||||||
|
if default_route is not None and default_route.startswith("utun"):
|
||||||
|
notes.append(
|
||||||
|
"Default route uses a VPN/tunnel interface; future K1 commands must resolve "
|
||||||
|
"the route for the confirmed K1 IP instead of assuming the default route."
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"k1link_version": __version__,
|
||||||
|
"python_version": platform.python_version(),
|
||||||
|
"python_executable": str(executable),
|
||||||
|
"python_is_3_12": sys.version_info[:2] == (3, 12),
|
||||||
|
"local_venv": Path(sys.prefix).name == ".venv",
|
||||||
|
"platform": platform.platform(),
|
||||||
|
"machine": platform.machine(),
|
||||||
|
"network": {
|
||||||
|
"wifi_interface": wifi_interface,
|
||||||
|
"default_route_interface": default_route,
|
||||||
|
"vpn_default_route": bool(default_route and default_route.startswith("utun")),
|
||||||
|
},
|
||||||
|
"tools": [_tool_status(name) for name in ("uv", "tcpdump", "tshark", "nmap", "ffmpeg")],
|
||||||
|
"notes": notes,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@app.callback()
|
||||||
|
def main() -> None:
|
||||||
|
"""Run safe research commands for an owner-controlled XGRIDS K1."""
|
||||||
|
|
||||||
|
|
||||||
|
@app.command()
|
||||||
|
def doctor(
|
||||||
|
json_output: bool = typer.Option(False, "--json", help="Emit machine-readable JSON."),
|
||||||
|
) -> None:
|
||||||
|
"""Inspect the local toolchain without touching the K1 or system configuration."""
|
||||||
|
payload = _doctor_payload()
|
||||||
|
if json_output:
|
||||||
|
typer.echo(json.dumps(payload, ensure_ascii=False, indent=2))
|
||||||
|
return
|
||||||
|
|
||||||
|
console.print(f"k1link {payload['k1link_version']}")
|
||||||
|
console.print(f"Python {payload['python_version']} ({payload['python_executable']})")
|
||||||
|
console.print(
|
||||||
|
"Local .venv: " + ("[green]yes[/green]" if payload["local_venv"] else "[red]no[/red]")
|
||||||
|
)
|
||||||
|
network = payload["network"]
|
||||||
|
console.print(
|
||||||
|
"Wi-Fi interface: "
|
||||||
|
f"{network['wifi_interface'] or '-'}; default route: "
|
||||||
|
f"{network['default_route_interface'] or '-'}"
|
||||||
|
)
|
||||||
|
|
||||||
|
table = Table(title="External tools")
|
||||||
|
table.add_column("Tool")
|
||||||
|
table.add_column("Available")
|
||||||
|
table.add_column("Path")
|
||||||
|
for item in payload["tools"]:
|
||||||
|
table.add_row(
|
||||||
|
str(item["name"]),
|
||||||
|
"yes" if item["available"] else "no",
|
||||||
|
str(item["path"] or "-"),
|
||||||
|
)
|
||||||
|
console.print(table)
|
||||||
|
for note in payload["notes"]:
|
||||||
|
console.print(f"- {note}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app()
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
from typer.testing import CliRunner
|
||||||
|
|
||||||
|
from k1link.cli import app
|
||||||
|
|
||||||
|
runner = CliRunner()
|
||||||
|
|
||||||
|
|
||||||
|
def test_help() -> None:
|
||||||
|
result = runner.invoke(app, ["--help"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "evidence-led" in result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def test_doctor_json() -> None:
|
||||||
|
result = runner.invoke(app, ["doctor", "--json"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
payload = json.loads(result.stdout)
|
||||||
|
assert payload["k1link_version"] == "0.1.0"
|
||||||
|
assert isinstance(payload["tools"], list)
|
||||||
|
assert isinstance(payload["network"], dict)
|
||||||
|
assert any(item["name"] == "tcpdump" for item in payload["tools"])
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).parents[1]
|
||||||
|
|
||||||
|
|
||||||
|
def _sha256(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 test_reference_inputs_are_unchanged() -> None:
|
||||||
|
expected = {
|
||||||
|
"CODEX_K1_MAC_LINK_PREPROD_PROMPT.md": (
|
||||||
|
"f24537119bd76f76d1914ca7f34a6736d8c5d3b6d19ead6d0ae8a0e5966c9770"
|
||||||
|
),
|
||||||
|
"XGRIDS_Lixel_K1_PreProduction_Bible.md": (
|
||||||
|
"22ea9d0dc6f5a623ba26d6f08108a905170db08112e0531a1d80bddd6101689c"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
for filename, expected_hash in expected.items():
|
||||||
|
assert _sha256(ROOT / "docs" / "reference" / filename) == expected_hash
|
||||||
|
|
||||||
|
|
||||||
|
def test_redacted_manifest_example_is_json() -> None:
|
||||||
|
path = ROOT / "examples" / "session_manifest.redacted.json"
|
||||||
|
payload = json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
assert payload["schema_version"] == 1
|
||||||
|
assert payload["decision"]["status"] in {"GO", "PAUSE", "BLOCKED"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_manifest_schema_is_json() -> None:
|
||||||
|
path = ROOT / "schemas" / "session-manifest.schema.json"
|
||||||
|
payload = json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
assert payload["$schema"] == "https://json-schema.org/draft/2020-12/schema"
|
||||||
|
assert payload["properties"]["schema_version"]["const"] == 1
|
||||||
|
|
@ -0,0 +1,255 @@
|
||||||
|
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 = "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 = "iniconfig"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "librt"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/dc/2f/3908645ddddab7120b46295e541ead308109fa48dbec7d67d7a778870d60/librt-0.13.0.tar.gz", hash = "sha256:1d2a610c14ac0d0750ee0a3ab8548e83155258387891caaca04def4bf7289781", size = 211402, upload-time = "2026-07-08T12:26:29.834Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f0/f4/b2933ddae222dac338476abb872641169a5cfed2c2bb5444a5b07b32b0c3/librt-0.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30536798f4504c0fad0885b1d371b0539abb081e4570c9d7c641cb51141b49f0", size = 150990, upload-time = "2026-07-08T12:25:02.42Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/90/ef/db98f744ca50e6efc9c95c70ee49b77aefac31f6a3fc7c83754a42d6a74f/librt-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:93d24ebb82aa4420b1409c389e7857bc35bd0b668007ac8172427d5c73cc8cc5", size = 155238, upload-time = "2026-07-08T12:25:03.681Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/03/e7/a197e7bc72baf2c61ce7fdc6906a5054dc05bd8da0819aa894e4857bf87e/librt-0.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb8a1adce42d8b75485a5d56a9623a50bcab995b6079f1dac59fc44034dd93d9", size = 503073, upload-time = "2026-07-08T12:25:05.049Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f8/e7/7887712e27da7c1ab80fcabb1de6eb24243964f6557cae530d4b70706dbd/librt-0.13.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0763ca2ab66058174f9dee426dc64f5e0a89c24a7df8d3fe3f1836c04e25de4b", size = 496528, upload-time = "2026-07-08T12:25:06.26Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/f0/f2283385bb6b950b26a1410f4ce51ec27231e0b3a4b925c46366d218b198/librt-0.13.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b222493da6e7b6199db9bd79502436cf5a27da3c1f7fa83c7e285444fc93fd03", size = 531786, upload-time = "2026-07-08T12:25:07.658Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/36/11/69ac3b54766ffba5fd7e5acebfb048d66dbe1f9f2d14516c2b3edc59cf87/librt-0.13.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fadc63331f4388c3dc90090448f682a7e9feafc11481391c1e94f2f907a3976e", size = 524393, upload-time = "2026-07-08T12:25:09.121Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/61/5f/d72f95fd444a926a3c14b4e24979474116988dd57a45be242077c45d3c22/librt-0.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:70d9c62a4cffd9f23396cd5ef93fc5d11b31596b9b7d6306074abe3d5fcf09bd", size = 543026, upload-time = "2026-07-08T12:25:10.459Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c4/08/dcd9993ad192737a004ba263d549f8ea605b326b952e7d6205c7d4170b76/librt-0.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:66c0e7e6b02a155576df2c77ec933a70b72da726e248c494abf690923e624348", size = 546829, upload-time = "2026-07-08T12:25:11.716Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/96/d5/6d9bb2f54e4109a956b7128836529653eb9d740f784bc47ed10a02c1000e/librt-0.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ac04bcd3328eb91d99dfedf6a60d9c1f15d3434e6f6daf922f0420f7d90b85c7", size = 535700, upload-time = "2026-07-08T12:25:13.144Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8c/f2/10946922503858a359492fa27f13e86228bde702116a740ac7b3cd185f24/librt-0.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db327e7271e653c32040b85ae6188059c924b57d7e1e29f935523fa017cd4e82", size = 573566, upload-time = "2026-07-08T12:25:14.336Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/a8/94f00e3c99479a18088af3685ea016c42f3c7d5d1964d8dbb40c08d7f1aa/librt-0.13.0-cp312-cp312-win32.whl", hash = "sha256:860bd1d8ba48456ce08feaf8d343a8aaeb2fa086f2bcaa2a923fa3f7a3ff9aa3", size = 106099, upload-time = "2026-07-08T12:25:16.159Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c9/7b/2da9c74c1ed25a89cc4e1c8e007ea2eb4a0f1fafa3e70d757fe3242c5c5c/librt-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:e54a315caf843c8d77e388cadc56ea9ded569935ee2d2347d7ea94992e5aa6fa", size = 126934, upload-time = "2026-07-08T12:25:17.275Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d0/65/aead61bbf3b5358593f9d4779d2a0e88eaf6ec191a6342dde36dd1df6371/librt-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:c718e99a0992127af84385378460db624103b559ab260435abcfe77a4e4ed1c1", size = 112236, upload-time = "2026-07-08T12:25:18.425Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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 = "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 = "mypy"
|
||||||
|
version = "1.20.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "librt", marker = "platform_python_implementation != 'PyPy'" },
|
||||||
|
{ name = "mypy-extensions" },
|
||||||
|
{ name = "pathspec" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/04/af/e3d4b3e9ec91a0ff9aabfdb38692952acf49bbb899c2e4c29acb3a6da3ae/mypy-1.20.2.tar.gz", hash = "sha256:e8222c26daaafd9e8626dec58ae36029f82585890589576f769a650dd20fd665", size = 3817349, upload-time = "2026-04-21T17:12:28.473Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/71/4e/7560e4528db9e9b147e4c0f22660466bf30a0a1fe3d63d1b9d3b0fd354ee/mypy-1.20.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4dbfcf869f6b0517f70cf0030ba6ea1d6645e132337a7d5204a18d8d5636c02b", size = 14539393, upload-time = "2026-04-21T17:07:12.52Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/32/d9/34a5efed8124f5a9234f55ac6a4ced4201e2c5b81e1109c49ad23190ec8c/mypy-1.20.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b6481b228d072315b053210b01ac320e1be243dc17f9e5887ef167f23f5fae4", size = 13361642, upload-time = "2026-04-21T17:06:53.742Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/14/eb377acf78c03c92d566a1510cda8137348215b5335085ef662ab82ecd3a/mypy-1.20.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34397cdced6b90b836e38182076049fdb41424322e0b0728c946b0939ebdf9f6", size = 13740347, upload-time = "2026-04-21T17:12:04.73Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b9/94/7e4634a32b641aa1c112422eed1bbece61ee16205f674190e8b536f884de/mypy-1.20.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5da6976f20cae27059ea8d0c86e7cef3de720e04c4bb9ee18e3690fdb792066", size = 14734042, upload-time = "2026-04-21T17:07:43.16Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7a/f3/f7e62395cb7f434541b4491a01149a4439e28ace4c0c632bbf5431e92d1f/mypy-1.20.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:56908d7e08318d39f85b1f0c6cfd47b0cac1a130da677630dac0de3e0623e102", size = 14964958, upload-time = "2026-04-21T17:11:00.665Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/0d/47e3c3a0ec2a876e35aeac365df3cac7776c36bbd4ed18cc521e1b9d255b/mypy-1.20.2-cp312-cp312-win_amd64.whl", hash = "sha256:d52ad8d78522da1d308789df651ee5379088e77c76cb1994858d40a426b343b9", size = 10911340, upload-time = "2026-04-21T17:10:49.179Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d6/b2/6c852d72e0ea8b01f49da817fb52539993cde327e7d010e0103dc12d0dac/mypy-1.20.2-cp312-cp312-win_arm64.whl", hash = "sha256:785b08db19c9f214dc37d65f7c165d19a30fcecb48abfa30f31b01b5acaabb58", size = 9833947, upload-time = "2026-04-21T17:09:05.267Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/28/9a/f23c163e25b11074188251b0b5a0342625fc1cdb6af604757174fa9acc9b/mypy-1.20.2-py3-none-any.whl", hash = "sha256:a94c5a76ab46c5e6257c7972b6c8cff0574201ca7dc05647e33e795d78680563", size = 2637314, upload-time = "2026-04-21T17:05:54.5Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy-extensions"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ndc-xgrids-k1-connector"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { editable = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "rich" },
|
||||||
|
{ name = "typer" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dev-dependencies]
|
||||||
|
dev = [
|
||||||
|
{ name = "mypy" },
|
||||||
|
{ name = "pytest" },
|
||||||
|
{ name = "ruff" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [
|
||||||
|
{ name = "rich", specifier = ">=13.9,<15" },
|
||||||
|
{ name = "typer", specifier = ">=0.15,<1" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata.requires-dev]
|
||||||
|
dev = [
|
||||||
|
{ name = "mypy", specifier = ">=1.15,<2" },
|
||||||
|
{ name = "pytest", specifier = ">=8.3,<9" },
|
||||||
|
{ name = "ruff", specifier = ">=0.11,<1" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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 = "pathspec"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pluggy"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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 = "pytest"
|
||||||
|
version = "8.4.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||||
|
{ name = "iniconfig" },
|
||||||
|
{ name = "packaging" },
|
||||||
|
{ name = "pluggy" },
|
||||||
|
{ name = "pygments" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rich"
|
||||||
|
version = "14.3.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "markdown-it-py" },
|
||||||
|
{ name = "pygments" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/e9/67/cae617f1351490c25a4b8ac3b8b63a4dda609295d8222bad12242dfdc629/rich-14.3.4.tar.gz", hash = "sha256:817e02727f2b25b40ef56f5aa2217f400c8489f79ca8f46ea2b70dd5e14558a9", size = 230524, upload-time = "2026-04-11T02:57:45.419Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b3/76/6d163cfac87b632216f71879e6b2cf17163f773ff59c00b5ff4900a80fa3/rich-14.3.4-py3-none-any.whl", hash = "sha256:07e7adb4690f68864777b1450859253bed81a99a31ac321ac1817b2313558952", size = 310480, upload-time = "2026-04-11T02:57:47.484Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ruff"
|
||||||
|
version = "0.15.21"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/0f/36/6f65aa9989acdec45d417192d8f4e7921931d8a6cf87ac74bce3eed98a8e/ruff-0.15.21.tar.gz", hash = "sha256:d0cfc841c572283c36548f82664a54ce6565567f1b0d5b4cf2caac693d8b7500", size = 4769401, upload-time = "2026-07-09T20:01:34.005Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d0/c6/ede15cac6839f3dbce52565c8f5164a8210e669c7bc4decb03e5bdf47d0d/ruff-0.15.21-py3-none-linux_armv6l.whl", hash = "sha256:63ea0e965e5d73c90e95b2434beeafc70820536717f561b32ab6e777cb9bdf5d", size = 10854342, upload-time = "2026-07-09T20:00:53.998Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/28/9d/d825b07ee7ea9e2d61df92a860033c94e06e7300d50a1c2653aac27d24fe/ruff-0.15.21-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0f212c5d7d54c01bbfe6dcab02b724a39300f3e34ed7acbe995ccb320a2c58bd", size = 11139539, upload-time = "2026-07-09T20:00:57.809Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f5/de/3b107712e642f063c7a9e0887c427b22cb44097de5aab36c05f2e280670c/ruff-0.15.21-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e6312e41bc96791299614995ea3a977c5857c3b5662b1ecef6755b02b87cb646", size = 10595437, upload-time = "2026-07-09T20:01:00.006Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/6f/b4523cc90ba239ede441447a19d0c968846a3012e5a0b0c5b62831a3d5e3/ruff-0.15.21-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01d65b4831c6b2a4ba8ee6faa84049d44d982b7a706e622c4094c509e51673be", size = 10990053, upload-time = "2026-07-09T20:01:02.187Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/92/cc/c6a9872a5375f0628875481cf2f66b13d7d865bf3ca2e57f91c7e762d976/ruff-0.15.21-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c5a913a589120ce67933d5d05fd6ddbcc2481c6a054980ee767f7414c72b4fd", size = 10666096, upload-time = "2026-07-09T20:01:04.299Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ab/97/c621f7a17e097f1790fa3af6374138823b330b2d03fc38337945daca212c/ruff-0.15.21-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef04b681d02ad4dc9620f00f83ac5c22f652d0e9a9cfe431d219b16ad5ccc41", size = 11537011, upload-time = "2026-07-09T20:01:06.771Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ea/51/d928727e476e25ccc57c6f449ffd80241a651a973ad949d39cfb2a771d28/ruff-0.15.21-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16d090c0740916594157e75b80d666eab8e78083b39b3b0e1d698f4670a17b86", size = 12347101, upload-time = "2026-07-09T20:01:08.859Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1e/88/8cd62026802b16018ad06931d87997cf795ba2a6239ab659606c87d96bf0/ruff-0.15.21-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a10e74757dd65004d779b73e2f3c5210156d9980b41224d50d2ebcf1db51e67", size = 11572001, upload-time = "2026-07-09T20:01:11.092Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b2/97/f63084cf55444fc110e8cb985ebfcc592af47f597d44453d778cb81bc156/ruff-0.15.21-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bab0905d2f29e0d9fbc3c373ed23db0095edaa3f71f1f4f519ec15134d9e85c8", size = 11549239, upload-time = "2026-07-09T20:01:13.27Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9d/77/f107da4a2874b7715914b03f09ba9c54424de3ff8a1cc5d015d3ee2ce0ac/ruff-0.15.21-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:00eca240af5789fec6fe7df74c088cc1f9644ed83027113468efba7c92b94075", size = 11535340, upload-time = "2026-07-09T20:01:15.206Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d5/e9/601deb322d3303a7bf212b0100ead6f2ee3f6a044d89c30f2f92bf83c731/ruff-0.15.21-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:262ab31557a75141325e32d3357f3597645a7f084e732b6b054dde428ecd9341", size = 10964048, upload-time = "2026-07-09T20:01:17.723Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ea/2e/0f2176d1e99c15192caea19c8c3a0a955246b4cb4de795042eeb616345cd/ruff-0.15.21-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:659c4e7a4212f83306045ec7c5e5a356d16d9a6ef4ae0c7a4d872914fc655d9d", size = 10667055, upload-time = "2026-07-09T20:01:19.73Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/60/abd74a02e0c4214f12a68becfd30af7165cfdcb0e661ecdc60bbb949c09a/ruff-0.15.21-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9e866eab611a5f959d36df2d10e446973a3610bc42b0c15b31dc27977d59c233", size = 11242043, upload-time = "2026-07-09T20:01:21.947Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b2/c6/583075d8ccabb4b229345edcaf1545eb3d8d6be90f686a479d7e94088bbf/ruff-0.15.21-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e89bc93c0d3803ba870b55c29671bad9dc6d94bb1eb181b056b52eb05b52854f", size = 11648064, upload-time = "2026-07-09T20:01:24.023Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3a/3c/37d0ecb729a7cc2d393ea7dce316fc585680f35d93b8d62139d7d0a3700c/ruff-0.15.21-py3-none-win32.whl", hash = "sha256:01f8d5be84823c172b389e123174f781f9daf86d6c58719d603f941932195cdd", size = 10896555, upload-time = "2026-07-09T20:01:26.941Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c0/b8/e43466b2a6067ce91e669068f6e28d6c719a920f014b070d5c8731725de3/ruff-0.15.21-py3-none-win_amd64.whl", hash = "sha256:d4b8d9a2f0f12b816b50447f6eccb9f4bb01a6b82c86b50fb3b5354b458dc6d3", size = 12038772, upload-time = "2026-07-09T20:01:29.497Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dd/75/e90ab9aeece218a9fc5a5bc3ec97d0ee6bb3c4ff95869463c1de58e29a1c/ruff-0.15.21-py3-none-win_arm64.whl", hash = "sha256:6e83115d4b9377c1cbc13abf0e051f069fab0ef815ea0504a8a008cee24dd0a8", size = 11375265, upload-time = "2026-07-09T20:01:31.772Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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 = "typer"
|
||||||
|
version = "0.26.8"
|
||||||
|
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/7c/f7/68adc395201b20b872d68e975386832e8005ffeacedd43a1d837a32815be/typer-0.26.8.tar.gz", hash = "sha256:c244a6bd558886fe3f8780efb6bdd28bb9aff005a94eedebaa5cb32926fe2f7e", size = 202097, upload-time = "2026-06-26T09:22:45.705Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/80/87/b9fd69c92c6102a066e1b86a35243f53e70bd4c709f2a26d9f4fee4f4dc0/typer-0.26.8-py3-none-any.whl", hash = "sha256:3512ca79ac5c11113414b36e80281b872884477722440691c89d1112e321a49c", size = 122564, upload-time = "2026-06-26T09:22:44.72Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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" },
|
||||||
|
]
|
||||||
Loading…
Reference in New Issue